@xbrain-swe/nova-chat-widget 0.1.8 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +288 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +59 -37
- package/dist/index.d.ts +59 -37
- package/dist/index.js +288 -33
- package/dist/index.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1010,6 +1010,9 @@ async function consumeSse(response, handlers) {
|
|
|
1010
1010
|
continue;
|
|
1011
1011
|
}
|
|
1012
1012
|
switch (event.type) {
|
|
1013
|
+
case "start":
|
|
1014
|
+
if (typeof event.messageId === "string" && event.messageId) handlers.onMessageId?.(event.messageId);
|
|
1015
|
+
break;
|
|
1013
1016
|
case "text-start": {
|
|
1014
1017
|
const id = String(event.id);
|
|
1015
1018
|
textIndex.set(id, parts.length);
|
|
@@ -1086,7 +1089,7 @@ async function resumeToolResult(apiBaseUrl, apiKey, body, handlers) {
|
|
|
1086
1089
|
// src/use-chat.ts
|
|
1087
1090
|
var DEFAULT_CLIENT_ACTION_TIMEOUT_MS = 15e3;
|
|
1088
1091
|
function useXbNovaChat(options) {
|
|
1089
|
-
const { auth, trackRegistry, clientActionHandlers } = useXbNovaInternal();
|
|
1092
|
+
const { auth, trackRegistry, clientActionHandlers, visitorId } = useXbNovaInternal();
|
|
1090
1093
|
const botId = options?.botId ?? auth.botId;
|
|
1091
1094
|
const nudgeNodeId = options?.nudgeNodeId;
|
|
1092
1095
|
const { apiKey, apiBaseUrl } = auth;
|
|
@@ -1095,6 +1098,9 @@ function useXbNovaChat(options) {
|
|
|
1095
1098
|
const [busy, setBusy] = useState3(false);
|
|
1096
1099
|
const [error, setError] = useState3(null);
|
|
1097
1100
|
const [clientResults, setClientResults] = useState3({});
|
|
1101
|
+
const [feedback, setFeedbackState] = useState3({});
|
|
1102
|
+
const feedbackRef = useRef7(feedback);
|
|
1103
|
+
feedbackRef.current = feedback;
|
|
1098
1104
|
const dispatchedClientActionsRef = useRef7(/* @__PURE__ */ new Set());
|
|
1099
1105
|
const nudgeNodeIdRef = useRef7(nudgeNodeId);
|
|
1100
1106
|
nudgeNodeIdRef.current = nudgeNodeId;
|
|
@@ -1103,6 +1109,7 @@ function useXbNovaChat(options) {
|
|
|
1103
1109
|
setMessages([]);
|
|
1104
1110
|
setError(null);
|
|
1105
1111
|
setClientResults({});
|
|
1112
|
+
setFeedbackState({});
|
|
1106
1113
|
dispatchedClientActionsRef.current = /* @__PURE__ */ new Set();
|
|
1107
1114
|
fetch(`${apiBaseUrl}/v1/conversations`, {
|
|
1108
1115
|
method: "POST",
|
|
@@ -1112,9 +1119,42 @@ function useXbNovaChat(options) {
|
|
|
1112
1119
|
}, [apiBaseUrl, apiKey, botId]);
|
|
1113
1120
|
const appendToLastAssistant = (existingParts, newParts) => setMessages((prev) => {
|
|
1114
1121
|
const next = [...prev];
|
|
1115
|
-
next[next.length - 1] = { role: "assistant", parts: [...existingParts, ...newParts] };
|
|
1122
|
+
next[next.length - 1] = { ...next[next.length - 1], role: "assistant", parts: [...existingParts, ...newParts] };
|
|
1123
|
+
return next;
|
|
1124
|
+
});
|
|
1125
|
+
const setLastAssistantId = (id) => setMessages((prev) => {
|
|
1126
|
+
if (prev.length === 0) return prev;
|
|
1127
|
+
const next = [...prev];
|
|
1128
|
+
next[next.length - 1] = { ...next[next.length - 1], id };
|
|
1116
1129
|
return next;
|
|
1117
1130
|
});
|
|
1131
|
+
async function setFeedback(messageId, rating, reason) {
|
|
1132
|
+
const conversationId = onboarding?.conversation_id;
|
|
1133
|
+
if (!conversationId || !messageId) return;
|
|
1134
|
+
const prevRating = feedbackRef.current[messageId] ?? null;
|
|
1135
|
+
const apply = (r) => setFeedbackState((prev) => {
|
|
1136
|
+
const next = { ...prev };
|
|
1137
|
+
if (r === null) delete next[messageId];
|
|
1138
|
+
else next[messageId] = r;
|
|
1139
|
+
return next;
|
|
1140
|
+
});
|
|
1141
|
+
apply(rating);
|
|
1142
|
+
try {
|
|
1143
|
+
const res = await fetch(
|
|
1144
|
+
`${apiBaseUrl}/v1/conversations/${encodeURIComponent(conversationId)}/messages/${encodeURIComponent(messageId)}/feedback`,
|
|
1145
|
+
{
|
|
1146
|
+
method: "PUT",
|
|
1147
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
|
|
1148
|
+
body: JSON.stringify({ rating, visitor_id: visitorId, reason: rating === "down" ? reason ?? null : null })
|
|
1149
|
+
}
|
|
1150
|
+
);
|
|
1151
|
+
if (!res.ok) throw new Error(`${res.status}`);
|
|
1152
|
+
options?.onFeedback?.({ conversationId, messageId, rating, reason });
|
|
1153
|
+
} catch (e) {
|
|
1154
|
+
console.warn("[xbnova] \uD53C\uB4DC\uBC31 \uC800\uC7A5 \uC2E4\uD328", e);
|
|
1155
|
+
apply(prevRating);
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1118
1158
|
async function runClientAction(spec, part, existingParts) {
|
|
1119
1159
|
if (spec.dismissSheet) options?.onDismissSheet?.();
|
|
1120
1160
|
let result;
|
|
@@ -1160,7 +1200,11 @@ function useXbNovaChat(options) {
|
|
|
1160
1200
|
tool_call_id: part.toolCallId,
|
|
1161
1201
|
result_text: resultText
|
|
1162
1202
|
},
|
|
1163
|
-
{
|
|
1203
|
+
{
|
|
1204
|
+
onPart: (newParts) => appendToLastAssistant(existingParts, newParts),
|
|
1205
|
+
onMessageId: setLastAssistantId,
|
|
1206
|
+
onError: setError
|
|
1207
|
+
}
|
|
1164
1208
|
);
|
|
1165
1209
|
} catch (e) {
|
|
1166
1210
|
setError(e instanceof Error ? e.message : String(e));
|
|
@@ -1193,7 +1237,7 @@ function useXbNovaChat(options) {
|
|
|
1193
1237
|
]);
|
|
1194
1238
|
const replaceLast = (parts) => setMessages((prev) => {
|
|
1195
1239
|
const next = [...prev];
|
|
1196
|
-
next[next.length - 1] = { role: "assistant", parts };
|
|
1240
|
+
next[next.length - 1] = { ...next[next.length - 1], role: "assistant", parts };
|
|
1197
1241
|
return next;
|
|
1198
1242
|
});
|
|
1199
1243
|
const focused = getFocusedTrackEntry(trackRegistry);
|
|
@@ -1209,7 +1253,7 @@ function useXbNovaChat(options) {
|
|
|
1209
1253
|
context,
|
|
1210
1254
|
nudge_node_id: nudgeNodeId ?? null
|
|
1211
1255
|
},
|
|
1212
|
-
{ onPart: replaceLast, onError: setError }
|
|
1256
|
+
{ onPart: replaceLast, onMessageId: setLastAssistantId, onError: setError }
|
|
1213
1257
|
);
|
|
1214
1258
|
} catch (e) {
|
|
1215
1259
|
setError(e instanceof Error ? e.message : String(e));
|
|
@@ -1217,7 +1261,7 @@ function useXbNovaChat(options) {
|
|
|
1217
1261
|
setBusy(false);
|
|
1218
1262
|
}
|
|
1219
1263
|
}
|
|
1220
|
-
return { onboarding, messages, busy, error, send, clientResults };
|
|
1264
|
+
return { onboarding, messages, busy, error, send, clientResults, feedback, setFeedback };
|
|
1221
1265
|
}
|
|
1222
1266
|
|
|
1223
1267
|
// src/views.tsx
|
|
@@ -1477,7 +1521,9 @@ function XbNovaChat({
|
|
|
1477
1521
|
defaultOpen = false,
|
|
1478
1522
|
idleText = "AI\uC5D0\uAC8C \uC774 \uCC28 \uBB3C\uC5B4\uBCF4\uAE30",
|
|
1479
1523
|
avatarSize = 28,
|
|
1480
|
-
onVerdictAction
|
|
1524
|
+
onVerdictAction,
|
|
1525
|
+
feedback: feedbackEnabled = true,
|
|
1526
|
+
onFeedback
|
|
1481
1527
|
}) {
|
|
1482
1528
|
const { auth, trackRegistry, factsRegistry, visitorId, clientActionHandlers, theme } = useXbNovaInternal();
|
|
1483
1529
|
const themeVars = {
|
|
@@ -1500,7 +1546,8 @@ function XbNovaChat({
|
|
|
1500
1546
|
const [input, setInput] = useState4("");
|
|
1501
1547
|
const bottomRef = useRef8(null);
|
|
1502
1548
|
const typedHintText = useTypewriter(hint?.text ?? "", hint?.triggerNodeId ?? "");
|
|
1503
|
-
const { onboarding, messages, busy, error, send, clientResults } = useXbNovaChat({
|
|
1549
|
+
const { onboarding, messages, busy, error, send, clientResults, feedback, setFeedback } = useXbNovaChat({
|
|
1550
|
+
onFeedback,
|
|
1504
1551
|
botId: activeBotId ?? botId,
|
|
1505
1552
|
nudgeNodeId: activeNudgeNodeId,
|
|
1506
1553
|
onDismissSheet: () => setOpen(false)
|
|
@@ -1729,31 +1776,41 @@ function XbNovaChat({
|
|
|
1729
1776
|
/* @__PURE__ */ jsxs3("div", { className: "flex-1 space-y-3 overflow-y-auto p-4", children: [
|
|
1730
1777
|
showDisclosure ? /* @__PURE__ */ jsx7("div", { className: "rounded-lg bg-neutral-50 px-3 py-2 text-[11px] text-neutral-400 dark:bg-neutral-800", children: "\uC9C0\uAE08 \uBCF4\uACE0 \uACC4\uC2E0 \uD654\uBA74\uACFC \uB9E4\uBB3C \uC815\uBCF4\uB97C \uAE30\uC900\uC73C\uB85C \uC548\uB0B4\uD574 \uB4DC\uB824\uC694." }) : null,
|
|
1731
1778
|
firstBubble ? /* @__PURE__ */ jsx7(Bubble, { role: "assistant", children: /* @__PURE__ */ jsx7(AssistantMarkdown, { text: firstBubble }) }) : null,
|
|
1732
|
-
messages.map((message, i) => /* @__PURE__ */ jsx7("div", { children: message.role === "user" ? /* @__PURE__ */ jsx7(Bubble, { role: "user", children: message.parts.map((p) => p.kind === "text" ? p.text : "").join("") }) : /* @__PURE__ */ jsxs3(
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1779
|
+
messages.map((message, i) => /* @__PURE__ */ jsx7("div", { children: message.role === "user" ? /* @__PURE__ */ jsx7(Bubble, { role: "user", children: message.parts.map((p) => p.kind === "text" ? p.text : "").join("") }) : /* @__PURE__ */ jsxs3(
|
|
1780
|
+
AnswerActionsFrame,
|
|
1781
|
+
{
|
|
1782
|
+
enabled: feedbackEnabled && !busy && !!message.id && message.parts.some((p) => p.kind === "text" && p.text.trim()),
|
|
1783
|
+
text: message.parts.filter((p) => p.kind === "text").map((p) => p.kind === "text" ? p.text : "").join("\n\n"),
|
|
1784
|
+
rating: message.id ? feedback[message.id] ?? null : null,
|
|
1785
|
+
isLast: i === messages.length - 1,
|
|
1786
|
+
onRate: (r, reason) => void setFeedback(message.id, r, reason),
|
|
1787
|
+
children: [
|
|
1788
|
+
message.parts.map((part, j) => {
|
|
1789
|
+
if (part.kind === "text") {
|
|
1790
|
+
return /* @__PURE__ */ jsx7(Bubble, { role: "assistant", children: /* @__PURE__ */ jsx7(AssistantMarkdown, { text: part.text }) }, j);
|
|
1791
|
+
}
|
|
1792
|
+
if (clientActionHandlers.has(part.toolName)) {
|
|
1793
|
+
const result = clientResults[part.toolCallId];
|
|
1794
|
+
if (result) {
|
|
1795
|
+
return /* @__PURE__ */ jsx7(
|
|
1796
|
+
ToolView,
|
|
1797
|
+
{
|
|
1798
|
+
part: { ...part, state: "output", view: result.view, output: result.output },
|
|
1799
|
+
viewRenderers,
|
|
1800
|
+
onVerdictAction
|
|
1801
|
+
},
|
|
1802
|
+
j
|
|
1803
|
+
);
|
|
1804
|
+
}
|
|
1805
|
+
const spec = clientActionHandlers.get(part.toolName);
|
|
1806
|
+
return spec?.render ? /* @__PURE__ */ jsx7("div", { children: spec.render(part.input) }, j) : null;
|
|
1807
|
+
}
|
|
1808
|
+
return /* @__PURE__ */ jsx7(ToolView, { part, viewRenderers, onVerdictAction }, j);
|
|
1809
|
+
}),
|
|
1810
|
+
!hasVisiblePart(message.parts, clientActionHandlers, clientResults) && busy ? /* @__PURE__ */ jsx7(Bubble, { role: "assistant", children: /* @__PURE__ */ jsx7("span", { className: "text-neutral-400", children: "\uC0DD\uAC01\uD558\uB294 \uC911\u2026" }) }) : null
|
|
1811
|
+
]
|
|
1812
|
+
}
|
|
1813
|
+
) }, i)),
|
|
1757
1814
|
error ? /* @__PURE__ */ jsx7("div", { className: "rounded-lg bg-red-50 px-3 py-2 text-xs text-red-700 dark:bg-red-950 dark:text-red-300", children: error }) : null,
|
|
1758
1815
|
/* @__PURE__ */ jsx7("div", { ref: bottomRef })
|
|
1759
1816
|
] }),
|
|
@@ -1854,6 +1911,196 @@ function Avatar({
|
|
|
1854
1911
|
const text = branding?.avatar_text?.trim() || "AI";
|
|
1855
1912
|
return /* @__PURE__ */ jsx7("span", { className: base, style: { ...gradient, ...box, fontSize: Math.max(10, Math.round(size * 0.4)) }, children: text });
|
|
1856
1913
|
}
|
|
1914
|
+
var THUMB_UP_PATH = "M7 10v12M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88Z";
|
|
1915
|
+
var THUMB_DOWN_PATH = "M17 14V2M9 18.12 10 14H4.17a2 2 0 0 1-1.92-2.56l2.33-8A2 2 0 0 1 6.5 2H20a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2.76a2 2 0 0 0-1.79 1.11L12 22a3.13 3.13 0 0 1-3-3.88Z";
|
|
1916
|
+
var COPY_PATH = "M9 9h11v11H9zM5 15H4a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1";
|
|
1917
|
+
var CHECK_PATH = "m5 12 5 5L20 7";
|
|
1918
|
+
var FEEDBACK_REASONS = [
|
|
1919
|
+
{ code: "inaccurate", label: "\uC815\uD655\uD558\uC9C0 \uC54A\uC544\uC694" },
|
|
1920
|
+
{ code: "irrelevant", label: "\uC9C8\uBB38\uACFC \uB2EC\uB77C\uC694" },
|
|
1921
|
+
{ code: "too_long", label: "\uB108\uBB34 \uAE38\uC5B4\uC694" },
|
|
1922
|
+
{ code: "unclear", label: "\uC774\uD574\uD558\uAE30 \uC5B4\uB824\uC6CC\uC694" }
|
|
1923
|
+
];
|
|
1924
|
+
var LONG_PRESS_MS = 420;
|
|
1925
|
+
function ActionIcon({ path, filled }) {
|
|
1926
|
+
return /* @__PURE__ */ jsx7(
|
|
1927
|
+
"svg",
|
|
1928
|
+
{
|
|
1929
|
+
width: "16",
|
|
1930
|
+
height: "16",
|
|
1931
|
+
viewBox: "0 0 24 24",
|
|
1932
|
+
fill: filled ? "currentColor" : "none",
|
|
1933
|
+
stroke: "currentColor",
|
|
1934
|
+
strokeWidth: "2",
|
|
1935
|
+
strokeLinecap: "round",
|
|
1936
|
+
strokeLinejoin: "round",
|
|
1937
|
+
"aria-hidden": "true",
|
|
1938
|
+
children: /* @__PURE__ */ jsx7("path", { d: path })
|
|
1939
|
+
}
|
|
1940
|
+
);
|
|
1941
|
+
}
|
|
1942
|
+
async function copyText(text) {
|
|
1943
|
+
try {
|
|
1944
|
+
await navigator.clipboard.writeText(text);
|
|
1945
|
+
return true;
|
|
1946
|
+
} catch {
|
|
1947
|
+
try {
|
|
1948
|
+
const ta = document.createElement("textarea");
|
|
1949
|
+
ta.value = text;
|
|
1950
|
+
ta.style.position = "fixed";
|
|
1951
|
+
ta.style.opacity = "0";
|
|
1952
|
+
document.body.appendChild(ta);
|
|
1953
|
+
ta.select();
|
|
1954
|
+
const ok = document.execCommand("copy");
|
|
1955
|
+
ta.remove();
|
|
1956
|
+
return ok;
|
|
1957
|
+
} catch {
|
|
1958
|
+
return false;
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
function AnswerActions({
|
|
1963
|
+
text,
|
|
1964
|
+
rating,
|
|
1965
|
+
isLast,
|
|
1966
|
+
onRate,
|
|
1967
|
+
children
|
|
1968
|
+
}) {
|
|
1969
|
+
const rootRef = useRef8(null);
|
|
1970
|
+
const timerRef = useRef8(null);
|
|
1971
|
+
const [menuOpen, setMenuOpen] = useState4(false);
|
|
1972
|
+
const [reasonOpen, setReasonOpen] = useState4(false);
|
|
1973
|
+
const [copied, setCopied] = useState4(false);
|
|
1974
|
+
const [entered, setEntered] = useState4(false);
|
|
1975
|
+
const clearTimer = () => {
|
|
1976
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
1977
|
+
timerRef.current = null;
|
|
1978
|
+
};
|
|
1979
|
+
useEffect8(() => clearTimer, []);
|
|
1980
|
+
useEffect8(() => {
|
|
1981
|
+
if (!menuOpen) {
|
|
1982
|
+
setEntered(false);
|
|
1983
|
+
return;
|
|
1984
|
+
}
|
|
1985
|
+
const raf = requestAnimationFrame(() => setEntered(true));
|
|
1986
|
+
const onDown = (e) => {
|
|
1987
|
+
if (!e.composedPath().includes(rootRef.current)) setMenuOpen(false);
|
|
1988
|
+
};
|
|
1989
|
+
document.addEventListener("pointerdown", onDown, true);
|
|
1990
|
+
return () => {
|
|
1991
|
+
cancelAnimationFrame(raf);
|
|
1992
|
+
document.removeEventListener("pointerdown", onDown, true);
|
|
1993
|
+
};
|
|
1994
|
+
}, [menuOpen]);
|
|
1995
|
+
const startPress = (e) => {
|
|
1996
|
+
if (e.pointerType === "mouse" || isLast) return;
|
|
1997
|
+
clearTimer();
|
|
1998
|
+
timerRef.current = setTimeout(() => {
|
|
1999
|
+
setMenuOpen(true);
|
|
2000
|
+
if (typeof navigator.vibrate === "function") navigator.vibrate(10);
|
|
2001
|
+
}, LONG_PRESS_MS);
|
|
2002
|
+
};
|
|
2003
|
+
const handleCopy = async () => {
|
|
2004
|
+
if (await copyText(text)) {
|
|
2005
|
+
setCopied(true);
|
|
2006
|
+
setTimeout(() => setCopied(false), 1400);
|
|
2007
|
+
}
|
|
2008
|
+
};
|
|
2009
|
+
const handleRate = (kind) => {
|
|
2010
|
+
if (rating === kind) {
|
|
2011
|
+
onRate(null);
|
|
2012
|
+
setReasonOpen(false);
|
|
2013
|
+
return;
|
|
2014
|
+
}
|
|
2015
|
+
onRate(kind);
|
|
2016
|
+
setReasonOpen(kind === "down");
|
|
2017
|
+
if (kind === "up") setMenuOpen(false);
|
|
2018
|
+
};
|
|
2019
|
+
const barVisible = isLast || menuOpen;
|
|
2020
|
+
const floating = menuOpen && !isLast;
|
|
2021
|
+
const btn = (label, onClick, selected, color, icon) => /* @__PURE__ */ jsxs3(
|
|
2022
|
+
"button",
|
|
2023
|
+
{
|
|
2024
|
+
type: "button",
|
|
2025
|
+
"aria-label": label,
|
|
2026
|
+
"aria-pressed": selected,
|
|
2027
|
+
onClick,
|
|
2028
|
+
className: `flex items-center justify-center gap-1 rounded-full text-neutral-400 transition-colors hover:text-neutral-600 dark:text-neutral-500 dark:hover:text-neutral-300 ${floating ? "h-9 px-3 text-xs font-semibold" : "h-7 w-7"}`,
|
|
2029
|
+
style: color ? { color } : void 0,
|
|
2030
|
+
children: [
|
|
2031
|
+
icon,
|
|
2032
|
+
floating ? /* @__PURE__ */ jsx7("span", { children: label }) : null
|
|
2033
|
+
]
|
|
2034
|
+
}
|
|
2035
|
+
);
|
|
2036
|
+
return /* @__PURE__ */ jsxs3(
|
|
2037
|
+
"div",
|
|
2038
|
+
{
|
|
2039
|
+
ref: rootRef,
|
|
2040
|
+
className: "group space-y-1",
|
|
2041
|
+
onPointerDown: startPress,
|
|
2042
|
+
onPointerUp: clearTimer,
|
|
2043
|
+
onPointerCancel: clearTimer,
|
|
2044
|
+
onPointerLeave: clearTimer,
|
|
2045
|
+
onPointerMove: (e) => {
|
|
2046
|
+
if (Math.abs(e.movementX) + Math.abs(e.movementY) > 6) clearTimer();
|
|
2047
|
+
},
|
|
2048
|
+
onContextMenu: (e) => {
|
|
2049
|
+
if (!isLast && e.nativeEvent.pointerType !== "mouse") e.preventDefault();
|
|
2050
|
+
},
|
|
2051
|
+
children: [
|
|
2052
|
+
children,
|
|
2053
|
+
/* @__PURE__ */ jsx7(
|
|
2054
|
+
"div",
|
|
2055
|
+
{
|
|
2056
|
+
className: `flex pl-1 ${barVisible ? "" : "hidden md:group-hover:flex"}`,
|
|
2057
|
+
style: { transition: "opacity 140ms, transform 140ms", opacity: floating && !entered ? 0 : 1, transform: floating && !entered ? "scale(0.94)" : "none" },
|
|
2058
|
+
children: /* @__PURE__ */ jsxs3(
|
|
2059
|
+
"div",
|
|
2060
|
+
{
|
|
2061
|
+
className: `flex items-center ${floating ? "gap-0.5 rounded-full border border-neutral-200 bg-white p-1 shadow-lg dark:border-neutral-700 dark:bg-neutral-800" : "gap-0.5"}`,
|
|
2062
|
+
children: [
|
|
2063
|
+
btn(copied ? "\uBCF5\uC0AC\uB428" : "\uBCF5\uC0AC", handleCopy, false, copied ? "var(--xbnova-accent-1, #4f46e5)" : void 0, /* @__PURE__ */ jsx7(ActionIcon, { path: copied ? CHECK_PATH : COPY_PATH })),
|
|
2064
|
+
btn(
|
|
2065
|
+
"\uC88B\uC544\uC694",
|
|
2066
|
+
() => handleRate("up"),
|
|
2067
|
+
rating === "up",
|
|
2068
|
+
rating === "up" ? "var(--xbnova-accent-1, #4f46e5)" : void 0,
|
|
2069
|
+
/* @__PURE__ */ jsx7(ActionIcon, { path: THUMB_UP_PATH, filled: rating === "up" })
|
|
2070
|
+
),
|
|
2071
|
+
btn(
|
|
2072
|
+
"\uBCC4\uB85C\uC608\uC694",
|
|
2073
|
+
() => handleRate("down"),
|
|
2074
|
+
rating === "down",
|
|
2075
|
+
rating === "down" ? "#dc2626" : void 0,
|
|
2076
|
+
/* @__PURE__ */ jsx7(ActionIcon, { path: THUMB_DOWN_PATH, filled: rating === "down" })
|
|
2077
|
+
)
|
|
2078
|
+
]
|
|
2079
|
+
}
|
|
2080
|
+
)
|
|
2081
|
+
}
|
|
2082
|
+
),
|
|
2083
|
+
rating === "down" && reasonOpen ? /* @__PURE__ */ jsxs3("div", { className: "flex flex-wrap items-center gap-1.5 pl-1 pt-0.5", children: [
|
|
2084
|
+
/* @__PURE__ */ jsx7("span", { className: "text-[11px] text-neutral-400", children: "\uC5B4\uB5A4 \uC810\uC774 \uC544\uC26C\uC6E0\uB098\uC694?" }),
|
|
2085
|
+
FEEDBACK_REASONS.map((r) => /* @__PURE__ */ jsx7(
|
|
2086
|
+
"button",
|
|
2087
|
+
{
|
|
2088
|
+
type: "button",
|
|
2089
|
+
onClick: () => {
|
|
2090
|
+
onRate("down", r.code);
|
|
2091
|
+
setReasonOpen(false);
|
|
2092
|
+
setMenuOpen(false);
|
|
2093
|
+
},
|
|
2094
|
+
className: "xbnova-chip rounded-full border px-2.5 py-1 text-[11px] font-semibold transition",
|
|
2095
|
+
children: r.label
|
|
2096
|
+
},
|
|
2097
|
+
r.code
|
|
2098
|
+
))
|
|
2099
|
+
] }) : null
|
|
2100
|
+
]
|
|
2101
|
+
}
|
|
2102
|
+
);
|
|
2103
|
+
}
|
|
1857
2104
|
function Bubble({ role, children }) {
|
|
1858
2105
|
const mine = role === "user";
|
|
1859
2106
|
return /* @__PURE__ */ jsx7("div", { className: mine ? "flex justify-end" : "flex justify-start", children: /* @__PURE__ */ jsx7(
|
|
@@ -1867,6 +2114,14 @@ function Bubble({ role, children }) {
|
|
|
1867
2114
|
function AssistantMarkdown({ text }) {
|
|
1868
2115
|
return /* @__PURE__ */ jsx7("div", { className: "space-y-1.5 text-sm leading-relaxed [&_ol]:list-decimal [&_ul]:list-disc [&_ol]:pl-5 [&_ul]:pl-5 [&_li]:pl-0.5 [&_strong]:font-semibold [&_a]:underline [&_code]:rounded [&_code]:bg-black/10 [&_code]:px-1 [&_code]:py-0.5 [&_code]:text-[0.85em] [&_p]:mb-1.5 last:[&_p]:mb-0", children: /* @__PURE__ */ jsx7(ReactMarkdown, { remarkPlugins: [remarkGfm, remarkBreaks], children: text }) });
|
|
1869
2116
|
}
|
|
2117
|
+
function AnswerActionsFrame({
|
|
2118
|
+
enabled,
|
|
2119
|
+
children,
|
|
2120
|
+
...rest
|
|
2121
|
+
}) {
|
|
2122
|
+
if (!enabled) return /* @__PURE__ */ jsx7("div", { className: "space-y-1", children });
|
|
2123
|
+
return /* @__PURE__ */ jsx7(AnswerActions, { ...rest, children });
|
|
2124
|
+
}
|
|
1870
2125
|
|
|
1871
2126
|
// src/facts.ts
|
|
1872
2127
|
import { useEffect as useEffect9, useRef as useRef9 } from "react";
|