@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.cjs
CHANGED
|
@@ -1057,6 +1057,9 @@ async function consumeSse(response, handlers) {
|
|
|
1057
1057
|
continue;
|
|
1058
1058
|
}
|
|
1059
1059
|
switch (event.type) {
|
|
1060
|
+
case "start":
|
|
1061
|
+
if (typeof event.messageId === "string" && event.messageId) handlers.onMessageId?.(event.messageId);
|
|
1062
|
+
break;
|
|
1060
1063
|
case "text-start": {
|
|
1061
1064
|
const id = String(event.id);
|
|
1062
1065
|
textIndex.set(id, parts.length);
|
|
@@ -1133,7 +1136,7 @@ async function resumeToolResult(apiBaseUrl, apiKey, body, handlers) {
|
|
|
1133
1136
|
// src/use-chat.ts
|
|
1134
1137
|
var DEFAULT_CLIENT_ACTION_TIMEOUT_MS = 15e3;
|
|
1135
1138
|
function useXbNovaChat(options) {
|
|
1136
|
-
const { auth, trackRegistry, clientActionHandlers } = useXbNovaInternal();
|
|
1139
|
+
const { auth, trackRegistry, clientActionHandlers, visitorId } = useXbNovaInternal();
|
|
1137
1140
|
const botId = options?.botId ?? auth.botId;
|
|
1138
1141
|
const nudgeNodeId = options?.nudgeNodeId;
|
|
1139
1142
|
const { apiKey, apiBaseUrl } = auth;
|
|
@@ -1142,6 +1145,9 @@ function useXbNovaChat(options) {
|
|
|
1142
1145
|
const [busy, setBusy] = (0, import_react8.useState)(false);
|
|
1143
1146
|
const [error, setError] = (0, import_react8.useState)(null);
|
|
1144
1147
|
const [clientResults, setClientResults] = (0, import_react8.useState)({});
|
|
1148
|
+
const [feedback, setFeedbackState] = (0, import_react8.useState)({});
|
|
1149
|
+
const feedbackRef = (0, import_react8.useRef)(feedback);
|
|
1150
|
+
feedbackRef.current = feedback;
|
|
1145
1151
|
const dispatchedClientActionsRef = (0, import_react8.useRef)(/* @__PURE__ */ new Set());
|
|
1146
1152
|
const nudgeNodeIdRef = (0, import_react8.useRef)(nudgeNodeId);
|
|
1147
1153
|
nudgeNodeIdRef.current = nudgeNodeId;
|
|
@@ -1150,6 +1156,7 @@ function useXbNovaChat(options) {
|
|
|
1150
1156
|
setMessages([]);
|
|
1151
1157
|
setError(null);
|
|
1152
1158
|
setClientResults({});
|
|
1159
|
+
setFeedbackState({});
|
|
1153
1160
|
dispatchedClientActionsRef.current = /* @__PURE__ */ new Set();
|
|
1154
1161
|
fetch(`${apiBaseUrl}/v1/conversations`, {
|
|
1155
1162
|
method: "POST",
|
|
@@ -1159,9 +1166,42 @@ function useXbNovaChat(options) {
|
|
|
1159
1166
|
}, [apiBaseUrl, apiKey, botId]);
|
|
1160
1167
|
const appendToLastAssistant = (existingParts, newParts) => setMessages((prev) => {
|
|
1161
1168
|
const next = [...prev];
|
|
1162
|
-
next[next.length - 1] = { role: "assistant", parts: [...existingParts, ...newParts] };
|
|
1169
|
+
next[next.length - 1] = { ...next[next.length - 1], role: "assistant", parts: [...existingParts, ...newParts] };
|
|
1170
|
+
return next;
|
|
1171
|
+
});
|
|
1172
|
+
const setLastAssistantId = (id) => setMessages((prev) => {
|
|
1173
|
+
if (prev.length === 0) return prev;
|
|
1174
|
+
const next = [...prev];
|
|
1175
|
+
next[next.length - 1] = { ...next[next.length - 1], id };
|
|
1163
1176
|
return next;
|
|
1164
1177
|
});
|
|
1178
|
+
async function setFeedback(messageId, rating, reason) {
|
|
1179
|
+
const conversationId = onboarding?.conversation_id;
|
|
1180
|
+
if (!conversationId || !messageId) return;
|
|
1181
|
+
const prevRating = feedbackRef.current[messageId] ?? null;
|
|
1182
|
+
const apply = (r) => setFeedbackState((prev) => {
|
|
1183
|
+
const next = { ...prev };
|
|
1184
|
+
if (r === null) delete next[messageId];
|
|
1185
|
+
else next[messageId] = r;
|
|
1186
|
+
return next;
|
|
1187
|
+
});
|
|
1188
|
+
apply(rating);
|
|
1189
|
+
try {
|
|
1190
|
+
const res = await fetch(
|
|
1191
|
+
`${apiBaseUrl}/v1/conversations/${encodeURIComponent(conversationId)}/messages/${encodeURIComponent(messageId)}/feedback`,
|
|
1192
|
+
{
|
|
1193
|
+
method: "PUT",
|
|
1194
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
|
|
1195
|
+
body: JSON.stringify({ rating, visitor_id: visitorId, reason: rating === "down" ? reason ?? null : null })
|
|
1196
|
+
}
|
|
1197
|
+
);
|
|
1198
|
+
if (!res.ok) throw new Error(`${res.status}`);
|
|
1199
|
+
options?.onFeedback?.({ conversationId, messageId, rating, reason });
|
|
1200
|
+
} catch (e) {
|
|
1201
|
+
console.warn("[xbnova] \uD53C\uB4DC\uBC31 \uC800\uC7A5 \uC2E4\uD328", e);
|
|
1202
|
+
apply(prevRating);
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1165
1205
|
async function runClientAction(spec, part, existingParts) {
|
|
1166
1206
|
if (spec.dismissSheet) options?.onDismissSheet?.();
|
|
1167
1207
|
let result;
|
|
@@ -1207,7 +1247,11 @@ function useXbNovaChat(options) {
|
|
|
1207
1247
|
tool_call_id: part.toolCallId,
|
|
1208
1248
|
result_text: resultText
|
|
1209
1249
|
},
|
|
1210
|
-
{
|
|
1250
|
+
{
|
|
1251
|
+
onPart: (newParts) => appendToLastAssistant(existingParts, newParts),
|
|
1252
|
+
onMessageId: setLastAssistantId,
|
|
1253
|
+
onError: setError
|
|
1254
|
+
}
|
|
1211
1255
|
);
|
|
1212
1256
|
} catch (e) {
|
|
1213
1257
|
setError(e instanceof Error ? e.message : String(e));
|
|
@@ -1240,7 +1284,7 @@ function useXbNovaChat(options) {
|
|
|
1240
1284
|
]);
|
|
1241
1285
|
const replaceLast = (parts) => setMessages((prev) => {
|
|
1242
1286
|
const next = [...prev];
|
|
1243
|
-
next[next.length - 1] = { role: "assistant", parts };
|
|
1287
|
+
next[next.length - 1] = { ...next[next.length - 1], role: "assistant", parts };
|
|
1244
1288
|
return next;
|
|
1245
1289
|
});
|
|
1246
1290
|
const focused = getFocusedTrackEntry(trackRegistry);
|
|
@@ -1256,7 +1300,7 @@ function useXbNovaChat(options) {
|
|
|
1256
1300
|
context,
|
|
1257
1301
|
nudge_node_id: nudgeNodeId ?? null
|
|
1258
1302
|
},
|
|
1259
|
-
{ onPart: replaceLast, onError: setError }
|
|
1303
|
+
{ onPart: replaceLast, onMessageId: setLastAssistantId, onError: setError }
|
|
1260
1304
|
);
|
|
1261
1305
|
} catch (e) {
|
|
1262
1306
|
setError(e instanceof Error ? e.message : String(e));
|
|
@@ -1264,7 +1308,7 @@ function useXbNovaChat(options) {
|
|
|
1264
1308
|
setBusy(false);
|
|
1265
1309
|
}
|
|
1266
1310
|
}
|
|
1267
|
-
return { onboarding, messages, busy, error, send, clientResults };
|
|
1311
|
+
return { onboarding, messages, busy, error, send, clientResults, feedback, setFeedback };
|
|
1268
1312
|
}
|
|
1269
1313
|
|
|
1270
1314
|
// src/views.tsx
|
|
@@ -1524,7 +1568,9 @@ function XbNovaChat({
|
|
|
1524
1568
|
defaultOpen = false,
|
|
1525
1569
|
idleText = "AI\uC5D0\uAC8C \uC774 \uCC28 \uBB3C\uC5B4\uBCF4\uAE30",
|
|
1526
1570
|
avatarSize = 28,
|
|
1527
|
-
onVerdictAction
|
|
1571
|
+
onVerdictAction,
|
|
1572
|
+
feedback: feedbackEnabled = true,
|
|
1573
|
+
onFeedback
|
|
1528
1574
|
}) {
|
|
1529
1575
|
const { auth, trackRegistry, factsRegistry, visitorId, clientActionHandlers, theme } = useXbNovaInternal();
|
|
1530
1576
|
const themeVars = {
|
|
@@ -1547,7 +1593,8 @@ function XbNovaChat({
|
|
|
1547
1593
|
const [input, setInput] = (0, import_react9.useState)("");
|
|
1548
1594
|
const bottomRef = (0, import_react9.useRef)(null);
|
|
1549
1595
|
const typedHintText = useTypewriter(hint?.text ?? "", hint?.triggerNodeId ?? "");
|
|
1550
|
-
const { onboarding, messages, busy, error, send, clientResults } = useXbNovaChat({
|
|
1596
|
+
const { onboarding, messages, busy, error, send, clientResults, feedback, setFeedback } = useXbNovaChat({
|
|
1597
|
+
onFeedback,
|
|
1551
1598
|
botId: activeBotId ?? botId,
|
|
1552
1599
|
nudgeNodeId: activeNudgeNodeId,
|
|
1553
1600
|
onDismissSheet: () => setOpen(false)
|
|
@@ -1776,31 +1823,41 @@ function XbNovaChat({
|
|
|
1776
1823
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex-1 space-y-3 overflow-y-auto p-4", children: [
|
|
1777
1824
|
showDisclosure ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("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,
|
|
1778
1825
|
firstBubble ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Bubble, { role: "assistant", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(AssistantMarkdown, { text: firstBubble }) }) : null,
|
|
1779
|
-
messages.map((message, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { children: message.role === "user" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Bubble, { role: "user", children: message.parts.map((p) => p.kind === "text" ? p.text : "").join("") }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1826
|
+
messages.map((message, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { children: message.role === "user" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Bubble, { role: "user", children: message.parts.map((p) => p.kind === "text" ? p.text : "").join("") }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1827
|
+
AnswerActionsFrame,
|
|
1828
|
+
{
|
|
1829
|
+
enabled: feedbackEnabled && !busy && !!message.id && message.parts.some((p) => p.kind === "text" && p.text.trim()),
|
|
1830
|
+
text: message.parts.filter((p) => p.kind === "text").map((p) => p.kind === "text" ? p.text : "").join("\n\n"),
|
|
1831
|
+
rating: message.id ? feedback[message.id] ?? null : null,
|
|
1832
|
+
isLast: i === messages.length - 1,
|
|
1833
|
+
onRate: (r, reason) => void setFeedback(message.id, r, reason),
|
|
1834
|
+
children: [
|
|
1835
|
+
message.parts.map((part, j) => {
|
|
1836
|
+
if (part.kind === "text") {
|
|
1837
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Bubble, { role: "assistant", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(AssistantMarkdown, { text: part.text }) }, j);
|
|
1838
|
+
}
|
|
1839
|
+
if (clientActionHandlers.has(part.toolName)) {
|
|
1840
|
+
const result = clientResults[part.toolCallId];
|
|
1841
|
+
if (result) {
|
|
1842
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1843
|
+
ToolView,
|
|
1844
|
+
{
|
|
1845
|
+
part: { ...part, state: "output", view: result.view, output: result.output },
|
|
1846
|
+
viewRenderers,
|
|
1847
|
+
onVerdictAction
|
|
1848
|
+
},
|
|
1849
|
+
j
|
|
1850
|
+
);
|
|
1851
|
+
}
|
|
1852
|
+
const spec = clientActionHandlers.get(part.toolName);
|
|
1853
|
+
return spec?.render ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { children: spec.render(part.input) }, j) : null;
|
|
1854
|
+
}
|
|
1855
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ToolView, { part, viewRenderers, onVerdictAction }, j);
|
|
1856
|
+
}),
|
|
1857
|
+
!hasVisiblePart(message.parts, clientActionHandlers, clientResults) && busy ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Bubble, { role: "assistant", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "text-neutral-400", children: "\uC0DD\uAC01\uD558\uB294 \uC911\u2026" }) }) : null
|
|
1858
|
+
]
|
|
1859
|
+
}
|
|
1860
|
+
) }, i)),
|
|
1804
1861
|
error ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("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,
|
|
1805
1862
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { ref: bottomRef })
|
|
1806
1863
|
] }),
|
|
@@ -1901,6 +1958,196 @@ function Avatar({
|
|
|
1901
1958
|
const text = branding?.avatar_text?.trim() || "AI";
|
|
1902
1959
|
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: base, style: { ...gradient, ...box, fontSize: Math.max(10, Math.round(size * 0.4)) }, children: text });
|
|
1903
1960
|
}
|
|
1961
|
+
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";
|
|
1962
|
+
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";
|
|
1963
|
+
var COPY_PATH = "M9 9h11v11H9zM5 15H4a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1";
|
|
1964
|
+
var CHECK_PATH = "m5 12 5 5L20 7";
|
|
1965
|
+
var FEEDBACK_REASONS = [
|
|
1966
|
+
{ code: "inaccurate", label: "\uC815\uD655\uD558\uC9C0 \uC54A\uC544\uC694" },
|
|
1967
|
+
{ code: "irrelevant", label: "\uC9C8\uBB38\uACFC \uB2EC\uB77C\uC694" },
|
|
1968
|
+
{ code: "too_long", label: "\uB108\uBB34 \uAE38\uC5B4\uC694" },
|
|
1969
|
+
{ code: "unclear", label: "\uC774\uD574\uD558\uAE30 \uC5B4\uB824\uC6CC\uC694" }
|
|
1970
|
+
];
|
|
1971
|
+
var LONG_PRESS_MS = 420;
|
|
1972
|
+
function ActionIcon({ path, filled }) {
|
|
1973
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1974
|
+
"svg",
|
|
1975
|
+
{
|
|
1976
|
+
width: "16",
|
|
1977
|
+
height: "16",
|
|
1978
|
+
viewBox: "0 0 24 24",
|
|
1979
|
+
fill: filled ? "currentColor" : "none",
|
|
1980
|
+
stroke: "currentColor",
|
|
1981
|
+
strokeWidth: "2",
|
|
1982
|
+
strokeLinecap: "round",
|
|
1983
|
+
strokeLinejoin: "round",
|
|
1984
|
+
"aria-hidden": "true",
|
|
1985
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: path })
|
|
1986
|
+
}
|
|
1987
|
+
);
|
|
1988
|
+
}
|
|
1989
|
+
async function copyText(text) {
|
|
1990
|
+
try {
|
|
1991
|
+
await navigator.clipboard.writeText(text);
|
|
1992
|
+
return true;
|
|
1993
|
+
} catch {
|
|
1994
|
+
try {
|
|
1995
|
+
const ta = document.createElement("textarea");
|
|
1996
|
+
ta.value = text;
|
|
1997
|
+
ta.style.position = "fixed";
|
|
1998
|
+
ta.style.opacity = "0";
|
|
1999
|
+
document.body.appendChild(ta);
|
|
2000
|
+
ta.select();
|
|
2001
|
+
const ok = document.execCommand("copy");
|
|
2002
|
+
ta.remove();
|
|
2003
|
+
return ok;
|
|
2004
|
+
} catch {
|
|
2005
|
+
return false;
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
function AnswerActions({
|
|
2010
|
+
text,
|
|
2011
|
+
rating,
|
|
2012
|
+
isLast,
|
|
2013
|
+
onRate,
|
|
2014
|
+
children
|
|
2015
|
+
}) {
|
|
2016
|
+
const rootRef = (0, import_react9.useRef)(null);
|
|
2017
|
+
const timerRef = (0, import_react9.useRef)(null);
|
|
2018
|
+
const [menuOpen, setMenuOpen] = (0, import_react9.useState)(false);
|
|
2019
|
+
const [reasonOpen, setReasonOpen] = (0, import_react9.useState)(false);
|
|
2020
|
+
const [copied, setCopied] = (0, import_react9.useState)(false);
|
|
2021
|
+
const [entered, setEntered] = (0, import_react9.useState)(false);
|
|
2022
|
+
const clearTimer = () => {
|
|
2023
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
2024
|
+
timerRef.current = null;
|
|
2025
|
+
};
|
|
2026
|
+
(0, import_react9.useEffect)(() => clearTimer, []);
|
|
2027
|
+
(0, import_react9.useEffect)(() => {
|
|
2028
|
+
if (!menuOpen) {
|
|
2029
|
+
setEntered(false);
|
|
2030
|
+
return;
|
|
2031
|
+
}
|
|
2032
|
+
const raf = requestAnimationFrame(() => setEntered(true));
|
|
2033
|
+
const onDown = (e) => {
|
|
2034
|
+
if (!e.composedPath().includes(rootRef.current)) setMenuOpen(false);
|
|
2035
|
+
};
|
|
2036
|
+
document.addEventListener("pointerdown", onDown, true);
|
|
2037
|
+
return () => {
|
|
2038
|
+
cancelAnimationFrame(raf);
|
|
2039
|
+
document.removeEventListener("pointerdown", onDown, true);
|
|
2040
|
+
};
|
|
2041
|
+
}, [menuOpen]);
|
|
2042
|
+
const startPress = (e) => {
|
|
2043
|
+
if (e.pointerType === "mouse" || isLast) return;
|
|
2044
|
+
clearTimer();
|
|
2045
|
+
timerRef.current = setTimeout(() => {
|
|
2046
|
+
setMenuOpen(true);
|
|
2047
|
+
if (typeof navigator.vibrate === "function") navigator.vibrate(10);
|
|
2048
|
+
}, LONG_PRESS_MS);
|
|
2049
|
+
};
|
|
2050
|
+
const handleCopy = async () => {
|
|
2051
|
+
if (await copyText(text)) {
|
|
2052
|
+
setCopied(true);
|
|
2053
|
+
setTimeout(() => setCopied(false), 1400);
|
|
2054
|
+
}
|
|
2055
|
+
};
|
|
2056
|
+
const handleRate = (kind) => {
|
|
2057
|
+
if (rating === kind) {
|
|
2058
|
+
onRate(null);
|
|
2059
|
+
setReasonOpen(false);
|
|
2060
|
+
return;
|
|
2061
|
+
}
|
|
2062
|
+
onRate(kind);
|
|
2063
|
+
setReasonOpen(kind === "down");
|
|
2064
|
+
if (kind === "up") setMenuOpen(false);
|
|
2065
|
+
};
|
|
2066
|
+
const barVisible = isLast || menuOpen;
|
|
2067
|
+
const floating = menuOpen && !isLast;
|
|
2068
|
+
const btn = (label, onClick, selected, color, icon) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
2069
|
+
"button",
|
|
2070
|
+
{
|
|
2071
|
+
type: "button",
|
|
2072
|
+
"aria-label": label,
|
|
2073
|
+
"aria-pressed": selected,
|
|
2074
|
+
onClick,
|
|
2075
|
+
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"}`,
|
|
2076
|
+
style: color ? { color } : void 0,
|
|
2077
|
+
children: [
|
|
2078
|
+
icon,
|
|
2079
|
+
floating ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: label }) : null
|
|
2080
|
+
]
|
|
2081
|
+
}
|
|
2082
|
+
);
|
|
2083
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
2084
|
+
"div",
|
|
2085
|
+
{
|
|
2086
|
+
ref: rootRef,
|
|
2087
|
+
className: "group space-y-1",
|
|
2088
|
+
onPointerDown: startPress,
|
|
2089
|
+
onPointerUp: clearTimer,
|
|
2090
|
+
onPointerCancel: clearTimer,
|
|
2091
|
+
onPointerLeave: clearTimer,
|
|
2092
|
+
onPointerMove: (e) => {
|
|
2093
|
+
if (Math.abs(e.movementX) + Math.abs(e.movementY) > 6) clearTimer();
|
|
2094
|
+
},
|
|
2095
|
+
onContextMenu: (e) => {
|
|
2096
|
+
if (!isLast && e.nativeEvent.pointerType !== "mouse") e.preventDefault();
|
|
2097
|
+
},
|
|
2098
|
+
children: [
|
|
2099
|
+
children,
|
|
2100
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2101
|
+
"div",
|
|
2102
|
+
{
|
|
2103
|
+
className: `flex pl-1 ${barVisible ? "" : "hidden md:group-hover:flex"}`,
|
|
2104
|
+
style: { transition: "opacity 140ms, transform 140ms", opacity: floating && !entered ? 0 : 1, transform: floating && !entered ? "scale(0.94)" : "none" },
|
|
2105
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
2106
|
+
"div",
|
|
2107
|
+
{
|
|
2108
|
+
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"}`,
|
|
2109
|
+
children: [
|
|
2110
|
+
btn(copied ? "\uBCF5\uC0AC\uB428" : "\uBCF5\uC0AC", handleCopy, false, copied ? "var(--xbnova-accent-1, #4f46e5)" : void 0, /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ActionIcon, { path: copied ? CHECK_PATH : COPY_PATH })),
|
|
2111
|
+
btn(
|
|
2112
|
+
"\uC88B\uC544\uC694",
|
|
2113
|
+
() => handleRate("up"),
|
|
2114
|
+
rating === "up",
|
|
2115
|
+
rating === "up" ? "var(--xbnova-accent-1, #4f46e5)" : void 0,
|
|
2116
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ActionIcon, { path: THUMB_UP_PATH, filled: rating === "up" })
|
|
2117
|
+
),
|
|
2118
|
+
btn(
|
|
2119
|
+
"\uBCC4\uB85C\uC608\uC694",
|
|
2120
|
+
() => handleRate("down"),
|
|
2121
|
+
rating === "down",
|
|
2122
|
+
rating === "down" ? "#dc2626" : void 0,
|
|
2123
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ActionIcon, { path: THUMB_DOWN_PATH, filled: rating === "down" })
|
|
2124
|
+
)
|
|
2125
|
+
]
|
|
2126
|
+
}
|
|
2127
|
+
)
|
|
2128
|
+
}
|
|
2129
|
+
),
|
|
2130
|
+
rating === "down" && reasonOpen ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-wrap items-center gap-1.5 pl-1 pt-0.5", children: [
|
|
2131
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "text-[11px] text-neutral-400", children: "\uC5B4\uB5A4 \uC810\uC774 \uC544\uC26C\uC6E0\uB098\uC694?" }),
|
|
2132
|
+
FEEDBACK_REASONS.map((r) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2133
|
+
"button",
|
|
2134
|
+
{
|
|
2135
|
+
type: "button",
|
|
2136
|
+
onClick: () => {
|
|
2137
|
+
onRate("down", r.code);
|
|
2138
|
+
setReasonOpen(false);
|
|
2139
|
+
setMenuOpen(false);
|
|
2140
|
+
},
|
|
2141
|
+
className: "xbnova-chip rounded-full border px-2.5 py-1 text-[11px] font-semibold transition",
|
|
2142
|
+
children: r.label
|
|
2143
|
+
},
|
|
2144
|
+
r.code
|
|
2145
|
+
))
|
|
2146
|
+
] }) : null
|
|
2147
|
+
]
|
|
2148
|
+
}
|
|
2149
|
+
);
|
|
2150
|
+
}
|
|
1904
2151
|
function Bubble({ role, children }) {
|
|
1905
2152
|
const mine = role === "user";
|
|
1906
2153
|
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: mine ? "flex justify-end" : "flex justify-start", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
@@ -1914,6 +2161,14 @@ function Bubble({ role, children }) {
|
|
|
1914
2161
|
function AssistantMarkdown({ text }) {
|
|
1915
2162
|
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("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__ */ (0, import_jsx_runtime7.jsx)(import_react_markdown.default, { remarkPlugins: [import_remark_gfm.default, import_remark_breaks.default], children: text }) });
|
|
1916
2163
|
}
|
|
2164
|
+
function AnswerActionsFrame({
|
|
2165
|
+
enabled,
|
|
2166
|
+
children,
|
|
2167
|
+
...rest
|
|
2168
|
+
}) {
|
|
2169
|
+
if (!enabled) return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "space-y-1", children });
|
|
2170
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(AnswerActions, { ...rest, children });
|
|
2171
|
+
}
|
|
1917
2172
|
|
|
1918
2173
|
// src/facts.ts
|
|
1919
2174
|
var import_react10 = require("react");
|