@frankieai/react 0.2.8 → 0.2.9
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/frankie.css +1 -1
- package/dist/index.cjs +373 -187
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +60 -8
- package/dist/index.d.ts +60 -8
- package/dist/index.js +356 -172
- package/dist/index.js.map +1 -1
- package/package.json +5 -2
- package/src/components/chat/ChatPanel.tsx +9 -1
- package/src/components/chat/Choices.tsx +52 -0
- package/src/components/chat/Composer.tsx +12 -2
- package/src/components/chat/FrankieChatPage.tsx +4 -1
- package/src/components/chat/FrankieInline.tsx +7 -2
- package/src/components/chat/FrankieWidget.tsx +11 -2
- package/src/components/chat/Markdown.tsx +15 -8
- package/src/components/chat/MessageBubble.tsx +29 -5
- package/src/hooks/useFrankieChat.ts +57 -1
- package/src/index.ts +3 -0
- package/src/lib/choices.ts +49 -0
- package/src/lib/styles-ready.ts +71 -0
- package/src/styles/frankie.css +106 -3
- package/src/styles-ready.test.tsx +71 -0
package/dist/index.cjs
CHANGED
|
@@ -31,6 +31,7 @@ __export(index_exports, {
|
|
|
31
31
|
CardHeader: () => CardHeader,
|
|
32
32
|
CardTitle: () => CardTitle,
|
|
33
33
|
ChatPanel: () => ChatPanel,
|
|
34
|
+
Choices: () => Choices,
|
|
34
35
|
Composer: () => Composer,
|
|
35
36
|
FRANKIE_CLOUD_URL: () => FRANKIE_CLOUD_URL,
|
|
36
37
|
FrankieApiError: () => FrankieApiError,
|
|
@@ -51,6 +52,7 @@ __export(index_exports, {
|
|
|
51
52
|
applyEvent: () => applyEvent,
|
|
52
53
|
buttonVariants: () => buttonVariants,
|
|
53
54
|
cn: () => cn,
|
|
55
|
+
extractChoices: () => extractChoices,
|
|
54
56
|
newAssistantMessage: () => newAssistantMessage,
|
|
55
57
|
newUserMessage: () => newUserMessage,
|
|
56
58
|
readEventStream: () => readEventStream,
|
|
@@ -463,6 +465,41 @@ function useFrankieChatState() {
|
|
|
463
465
|
setMessages(next);
|
|
464
466
|
const abort = new AbortController();
|
|
465
467
|
abortRef.current = abort;
|
|
468
|
+
let bufferedText = "";
|
|
469
|
+
let pendingThinking = "";
|
|
470
|
+
let frame = null;
|
|
471
|
+
const raf = typeof requestAnimationFrame === "function" ? requestAnimationFrame : (cb) => setTimeout(() => cb(performance.now()), 16);
|
|
472
|
+
const cancelRaf = typeof cancelAnimationFrame === "function" ? cancelAnimationFrame : clearTimeout;
|
|
473
|
+
const revealTick = () => {
|
|
474
|
+
frame = null;
|
|
475
|
+
let addText = "";
|
|
476
|
+
if (bufferedText) {
|
|
477
|
+
const step = Math.max(3, Math.ceil(bufferedText.length / 6));
|
|
478
|
+
addText = bufferedText.slice(0, step);
|
|
479
|
+
bufferedText = bufferedText.slice(step);
|
|
480
|
+
}
|
|
481
|
+
const addThinking = pendingThinking;
|
|
482
|
+
pendingThinking = "";
|
|
483
|
+
if (addText || addThinking) {
|
|
484
|
+
patchAssistant(assistantId, (message) => ({ ...message, text: message.text + addText, thinking: message.thinking + addThinking }));
|
|
485
|
+
}
|
|
486
|
+
if (bufferedText) scheduleReveal();
|
|
487
|
+
};
|
|
488
|
+
const scheduleReveal = () => {
|
|
489
|
+
if (frame === null) frame = raf(revealTick);
|
|
490
|
+
};
|
|
491
|
+
const flushDeltas = () => {
|
|
492
|
+
if (frame !== null) {
|
|
493
|
+
cancelRaf(frame);
|
|
494
|
+
frame = null;
|
|
495
|
+
}
|
|
496
|
+
if (!bufferedText && !pendingThinking) return;
|
|
497
|
+
const addText = bufferedText;
|
|
498
|
+
const addThinking = pendingThinking;
|
|
499
|
+
bufferedText = "";
|
|
500
|
+
pendingThinking = "";
|
|
501
|
+
patchAssistant(assistantId, (message) => ({ ...message, text: message.text + addText, thinking: message.thinking + addThinking }));
|
|
502
|
+
};
|
|
466
503
|
try {
|
|
467
504
|
let receivedText = false;
|
|
468
505
|
let receivedToolActivity = false;
|
|
@@ -484,11 +521,23 @@ function useFrankieChatState() {
|
|
|
484
521
|
return;
|
|
485
522
|
}
|
|
486
523
|
if (event.type === "ping" || event.type === "session") return;
|
|
487
|
-
if (event.type === "text_delta")
|
|
524
|
+
if (event.type === "text_delta") {
|
|
525
|
+
receivedText = true;
|
|
526
|
+
bufferedText += event.text;
|
|
527
|
+
scheduleReveal();
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
if (event.type === "thinking_delta") {
|
|
531
|
+
pendingThinking += event.text;
|
|
532
|
+
scheduleReveal();
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
488
535
|
if (event.type === "tool_start" || event.type === "tool_end") receivedToolActivity = true;
|
|
536
|
+
flushDeltas();
|
|
489
537
|
patchAssistant(assistantId, (message) => applyEvent(message, event));
|
|
490
538
|
}
|
|
491
539
|
);
|
|
540
|
+
flushDeltas();
|
|
492
541
|
if (!receivedText) {
|
|
493
542
|
patchAssistant(assistantId, (message) => ({
|
|
494
543
|
...message,
|
|
@@ -497,6 +546,7 @@ function useFrankieChatState() {
|
|
|
497
546
|
}));
|
|
498
547
|
}
|
|
499
548
|
} catch (caught) {
|
|
549
|
+
flushDeltas();
|
|
500
550
|
if (abort.signal.aborted) {
|
|
501
551
|
patchAssistant(assistantId, (message) => ({
|
|
502
552
|
...message,
|
|
@@ -509,6 +559,7 @@ function useFrankieChatState() {
|
|
|
509
559
|
patchAssistant(assistantId, (current) => ({ ...current, status: "error", error: message }));
|
|
510
560
|
}
|
|
511
561
|
} finally {
|
|
562
|
+
flushDeltas();
|
|
512
563
|
patchAssistant(
|
|
513
564
|
assistantId,
|
|
514
565
|
(message) => message.status === "streaming" ? { ...message, status: "done" } : message
|
|
@@ -611,8 +662,45 @@ function useFrankieTheme() {
|
|
|
611
662
|
}
|
|
612
663
|
|
|
613
664
|
// src/components/chat/FrankieWidget.tsx
|
|
614
|
-
var
|
|
615
|
-
var
|
|
665
|
+
var import_lucide_react7 = require("lucide-react");
|
|
666
|
+
var import_react10 = require("react");
|
|
667
|
+
|
|
668
|
+
// src/lib/styles-ready.ts
|
|
669
|
+
var import_react3 = require("react");
|
|
670
|
+
var useIsomorphicLayoutEffect = typeof window === "undefined" ? import_react3.useEffect : import_react3.useLayoutEffect;
|
|
671
|
+
var SENTINEL = "--frankie-styles-ready";
|
|
672
|
+
function useStylesReady(ref, timeoutMs = 600) {
|
|
673
|
+
const [ready, setReady] = (0, import_react3.useState)(false);
|
|
674
|
+
useIsomorphicLayoutEffect(() => {
|
|
675
|
+
if (ready) return;
|
|
676
|
+
const node = ref.current;
|
|
677
|
+
if (!node || typeof window === "undefined" || typeof window.getComputedStyle !== "function") {
|
|
678
|
+
setReady(true);
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
const applied = () => window.getComputedStyle(node).getPropertyValue(SENTINEL).trim() === "1";
|
|
682
|
+
if (applied()) {
|
|
683
|
+
setReady(true);
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
const deadline = Date.now() + timeoutMs;
|
|
687
|
+
let frame = 0;
|
|
688
|
+
const poll = () => {
|
|
689
|
+
if (applied() || Date.now() >= deadline) {
|
|
690
|
+
setReady(true);
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
frame = window.requestAnimationFrame(poll);
|
|
694
|
+
};
|
|
695
|
+
if (typeof window.requestAnimationFrame !== "function") {
|
|
696
|
+
setReady(true);
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
frame = window.requestAnimationFrame(poll);
|
|
700
|
+
return () => window.cancelAnimationFrame(frame);
|
|
701
|
+
}, [ref, ready, timeoutMs]);
|
|
702
|
+
return ready;
|
|
703
|
+
}
|
|
616
704
|
|
|
617
705
|
// src/lib/theme.ts
|
|
618
706
|
function themeToCssVars(theme) {
|
|
@@ -672,8 +760,8 @@ function Button({ className, variant, size, asChild = false, ...props }) {
|
|
|
672
760
|
}
|
|
673
761
|
|
|
674
762
|
// src/components/chat/ChatPanel.tsx
|
|
675
|
-
var
|
|
676
|
-
var
|
|
763
|
+
var import_lucide_react6 = require("lucide-react");
|
|
764
|
+
var import_react9 = require("react");
|
|
677
765
|
|
|
678
766
|
// src/components/ui/avatar.tsx
|
|
679
767
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
@@ -723,9 +811,9 @@ function CardFooter({ className, ...props }) {
|
|
|
723
811
|
}
|
|
724
812
|
|
|
725
813
|
// src/components/ui/scroll-area.tsx
|
|
726
|
-
var
|
|
814
|
+
var import_react4 = require("react");
|
|
727
815
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
728
|
-
var ScrollArea = (0,
|
|
816
|
+
var ScrollArea = (0, import_react4.forwardRef)(
|
|
729
817
|
function ScrollArea2({ className, ...props }, ref) {
|
|
730
818
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { ref, className: cn("frankie-scroll overflow-y-auto overscroll-contain", className), ...props });
|
|
731
819
|
}
|
|
@@ -779,12 +867,12 @@ function AgentContext() {
|
|
|
779
867
|
|
|
780
868
|
// src/components/chat/Composer.tsx
|
|
781
869
|
var import_lucide_react2 = require("lucide-react");
|
|
782
|
-
var
|
|
870
|
+
var import_react6 = require("react");
|
|
783
871
|
|
|
784
872
|
// src/components/ui/textarea.tsx
|
|
785
|
-
var
|
|
873
|
+
var import_react5 = require("react");
|
|
786
874
|
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
787
|
-
var Textarea = (0,
|
|
875
|
+
var Textarea = (0, import_react5.forwardRef)(
|
|
788
876
|
function Textarea2({ className, ...props }, ref) {
|
|
789
877
|
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
790
878
|
"textarea",
|
|
@@ -803,21 +891,28 @@ var Textarea = (0, import_react4.forwardRef)(
|
|
|
803
891
|
// src/components/chat/Composer.tsx
|
|
804
892
|
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
805
893
|
var MAX_ROWS_PX = 140;
|
|
806
|
-
function Composer({ onSend, onStop, busy, placeholder, disabled, autoFocus, attachments, modelPicker }) {
|
|
807
|
-
const [value, setValue] = (0,
|
|
808
|
-
const [files, setFiles] = (0,
|
|
809
|
-
const [fileError, setFileError] = (0,
|
|
810
|
-
const textareaRef = (0,
|
|
811
|
-
const fileInputRef = (0,
|
|
894
|
+
function Composer({ onSend, onStop, busy, placeholder, disabled, autoFocus, attachments, modelPicker, focusHandleRef }) {
|
|
895
|
+
const [value, setValue] = (0, import_react6.useState)("");
|
|
896
|
+
const [files, setFiles] = (0, import_react6.useState)([]);
|
|
897
|
+
const [fileError, setFileError] = (0, import_react6.useState)(void 0);
|
|
898
|
+
const textareaRef = (0, import_react6.useRef)(null);
|
|
899
|
+
const fileInputRef = (0, import_react6.useRef)(null);
|
|
900
|
+
(0, import_react6.useEffect)(() => {
|
|
901
|
+
if (!focusHandleRef) return;
|
|
902
|
+
focusHandleRef.current = () => textareaRef.current?.focus();
|
|
903
|
+
return () => {
|
|
904
|
+
focusHandleRef.current = null;
|
|
905
|
+
};
|
|
906
|
+
}, [focusHandleRef]);
|
|
812
907
|
const canAttach = Boolean(attachments?.enabled);
|
|
813
908
|
const models = modelPicker?.models ?? [];
|
|
814
|
-
(0,
|
|
909
|
+
(0, import_react6.useEffect)(() => {
|
|
815
910
|
const node = textareaRef.current;
|
|
816
911
|
if (!node) return;
|
|
817
912
|
node.style.height = "auto";
|
|
818
913
|
node.style.height = `${Math.min(node.scrollHeight, MAX_ROWS_PX)}px`;
|
|
819
914
|
}, [value]);
|
|
820
|
-
const addFiles = (0,
|
|
915
|
+
const addFiles = (0, import_react6.useCallback)(
|
|
821
916
|
async (list) => {
|
|
822
917
|
if (!list || !attachments) return;
|
|
823
918
|
setFileError(void 0);
|
|
@@ -850,7 +945,7 @@ function Composer({ onSend, onStop, busy, placeholder, disabled, autoFocus, atta
|
|
|
850
945
|
[attachments, files.length]
|
|
851
946
|
);
|
|
852
947
|
const removeFile = (id) => setFiles((current) => current.filter((file) => file.id !== id));
|
|
853
|
-
const submit = (0,
|
|
948
|
+
const submit = (0, import_react6.useCallback)(() => {
|
|
854
949
|
const text = value.trim();
|
|
855
950
|
if (!text && files.length === 0 || busy || disabled) return;
|
|
856
951
|
setValue("");
|
|
@@ -980,15 +1075,77 @@ function readAsBase64(file) {
|
|
|
980
1075
|
}
|
|
981
1076
|
|
|
982
1077
|
// src/components/chat/MessageBubble.tsx
|
|
983
|
-
var
|
|
984
|
-
var
|
|
1078
|
+
var import_lucide_react5 = require("lucide-react");
|
|
1079
|
+
var import_react8 = require("react");
|
|
985
1080
|
|
|
986
|
-
// src/
|
|
987
|
-
var
|
|
1081
|
+
// src/lib/choices.ts
|
|
1082
|
+
var CLOSED = /```frankie:choices[ \t]*\r?\n([\s\S]*?)```/;
|
|
1083
|
+
var OPEN = /```frankie:choices[ \t]*\r?\n?[\s\S]*$/;
|
|
1084
|
+
function extractChoices(text) {
|
|
1085
|
+
const closed = CLOSED.exec(text);
|
|
1086
|
+
if (closed) {
|
|
1087
|
+
const options = parseOptionLines(closed[1] ?? "");
|
|
1088
|
+
const body = (text.slice(0, closed.index) + text.slice(closed.index + closed[0].length)).replace(/\n{3,}/g, "\n\n").trimEnd();
|
|
1089
|
+
return { body, options: options.length ? options : null };
|
|
1090
|
+
}
|
|
1091
|
+
const open = OPEN.exec(text);
|
|
1092
|
+
if (open) return { body: text.slice(0, open.index).trimEnd(), options: null };
|
|
1093
|
+
return { body: text, options: null };
|
|
1094
|
+
}
|
|
1095
|
+
function parseOptionLines(block) {
|
|
1096
|
+
return block.split("\n").map((line) => line.trim().replace(/^([-*+]|\d+[.)])\s+/, "").trim()).filter(Boolean);
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
// src/components/chat/Choices.tsx
|
|
1100
|
+
var import_lucide_react3 = require("lucide-react");
|
|
988
1101
|
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
989
|
-
function
|
|
990
|
-
|
|
1102
|
+
function Choices({
|
|
1103
|
+
options,
|
|
1104
|
+
onPick,
|
|
1105
|
+
onSomethingElse,
|
|
1106
|
+
disabled
|
|
1107
|
+
}) {
|
|
1108
|
+
if (options.length === 0) return null;
|
|
1109
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "frankie-choices", role: "group", "aria-label": "Choose an option", children: [
|
|
1110
|
+
options.map((option, index) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
1111
|
+
"button",
|
|
1112
|
+
{
|
|
1113
|
+
type: "button",
|
|
1114
|
+
className: "frankie-choice",
|
|
1115
|
+
disabled,
|
|
1116
|
+
onClick: () => onPick(option),
|
|
1117
|
+
children: [
|
|
1118
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "frankie-choice-index", "aria-hidden": true, children: index + 1 }),
|
|
1119
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "frankie-choice-label", children: option }),
|
|
1120
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react3.ArrowRight, { className: "frankie-choice-go size-4", "aria-hidden": true })
|
|
1121
|
+
]
|
|
1122
|
+
},
|
|
1123
|
+
`${index}-${option}`
|
|
1124
|
+
)),
|
|
1125
|
+
onSomethingElse ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
1126
|
+
"button",
|
|
1127
|
+
{
|
|
1128
|
+
type: "button",
|
|
1129
|
+
className: "frankie-choice frankie-choice-other",
|
|
1130
|
+
disabled,
|
|
1131
|
+
onClick: onSomethingElse,
|
|
1132
|
+
children: [
|
|
1133
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "frankie-choice-index", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react3.PencilLine, { className: "size-3.5" }) }),
|
|
1134
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "frankie-choice-label", children: "Something else\u2026" }),
|
|
1135
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "frankie-choice-hint", "aria-hidden": true, children: "Type your own" })
|
|
1136
|
+
]
|
|
1137
|
+
}
|
|
1138
|
+
) : null
|
|
1139
|
+
] });
|
|
991
1140
|
}
|
|
1141
|
+
|
|
1142
|
+
// src/components/chat/Markdown.tsx
|
|
1143
|
+
var import_react7 = require("react");
|
|
1144
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
1145
|
+
var Markdown = (0, import_react7.memo)(function Markdown2({ text, className }) {
|
|
1146
|
+
const blocks = (0, import_react7.useMemo)(() => renderBlocks(text), [text]);
|
|
1147
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: cn("frankie-prose", className), children: blocks });
|
|
1148
|
+
});
|
|
992
1149
|
function renderBlocks(text) {
|
|
993
1150
|
const lines = text.split("\n");
|
|
994
1151
|
const blocks = [];
|
|
@@ -997,13 +1154,13 @@ function renderBlocks(text) {
|
|
|
997
1154
|
let code = null;
|
|
998
1155
|
const flushParagraph = () => {
|
|
999
1156
|
if (paragraph.length === 0) return;
|
|
1000
|
-
blocks.push(/* @__PURE__ */ (0,
|
|
1157
|
+
blocks.push(/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { children: renderInline(paragraph.join(" ")) }, `p${blocks.length}`));
|
|
1001
1158
|
paragraph = [];
|
|
1002
1159
|
};
|
|
1003
1160
|
const flushList = () => {
|
|
1004
1161
|
if (!list) return;
|
|
1005
|
-
const items = list.items.map((item, index) => /* @__PURE__ */ (0,
|
|
1006
|
-
blocks.push(list.ordered ? /* @__PURE__ */ (0,
|
|
1162
|
+
const items = list.items.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("li", { children: renderInline(item) }, index));
|
|
1163
|
+
blocks.push(list.ordered ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("ol", { children: items }, `l${blocks.length}`) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("ul", { children: items }, `l${blocks.length}`));
|
|
1007
1164
|
list = null;
|
|
1008
1165
|
};
|
|
1009
1166
|
for (const line of lines) {
|
|
@@ -1011,7 +1168,7 @@ function renderBlocks(text) {
|
|
|
1011
1168
|
if (fence) {
|
|
1012
1169
|
if (code) {
|
|
1013
1170
|
blocks.push(
|
|
1014
|
-
/* @__PURE__ */ (0,
|
|
1171
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("pre", { children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("code", { children: code.lines.join("\n") }) }, `c${blocks.length}`)
|
|
1015
1172
|
);
|
|
1016
1173
|
code = null;
|
|
1017
1174
|
} else {
|
|
@@ -1042,11 +1199,13 @@ function renderBlocks(text) {
|
|
|
1042
1199
|
flushList();
|
|
1043
1200
|
continue;
|
|
1044
1201
|
}
|
|
1045
|
-
const heading =
|
|
1202
|
+
const heading = /^(#{1,6})\s+(.*)$/.exec(line);
|
|
1046
1203
|
if (heading) {
|
|
1047
1204
|
flushParagraph();
|
|
1048
1205
|
flushList();
|
|
1049
|
-
|
|
1206
|
+
const level = (heading[1] ?? "#").length;
|
|
1207
|
+
const Tag = level <= 1 ? "h3" : level === 2 ? "h4" : "h5";
|
|
1208
|
+
blocks.push(/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Tag, { className: "frankie-h", children: renderInline(heading[2] ?? "") }, `h${blocks.length}`));
|
|
1050
1209
|
continue;
|
|
1051
1210
|
}
|
|
1052
1211
|
flushList();
|
|
@@ -1054,7 +1213,7 @@ function renderBlocks(text) {
|
|
|
1054
1213
|
}
|
|
1055
1214
|
if (code) {
|
|
1056
1215
|
blocks.push(
|
|
1057
|
-
/* @__PURE__ */ (0,
|
|
1216
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("pre", { children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("code", { children: code.lines.join("\n") }) }, `c${blocks.length}`)
|
|
1058
1217
|
);
|
|
1059
1218
|
}
|
|
1060
1219
|
flushParagraph();
|
|
@@ -1069,28 +1228,28 @@ function renderInline(text) {
|
|
|
1069
1228
|
const token = match[0];
|
|
1070
1229
|
const start = match.index;
|
|
1071
1230
|
if (start > lastIndex) nodes.push(text.slice(lastIndex, start));
|
|
1072
|
-
nodes.push(/* @__PURE__ */ (0,
|
|
1231
|
+
nodes.push(/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react7.Fragment, { children: renderToken(token) }, start));
|
|
1073
1232
|
lastIndex = start + token.length;
|
|
1074
1233
|
}
|
|
1075
1234
|
if (lastIndex < text.length) nodes.push(text.slice(lastIndex));
|
|
1076
1235
|
return nodes;
|
|
1077
1236
|
}
|
|
1078
1237
|
function renderToken(token) {
|
|
1079
|
-
if (token.startsWith("**")) return /* @__PURE__ */ (0,
|
|
1080
|
-
if (token.startsWith("`")) return /* @__PURE__ */ (0,
|
|
1238
|
+
if (token.startsWith("**")) return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("strong", { children: token.slice(2, -2) });
|
|
1239
|
+
if (token.startsWith("`")) return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("code", { children: token.slice(1, -1) });
|
|
1081
1240
|
const link = /^\[([^\]]+)\]\(([^)]+)\)$/.exec(token);
|
|
1082
1241
|
const href = link ? link[2] ?? "" : token;
|
|
1083
1242
|
const label = link ? link[1] ?? "" : token;
|
|
1084
1243
|
if (!/^(https?:\/\/|\/)/.test(href)) return token;
|
|
1085
|
-
return /* @__PURE__ */ (0,
|
|
1244
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("a", { href, target: "_blank", rel: "noopener noreferrer nofollow", children: label });
|
|
1086
1245
|
}
|
|
1087
1246
|
|
|
1088
1247
|
// src/components/chat/ToolActivityList.tsx
|
|
1089
|
-
var
|
|
1248
|
+
var import_lucide_react4 = require("lucide-react");
|
|
1090
1249
|
|
|
1091
1250
|
// src/components/ui/badge.tsx
|
|
1092
1251
|
var import_class_variance_authority2 = require("class-variance-authority");
|
|
1093
|
-
var
|
|
1252
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
1094
1253
|
var badgeVariants = (0, import_class_variance_authority2.cva)(
|
|
1095
1254
|
"inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-[0.6875rem] font-medium",
|
|
1096
1255
|
{
|
|
@@ -1110,44 +1269,44 @@ function Badge({
|
|
|
1110
1269
|
variant,
|
|
1111
1270
|
...props
|
|
1112
1271
|
}) {
|
|
1113
|
-
return /* @__PURE__ */ (0,
|
|
1272
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: cn(badgeVariants({ variant }), className), ...props });
|
|
1114
1273
|
}
|
|
1115
1274
|
|
|
1116
1275
|
// src/components/chat/ToolActivityList.tsx
|
|
1117
|
-
var
|
|
1276
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
1118
1277
|
function ToolActivityList({ tools, renderTool }) {
|
|
1119
1278
|
if (tools.length === 0) return null;
|
|
1120
1279
|
const running = tools.some((tool) => tool.status === "running");
|
|
1121
|
-
return /* @__PURE__ */ (0,
|
|
1122
|
-
/* @__PURE__ */ (0,
|
|
1123
|
-
/* @__PURE__ */ (0,
|
|
1124
|
-
/* @__PURE__ */ (0,
|
|
1125
|
-
/* @__PURE__ */ (0,
|
|
1280
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("details", { className: "frankie-tool-activity mt-2", open: running, children: [
|
|
1281
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("summary", { className: "inline-flex min-h-7 cursor-pointer list-none items-center gap-1.5 rounded-md px-1 text-xs font-medium text-muted-foreground hover:bg-muted hover:text-foreground [&::-webkit-details-marker]:hidden", children: [
|
|
1282
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react4.ChevronDown, { className: "frankie-tool-chevron size-3.5 transition-transform", "aria-hidden": "true" }),
|
|
1283
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { children: running ? "Working" : "Activity" }),
|
|
1284
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "text-[11px]", children: [
|
|
1126
1285
|
"\xB7 ",
|
|
1127
1286
|
tools.length,
|
|
1128
1287
|
" tool call",
|
|
1129
1288
|
tools.length === 1 ? "" : "s"
|
|
1130
1289
|
] })
|
|
1131
1290
|
] }),
|
|
1132
|
-
/* @__PURE__ */ (0,
|
|
1291
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("ul", { className: "mt-1 flex flex-col gap-1 border-l border-border pl-2", children: tools.map((tool) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("li", { children: renderTool ? renderTool(tool) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ToolActivityRow, { tool }) }, tool.id)) })
|
|
1133
1292
|
] });
|
|
1134
1293
|
}
|
|
1135
1294
|
function ToolActivityRow({ tool }) {
|
|
1136
|
-
return /* @__PURE__ */ (0,
|
|
1137
|
-
/* @__PURE__ */ (0,
|
|
1138
|
-
/* @__PURE__ */ (0,
|
|
1295
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Badge, { variant: badgeVariant(tool.status), title: tool.detail ?? tool.name, children: [
|
|
1296
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ToolIcon, { status: tool.status }),
|
|
1297
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "truncate", children: tool.label })
|
|
1139
1298
|
] });
|
|
1140
1299
|
}
|
|
1141
1300
|
function ToolIcon({ status }) {
|
|
1142
1301
|
switch (status) {
|
|
1143
1302
|
case "running":
|
|
1144
|
-
return /* @__PURE__ */ (0,
|
|
1303
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react4.Loader2, { className: "size-3 animate-spin", "aria-hidden": "true" });
|
|
1145
1304
|
case "done":
|
|
1146
|
-
return /* @__PURE__ */ (0,
|
|
1305
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react4.Check, { className: "size-3", "aria-hidden": "true" });
|
|
1147
1306
|
case "denied":
|
|
1148
|
-
return /* @__PURE__ */ (0,
|
|
1307
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react4.ShieldAlert, { className: "size-3", "aria-hidden": "true" });
|
|
1149
1308
|
default:
|
|
1150
|
-
return /* @__PURE__ */ (0,
|
|
1309
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react4.X, { className: "size-3", "aria-hidden": "true" });
|
|
1151
1310
|
}
|
|
1152
1311
|
}
|
|
1153
1312
|
function badgeVariant(status) {
|
|
@@ -1158,47 +1317,54 @@ function badgeVariant(status) {
|
|
|
1158
1317
|
}
|
|
1159
1318
|
|
|
1160
1319
|
// src/components/chat/MessageBubble.tsx
|
|
1161
|
-
var
|
|
1162
|
-
|
|
1320
|
+
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
1321
|
+
var MessageBubble = (0, import_react8.memo)(function MessageBubble2({
|
|
1163
1322
|
message,
|
|
1164
1323
|
assistantName,
|
|
1165
1324
|
avatarUrl,
|
|
1166
1325
|
showToolActivity,
|
|
1167
|
-
renderTool
|
|
1326
|
+
renderTool,
|
|
1327
|
+
onChoose,
|
|
1328
|
+
onRequestInput,
|
|
1329
|
+
active,
|
|
1330
|
+
busy
|
|
1168
1331
|
}) {
|
|
1169
1332
|
if (message.role === "user") {
|
|
1170
|
-
return /* @__PURE__ */ (0,
|
|
1171
|
-
message.attachments?.length ? /* @__PURE__ */ (0,
|
|
1333
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "frankie-user-message flex flex-col items-end gap-1", children: [
|
|
1334
|
+
message.attachments?.length ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "flex max-w-[85%] flex-wrap justify-end gap-1", children: message.attachments.map((file, index) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
1172
1335
|
"span",
|
|
1173
1336
|
{
|
|
1174
1337
|
className: "inline-flex max-w-[12rem] items-center gap-1 rounded-[var(--frankie-radius)] border border-border bg-muted px-2 py-1 text-xs",
|
|
1175
1338
|
children: [
|
|
1176
|
-
/* @__PURE__ */ (0,
|
|
1177
|
-
/* @__PURE__ */ (0,
|
|
1339
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react5.Paperclip, { className: "size-3 shrink-0 opacity-70" }),
|
|
1340
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "truncate", children: file.name })
|
|
1178
1341
|
]
|
|
1179
1342
|
},
|
|
1180
1343
|
`${file.name}-${index}`
|
|
1181
1344
|
)) }) : null,
|
|
1182
|
-
message.text ? /* @__PURE__ */ (0,
|
|
1183
|
-
/* @__PURE__ */ (0,
|
|
1184
|
-
/* @__PURE__ */ (0,
|
|
1345
|
+
message.text ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex max-w-[85%] items-end gap-1", children: [
|
|
1346
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "frankie-user-bubble rounded-[var(--frankie-radius)] bg-primary px-3 py-2 text-sm text-primary-foreground", children: message.text }),
|
|
1347
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(MessageCopyButton, { text: message.text })
|
|
1185
1348
|
] }) : null
|
|
1186
1349
|
] });
|
|
1187
1350
|
}
|
|
1188
1351
|
const empty = message.text.length === 0 && message.tools.length === 0;
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1352
|
+
const { body, options } = extractChoices(message.text);
|
|
1353
|
+
const showChoices = Boolean(active && onChoose && options && message.status === "done");
|
|
1354
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "frankie-assistant-message flex gap-2", children: [
|
|
1355
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "frankie-assistant-actions mt-0.5 flex shrink-0 flex-col items-center gap-1", children: [
|
|
1356
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Avatar, { ...avatarUrl ? { src: avatarUrl } : {}, name: assistantName }),
|
|
1357
|
+
body ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(MessageCopyButton, { text: body }) : null
|
|
1193
1358
|
] }),
|
|
1194
|
-
/* @__PURE__ */ (0,
|
|
1195
|
-
message.status === "error" ? /* @__PURE__ */ (0,
|
|
1196
|
-
|
|
1359
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "frankie-assistant-content min-w-0 max-w-[85%]", children: [
|
|
1360
|
+
message.status === "error" ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { className: "rounded-[var(--frankie-radius)] bg-red-500/10 px-3 py-2 text-sm text-red-600 dark:text-red-400", children: message.error }) : empty && message.status === "streaming" ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(TypingDots, {}) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Markdown, { text: body, className: "text-sm" }),
|
|
1361
|
+
showChoices ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Choices, { options: options ?? [], onPick: onChoose, ...onRequestInput ? { onSomethingElse: onRequestInput } : {}, disabled: busy }) : null,
|
|
1362
|
+
showToolActivity ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ToolActivityList, { tools: message.tools, ...renderTool ? { renderTool } : {} }) : null
|
|
1197
1363
|
] })
|
|
1198
1364
|
] });
|
|
1199
|
-
}
|
|
1365
|
+
});
|
|
1200
1366
|
function MessageCopyButton({ text }) {
|
|
1201
|
-
const [copied, setCopied] = (0,
|
|
1367
|
+
const [copied, setCopied] = (0, import_react8.useState)(false);
|
|
1202
1368
|
const copy = async () => {
|
|
1203
1369
|
try {
|
|
1204
1370
|
await navigator.clipboard.writeText(text);
|
|
@@ -1207,12 +1373,12 @@ function MessageCopyButton({ text }) {
|
|
|
1207
1373
|
} catch {
|
|
1208
1374
|
}
|
|
1209
1375
|
};
|
|
1210
|
-
return /* @__PURE__ */ (0,
|
|
1376
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("button", { type: "button", onClick: () => void copy(), className: "frankie-message-copy inline-flex size-8 shrink-0 items-center justify-center rounded-md border border-transparent text-muted-foreground transition hover:border-border hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary", "aria-label": copied ? "Copied" : "Copy message", title: copied ? "Copied" : "Copy message", children: copied ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react5.Check, { className: "size-3.5", "aria-hidden": "true" }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react5.Copy, { className: "size-3.5", "aria-hidden": "true" }) });
|
|
1211
1377
|
}
|
|
1212
1378
|
function TypingDots() {
|
|
1213
|
-
return /* @__PURE__ */ (0,
|
|
1214
|
-
/* @__PURE__ */ (0,
|
|
1215
|
-
[0, 1, 2].map((index) => /* @__PURE__ */ (0,
|
|
1379
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { className: "frankie-thinking inline-flex items-center gap-1 py-2", role: "status", "aria-label": "Thinking", children: [
|
|
1380
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "frankie-thinking-label", children: "Thinking" }),
|
|
1381
|
+
[0, 1, 2].map((index) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1216
1382
|
"span",
|
|
1217
1383
|
{
|
|
1218
1384
|
className: cn("size-1.5 animate-pulse rounded-full bg-muted-foreground"),
|
|
@@ -1224,14 +1390,14 @@ function TypingDots() {
|
|
|
1224
1390
|
}
|
|
1225
1391
|
|
|
1226
1392
|
// src/components/chat/SuggestedPrompts.tsx
|
|
1227
|
-
var
|
|
1393
|
+
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
1228
1394
|
function SuggestedPrompts({
|
|
1229
1395
|
prompts,
|
|
1230
1396
|
onPick,
|
|
1231
1397
|
disabled
|
|
1232
1398
|
}) {
|
|
1233
1399
|
if (prompts.length === 0) return null;
|
|
1234
|
-
return /* @__PURE__ */ (0,
|
|
1400
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "flex flex-wrap gap-2", children: prompts.map((prompt) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
1235
1401
|
Button,
|
|
1236
1402
|
{
|
|
1237
1403
|
type: "button",
|
|
@@ -1246,7 +1412,7 @@ function SuggestedPrompts({
|
|
|
1246
1412
|
}
|
|
1247
1413
|
|
|
1248
1414
|
// src/components/chat/ChatPanel.tsx
|
|
1249
|
-
var
|
|
1415
|
+
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
1250
1416
|
function ChatPanel({
|
|
1251
1417
|
className,
|
|
1252
1418
|
header,
|
|
@@ -1262,12 +1428,14 @@ function ChatPanel({
|
|
|
1262
1428
|
const { config, configError, loading } = useFrankie();
|
|
1263
1429
|
const theme = useFrankieTheme();
|
|
1264
1430
|
const { messages, busy, restoring, error, send, stop, reset } = useFrankieChat();
|
|
1265
|
-
const scrollRef = (0,
|
|
1266
|
-
const atBottomRef = (0,
|
|
1431
|
+
const scrollRef = (0, import_react9.useRef)(null);
|
|
1432
|
+
const atBottomRef = (0, import_react9.useRef)(true);
|
|
1267
1433
|
const features = config?.features;
|
|
1268
1434
|
const pickerModels = features?.modelPicker.enabled ? features.modelPicker.models : [];
|
|
1269
|
-
const [model, setModel] = (0,
|
|
1270
|
-
(0,
|
|
1435
|
+
const [model, setModel] = (0, import_react9.useState)(void 0);
|
|
1436
|
+
const composerFocus = (0, import_react9.useRef)(null);
|
|
1437
|
+
const focusComposer = () => composerFocus.current?.();
|
|
1438
|
+
(0, import_react9.useEffect)(() => {
|
|
1271
1439
|
const node = scrollRef.current;
|
|
1272
1440
|
if (!node) return;
|
|
1273
1441
|
const onScroll = () => {
|
|
@@ -1276,14 +1444,14 @@ function ChatPanel({
|
|
|
1276
1444
|
node.addEventListener("scroll", onScroll, { passive: true });
|
|
1277
1445
|
return () => node.removeEventListener("scroll", onScroll);
|
|
1278
1446
|
}, []);
|
|
1279
|
-
(0,
|
|
1447
|
+
(0, import_react9.useEffect)(() => {
|
|
1280
1448
|
const node = scrollRef.current;
|
|
1281
1449
|
if (!node || !atBottomRef.current) return;
|
|
1282
1450
|
node.scrollTop = node.scrollHeight;
|
|
1283
1451
|
}, [messages]);
|
|
1284
1452
|
const name = config?.branding.name ?? "Assistant";
|
|
1285
1453
|
const mode = resolveMode(theme?.mode);
|
|
1286
|
-
return /* @__PURE__ */ (0,
|
|
1454
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
|
|
1287
1455
|
Card,
|
|
1288
1456
|
{
|
|
1289
1457
|
className: cn(
|
|
@@ -1296,44 +1464,49 @@ function ChatPanel({
|
|
|
1296
1464
|
style: themeToCssVars(theme),
|
|
1297
1465
|
"data-frankie-mode": mode,
|
|
1298
1466
|
children: [
|
|
1299
|
-
header === false ? null : header !== void 0 ? header : /* @__PURE__ */ (0,
|
|
1300
|
-
/* @__PURE__ */ (0,
|
|
1301
|
-
/* @__PURE__ */ (0,
|
|
1302
|
-
/* @__PURE__ */ (0,
|
|
1303
|
-
config?.branding.tagline ? /* @__PURE__ */ (0,
|
|
1467
|
+
header === false ? null : header !== void 0 ? header : /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(CardHeader, { children: [
|
|
1468
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Avatar, { ...config?.branding.avatarUrl ? { src: config.branding.avatarUrl } : {}, name }),
|
|
1469
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "min-w-0 flex-1", children: [
|
|
1470
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(CardTitle, { children: name }),
|
|
1471
|
+
config?.branding.tagline ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(CardDescription, { children: config.branding.tagline }) : null
|
|
1304
1472
|
] }),
|
|
1305
|
-
/* @__PURE__ */ (0,
|
|
1306
|
-
onClose ? /* @__PURE__ */ (0,
|
|
1473
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Button, { size: "icon", variant: "ghost", onClick: reset, "aria-label": "Start a new conversation", title: "New conversation", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react6.RotateCcw, {}) }),
|
|
1474
|
+
onClose ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Button, { size: "icon", variant: "ghost", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react6.X, {}) }) : null
|
|
1307
1475
|
] }),
|
|
1308
|
-
/* @__PURE__ */ (0,
|
|
1309
|
-
/* @__PURE__ */ (0,
|
|
1310
|
-
/* @__PURE__ */ (0,
|
|
1311
|
-
/* @__PURE__ */ (0,
|
|
1312
|
-
/* @__PURE__ */ (0,
|
|
1313
|
-
] }) : loading ? /* @__PURE__ */ (0,
|
|
1314
|
-
/* @__PURE__ */ (0,
|
|
1315
|
-
/* @__PURE__ */ (0,
|
|
1316
|
-
] }) : /* @__PURE__ */ (0,
|
|
1317
|
-
(message) => renderMessage ? /* @__PURE__ */ (0,
|
|
1476
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(AgentContext, {}),
|
|
1477
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(CardContent, { className: "frankie-conversation-content", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ScrollArea, { ref: scrollRef, className: "frankie-transcript flex h-full flex-col gap-4 p-4", "aria-live": "polite", children: configError ? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "frankie-config-error", role: "alert", children: [
|
|
1478
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("strong", { children: "Frankie couldn\u2019t start" }),
|
|
1479
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { children: configError }),
|
|
1480
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { children: "Refresh this page after checking the workspace\u2019s allowed origin and sign-in settings." })
|
|
1481
|
+
] }) : loading ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: "text-sm text-muted-foreground", children: "Starting Frankie\u2026" }) : messages.length === 0 ? emptyState ?? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex flex-col gap-4", children: [
|
|
1482
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: "text-sm text-muted-foreground", children: config?.greeting }),
|
|
1483
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(SuggestedPrompts, { prompts: config?.suggestedPrompts ?? [], onPick: send, disabled: busy })
|
|
1484
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "frankie-message-list flex flex-col gap-4", children: messages.map(
|
|
1485
|
+
(message, index) => renderMessage ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { children: renderMessage(message) }, message.id) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
1318
1486
|
MessageBubble,
|
|
1319
1487
|
{
|
|
1320
1488
|
message,
|
|
1321
1489
|
assistantName: name,
|
|
1322
1490
|
...config?.branding.avatarUrl ? { avatarUrl: config.branding.avatarUrl } : {},
|
|
1323
1491
|
showToolActivity: config?.showToolActivity ?? true,
|
|
1324
|
-
...renderTool ? { renderTool } : {}
|
|
1492
|
+
...renderTool ? { renderTool } : {},
|
|
1493
|
+
onChoose: send,
|
|
1494
|
+
onRequestInput: focusComposer,
|
|
1495
|
+
active: index === messages.length - 1,
|
|
1496
|
+
busy
|
|
1325
1497
|
},
|
|
1326
1498
|
message.id
|
|
1327
1499
|
)
|
|
1328
1500
|
) }) }) }),
|
|
1329
|
-
/* @__PURE__ */ (0,
|
|
1330
|
-
error ? /* @__PURE__ */ (0,
|
|
1331
|
-
/* @__PURE__ */ (0,
|
|
1501
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(CardFooter, { className: "frankie-composer-footer flex flex-col gap-2", children: [
|
|
1502
|
+
error ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: "w-full text-xs text-red-600 dark:text-red-400", children: error }) : null,
|
|
1503
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "w-full", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
1332
1504
|
Composer,
|
|
1333
1505
|
{
|
|
1334
1506
|
onSend: send,
|
|
1335
1507
|
onStop: stop,
|
|
1336
1508
|
busy,
|
|
1509
|
+
focusHandleRef: composerFocus,
|
|
1337
1510
|
disabled: loading || restoring || Boolean(configError),
|
|
1338
1511
|
...placeholder ? { placeholder } : {},
|
|
1339
1512
|
...autoFocus ? { autoFocus } : {},
|
|
@@ -1349,7 +1522,7 @@ function ChatPanel({
|
|
|
1349
1522
|
}
|
|
1350
1523
|
|
|
1351
1524
|
// src/components/chat/FrankieWidget.tsx
|
|
1352
|
-
var
|
|
1525
|
+
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
1353
1526
|
function FrankieWidget({
|
|
1354
1527
|
position = "bottom-right",
|
|
1355
1528
|
offset = 24,
|
|
@@ -1362,13 +1535,15 @@ function FrankieWidget({
|
|
|
1362
1535
|
}) {
|
|
1363
1536
|
const { config } = useFrankie();
|
|
1364
1537
|
const theme = useFrankieTheme();
|
|
1365
|
-
const
|
|
1538
|
+
const rootRef = (0, import_react10.useRef)(null);
|
|
1539
|
+
const stylesReady = useStylesReady(rootRef);
|
|
1540
|
+
const [uncontrolledOpen, setUncontrolledOpen] = (0, import_react10.useState)(defaultOpen);
|
|
1366
1541
|
const open = controlledOpen ?? uncontrolledOpen;
|
|
1367
1542
|
const setOpen = (next) => {
|
|
1368
1543
|
if (controlledOpen === void 0) setUncontrolledOpen(next);
|
|
1369
1544
|
onOpenChange?.(next);
|
|
1370
1545
|
};
|
|
1371
|
-
(0,
|
|
1546
|
+
(0, import_react10.useEffect)(() => {
|
|
1372
1547
|
if (!open) return;
|
|
1373
1548
|
const onKeyDown = (event) => {
|
|
1374
1549
|
if (event.key === "Escape") setOpen(false);
|
|
@@ -1379,15 +1554,17 @@ function FrankieWidget({
|
|
|
1379
1554
|
const label = config?.branding.launcherLabel ?? "Need a hand?";
|
|
1380
1555
|
const mode = resolveMode(theme?.mode);
|
|
1381
1556
|
const side = position === "bottom-right" ? { right: offset } : { left: offset };
|
|
1382
|
-
return /* @__PURE__ */ (0,
|
|
1557
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1383
1558
|
"div",
|
|
1384
1559
|
{
|
|
1560
|
+
ref: rootRef,
|
|
1385
1561
|
className: cn("frankie-root frankie-widget", mode === "dark" && "dark"),
|
|
1386
|
-
style: { ...themeToCssVars(theme), position: "fixed", bottom: offset, ...side, zIndex, pointerEvents: "none" },
|
|
1562
|
+
style: { ...themeToCssVars(theme), position: "fixed", bottom: offset, ...side, zIndex, pointerEvents: "none", ...stylesReady ? null : { opacity: 0 } },
|
|
1387
1563
|
"data-frankie-mode": mode,
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
/* @__PURE__ */ (0,
|
|
1564
|
+
"data-frankie-styles": stylesReady ? "ready" : "pending",
|
|
1565
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: cn("flex flex-col gap-3", position === "bottom-left" ? "items-start" : "items-end"), children: [
|
|
1566
|
+
open ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { pointerEvents: "auto", maxHeight: "calc(100dvh - 100px)", overflow: "hidden" }, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ChatPanel, { ...panelProps, className: cn("frankie-widget-panel", panelProps.className), onClose: () => setOpen(false), autoFocus: true }) }) : null,
|
|
1567
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { pointerEvents: "auto" }, children: renderLauncher ? renderLauncher({ open, toggle: () => setOpen(!open), label }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1391
1568
|
Button,
|
|
1392
1569
|
{
|
|
1393
1570
|
size: "icon",
|
|
@@ -1396,7 +1573,7 @@ function FrankieWidget({
|
|
|
1396
1573
|
"aria-expanded": open,
|
|
1397
1574
|
"aria-label": open ? "Close the assistant" : label,
|
|
1398
1575
|
title: label,
|
|
1399
|
-
children: open ? /* @__PURE__ */ (0,
|
|
1576
|
+
children: open ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react7.X, { className: "size-5" }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react7.MessageCircle, { className: "size-5" })
|
|
1400
1577
|
}
|
|
1401
1578
|
) })
|
|
1402
1579
|
] })
|
|
@@ -1405,39 +1582,44 @@ function FrankieWidget({
|
|
|
1405
1582
|
}
|
|
1406
1583
|
|
|
1407
1584
|
// src/components/chat/FrankieInline.tsx
|
|
1408
|
-
var
|
|
1585
|
+
var import_react11 = require("react");
|
|
1586
|
+
var import_jsx_runtime17 = require("react/jsx-runtime");
|
|
1409
1587
|
function FrankieInline({ className, style, ...panelProps }) {
|
|
1410
1588
|
const theme = useFrankieTheme();
|
|
1411
1589
|
const mode = resolveMode(theme?.mode);
|
|
1412
|
-
|
|
1590
|
+
const rootRef = (0, import_react11.useRef)(null);
|
|
1591
|
+
const stylesReady = useStylesReady(rootRef);
|
|
1592
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
1413
1593
|
"div",
|
|
1414
1594
|
{
|
|
1595
|
+
ref: rootRef,
|
|
1415
1596
|
className: cn("frankie-root frankie-inline", mode === "dark" && "dark", className),
|
|
1416
|
-
style: { ...themeToCssVars(theme), display: "flex", height: "100%", width: "100%", ...style },
|
|
1597
|
+
style: { ...themeToCssVars(theme), display: "flex", height: "100%", width: "100%", ...style, ...stylesReady ? null : { opacity: 0 } },
|
|
1417
1598
|
"data-frankie-mode": mode,
|
|
1418
|
-
|
|
1599
|
+
"data-frankie-styles": stylesReady ? "ready" : "pending",
|
|
1600
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(ChatPanel, { ...panelProps, fill: true })
|
|
1419
1601
|
}
|
|
1420
1602
|
);
|
|
1421
1603
|
}
|
|
1422
1604
|
|
|
1423
1605
|
// src/components/chat/FrankieChatPage.tsx
|
|
1424
|
-
var
|
|
1425
|
-
var
|
|
1426
|
-
var
|
|
1606
|
+
var import_react12 = require("react");
|
|
1607
|
+
var import_lucide_react8 = require("lucide-react");
|
|
1608
|
+
var import_jsx_runtime18 = require("react/jsx-runtime");
|
|
1427
1609
|
function PageEmptyState() {
|
|
1428
1610
|
const { config } = useFrankie();
|
|
1429
1611
|
const { send, busy, restoring } = useFrankieChat();
|
|
1430
1612
|
const name = config?.branding.name ?? "Frankie";
|
|
1431
|
-
return /* @__PURE__ */ (0,
|
|
1432
|
-
/* @__PURE__ */ (0,
|
|
1433
|
-
/* @__PURE__ */ (0,
|
|
1434
|
-
/* @__PURE__ */ (0,
|
|
1435
|
-
/* @__PURE__ */ (0,
|
|
1436
|
-
/* @__PURE__ */ (0,
|
|
1613
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("section", { className: "frankie-page-empty", "aria-label": "Start a conversation", children: [
|
|
1614
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "frankie-page-avatar", "aria-hidden": true, children: name.trim().slice(0, 1).toUpperCase() }),
|
|
1615
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [
|
|
1616
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "frankie-page-kicker", children: "Your product assistant" }),
|
|
1617
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h1", { children: "How can I help?" }),
|
|
1618
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { children: config?.greeting ?? "Ask a question or choose a task to get started." })
|
|
1437
1619
|
] }),
|
|
1438
|
-
/* @__PURE__ */ (0,
|
|
1439
|
-
/* @__PURE__ */ (0,
|
|
1440
|
-
/* @__PURE__ */ (0,
|
|
1620
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "frankie-page-prompts", children: config?.suggestedPrompts?.slice(0, 3).map((prompt) => /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("button", { type: "button", disabled: busy || restoring, onClick: () => void send(prompt), children: [
|
|
1621
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("strong", { children: prompt }),
|
|
1622
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("b", { "aria-hidden": true, children: "\u2197" })
|
|
1441
1623
|
] }, prompt)) })
|
|
1442
1624
|
] });
|
|
1443
1625
|
}
|
|
@@ -1445,22 +1627,22 @@ function PageChrome({ showHistory, navigation, currentId, onSelect, onNew, ...pa
|
|
|
1445
1627
|
const { client, config } = useFrankie();
|
|
1446
1628
|
const { busy, messages } = useFrankieChat();
|
|
1447
1629
|
const theme = useFrankieTheme();
|
|
1448
|
-
const [conversations, setConversations] = (0,
|
|
1449
|
-
const [historyError, setHistoryError] = (0,
|
|
1450
|
-
const [historyLoading, setHistoryLoading] = (0,
|
|
1451
|
-
const [drawerOpen, setDrawerOpen] = (0,
|
|
1452
|
-
const [collapsed, setCollapsed] = (0,
|
|
1630
|
+
const [conversations, setConversations] = (0, import_react12.useState)([]);
|
|
1631
|
+
const [historyError, setHistoryError] = (0, import_react12.useState)("");
|
|
1632
|
+
const [historyLoading, setHistoryLoading] = (0, import_react12.useState)(false);
|
|
1633
|
+
const [drawerOpen, setDrawerOpen] = (0, import_react12.useState)(false);
|
|
1634
|
+
const [collapsed, setCollapsed] = (0, import_react12.useState)(() => {
|
|
1453
1635
|
try {
|
|
1454
1636
|
return window.localStorage.getItem("frankie.history-collapsed") === "1";
|
|
1455
1637
|
} catch {
|
|
1456
1638
|
return false;
|
|
1457
1639
|
}
|
|
1458
1640
|
});
|
|
1459
|
-
const [query, setQuery] = (0,
|
|
1460
|
-
const dialog = (0,
|
|
1461
|
-
const menu = (0,
|
|
1462
|
-
const requestVersion = (0,
|
|
1463
|
-
const refresh = (0,
|
|
1641
|
+
const [query, setQuery] = (0, import_react12.useState)("");
|
|
1642
|
+
const dialog = (0, import_react12.useRef)(null);
|
|
1643
|
+
const menu = (0, import_react12.useRef)(null);
|
|
1644
|
+
const requestVersion = (0, import_react12.useRef)(0);
|
|
1645
|
+
const refresh = (0, import_react12.useCallback)(async () => {
|
|
1464
1646
|
if (!showHistory || !config || config.allowAnonymous) {
|
|
1465
1647
|
setHistoryError("");
|
|
1466
1648
|
return;
|
|
@@ -1481,19 +1663,19 @@ function PageChrome({ showHistory, navigation, currentId, onSelect, onNew, ...pa
|
|
|
1481
1663
|
if (version === requestVersion.current) setHistoryLoading(false);
|
|
1482
1664
|
}
|
|
1483
1665
|
}, [client, showHistory, config?.allowAnonymous]);
|
|
1484
|
-
(0,
|
|
1666
|
+
(0, import_react12.useEffect)(() => {
|
|
1485
1667
|
if (!busy) void refresh();
|
|
1486
1668
|
return () => {
|
|
1487
1669
|
requestVersion.current++;
|
|
1488
1670
|
};
|
|
1489
1671
|
}, [refresh, busy]);
|
|
1490
|
-
(0,
|
|
1672
|
+
(0, import_react12.useEffect)(() => {
|
|
1491
1673
|
try {
|
|
1492
1674
|
window.localStorage.setItem("frankie.history-collapsed", collapsed ? "1" : "0");
|
|
1493
1675
|
} catch {
|
|
1494
1676
|
}
|
|
1495
1677
|
}, [collapsed]);
|
|
1496
|
-
(0,
|
|
1678
|
+
(0, import_react12.useEffect)(() => {
|
|
1497
1679
|
const node = dialog.current;
|
|
1498
1680
|
if (drawerOpen) {
|
|
1499
1681
|
node?.showModal?.();
|
|
@@ -1511,79 +1693,81 @@ function PageChrome({ showHistory, navigation, currentId, onSelect, onNew, ...pa
|
|
|
1511
1693
|
close();
|
|
1512
1694
|
}
|
|
1513
1695
|
};
|
|
1514
|
-
const history = /* @__PURE__ */ (0,
|
|
1515
|
-
/* @__PURE__ */ (0,
|
|
1516
|
-
/* @__PURE__ */ (0,
|
|
1517
|
-
/* @__PURE__ */ (0,
|
|
1696
|
+
const history = /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
|
|
1697
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "frankie-history-heading", children: [
|
|
1698
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("strong", { children: "Conversations" }),
|
|
1699
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("button", { type: "button", className: "frankie-mobile-only", "aria-label": "Close conversations", onClick: close, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react8.X, { size: 18 }) })
|
|
1518
1700
|
] }),
|
|
1519
|
-
/* @__PURE__ */ (0,
|
|
1701
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("button", { type: "button", className: "frankie-new-chat", disabled: busy, onClick: () => {
|
|
1520
1702
|
onNew();
|
|
1521
1703
|
close();
|
|
1522
1704
|
}, children: [
|
|
1523
|
-
/* @__PURE__ */ (0,
|
|
1705
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react8.Plus, { size: 17 }),
|
|
1524
1706
|
"New chat"
|
|
1525
1707
|
] }),
|
|
1526
|
-
/* @__PURE__ */ (0,
|
|
1527
|
-
/* @__PURE__ */ (0,
|
|
1528
|
-
/* @__PURE__ */ (0,
|
|
1708
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("label", { className: "frankie-history-search", children: [
|
|
1709
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { children: "Search conversations" }),
|
|
1710
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("input", { type: "search", placeholder: "Search chats\u2026", value: query, onChange: (event) => setQuery(event.target.value) })
|
|
1529
1711
|
] }),
|
|
1530
|
-
/* @__PURE__ */ (0,
|
|
1531
|
-
/* @__PURE__ */ (0,
|
|
1532
|
-
/* @__PURE__ */ (0,
|
|
1712
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("nav", { className: "frankie-history-list", "aria-label": "Conversations", children: conversations.filter((item) => item.title.toLowerCase().includes(query.toLowerCase())).map((item) => /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("button", { type: "button", "aria-current": item.id === currentId ? "page" : void 0, disabled: busy, onClick: () => select(item.id), children: [
|
|
1713
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { children: item.title }),
|
|
1714
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("small", { children: new Date(item.updatedAt).toLocaleDateString() })
|
|
1533
1715
|
] }, item.id)) }),
|
|
1534
|
-
historyLoading ? /* @__PURE__ */ (0,
|
|
1716
|
+
historyLoading ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { role: "status", children: "Loading history\u2026" }) : historyError ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("p", { role: "status", children: [
|
|
1535
1717
|
historyError,
|
|
1536
|
-
/* @__PURE__ */ (0,
|
|
1537
|
-
] }) : !conversations.length ? /* @__PURE__ */ (0,
|
|
1538
|
-
navigation ? /* @__PURE__ */ (0,
|
|
1718
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("button", { type: "button", onClick: () => void refresh(), children: "Try again" })
|
|
1719
|
+
] }) : !conversations.length ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { children: config?.allowAnonymous ? "Sign in to sync conversations across devices." : "Your conversations will appear here." }) : null,
|
|
1720
|
+
navigation ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "frankie-host-navigation", children: navigation }) : null
|
|
1539
1721
|
] });
|
|
1540
|
-
return /* @__PURE__ */ (0,
|
|
1541
|
-
showHistory ? /* @__PURE__ */ (0,
|
|
1542
|
-
/* @__PURE__ */ (0,
|
|
1722
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "frankie-page-shell" + (resolveMode(theme?.mode) === "dark" ? " dark" : ""), style: themeToCssVars(theme), children: [
|
|
1723
|
+
showHistory ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("aside", { className: "frankie-history-desktop" + (collapsed ? " frankie-history-collapsed" : ""), children: [
|
|
1724
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("button", { type: "button", className: "frankie-history-toggle", "aria-label": collapsed ? "Expand conversations" : "Collapse conversations", "aria-expanded": !collapsed, onClick: () => setCollapsed((value) => !value), children: collapsed ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react8.ChevronsRight, { size: 16 }) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react8.ChevronsLeft, { size: 16 }) }),
|
|
1543
1725
|
collapsed ? null : history
|
|
1544
1726
|
] }) : null,
|
|
1545
|
-
/* @__PURE__ */ (0,
|
|
1546
|
-
/* @__PURE__ */ (0,
|
|
1547
|
-
/* @__PURE__ */ (0,
|
|
1548
|
-
showHistory ? /* @__PURE__ */ (0,
|
|
1549
|
-
/* @__PURE__ */ (0,
|
|
1727
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "frankie-page-main", children: [
|
|
1728
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("header", { className: "frankie-page-header", children: [
|
|
1729
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [
|
|
1730
|
+
showHistory ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("button", { type: "button", className: "frankie-mobile-only", ref: menu, "aria-label": "Open conversations", "aria-haspopup": "dialog", "aria-expanded": drawerOpen, onClick: () => setDrawerOpen(true), children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react8.Menu, { size: 20 }) }) : null,
|
|
1731
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("strong", { className: "frankie-thread-title", children: conversations.find((item) => item.id === currentId)?.title ?? messages.find((message) => message.role === "user")?.text ?? "New chat" })
|
|
1550
1732
|
] }),
|
|
1551
|
-
/* @__PURE__ */ (0,
|
|
1552
|
-
/* @__PURE__ */ (0,
|
|
1553
|
-
/* @__PURE__ */ (0,
|
|
1554
|
-
/* @__PURE__ */ (0,
|
|
1733
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [
|
|
1734
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("button", { type: "button", disabled: busy, onClick: onNew, "aria-label": "Start a new conversation", children: [
|
|
1735
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react8.Plus, { size: 16 }),
|
|
1736
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { children: "New chat" })
|
|
1555
1737
|
] }),
|
|
1556
|
-
/* @__PURE__ */ (0,
|
|
1557
|
-
/* @__PURE__ */ (0,
|
|
1558
|
-
/* @__PURE__ */ (0,
|
|
1559
|
-
/* @__PURE__ */ (0,
|
|
1560
|
-
/* @__PURE__ */ (0,
|
|
1561
|
-
/* @__PURE__ */ (0,
|
|
1562
|
-
/* @__PURE__ */ (0,
|
|
1738
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("details", { className: "frankie-chat-settings", children: [
|
|
1739
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("summary", { "aria-label": "Chat settings", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react8.Settings, { size: 19 }) }),
|
|
1740
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [
|
|
1741
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("strong", { children: "Chat settings" }),
|
|
1742
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { children: "History syncs automatically for signed-in users." }),
|
|
1743
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("button", { type: "button", onClick: () => void refresh(), disabled: historyLoading || !showHistory, children: [
|
|
1744
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react8.History, { size: 16 }),
|
|
1563
1745
|
"Refresh history"
|
|
1564
1746
|
] }),
|
|
1565
|
-
/* @__PURE__ */ (0,
|
|
1747
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("small", { children: "Skills are checked at the start of each message." })
|
|
1566
1748
|
] })
|
|
1567
1749
|
] })
|
|
1568
1750
|
] })
|
|
1569
1751
|
] }),
|
|
1570
|
-
/* @__PURE__ */ (0,
|
|
1752
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "frankie-page-canvas", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(FrankieInline, { ...panel, header: panel.header ?? false, emptyState: panel.emptyState ?? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(PageEmptyState, {}) }) })
|
|
1571
1753
|
] }),
|
|
1572
|
-
showHistory ? /* @__PURE__ */ (0,
|
|
1754
|
+
showHistory ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("dialog", { ref: dialog, className: "frankie-history-dialog frankie-root", "aria-label": "Chat conversations", style: themeToCssVars(theme), onCancel: close, onClick: (event) => {
|
|
1573
1755
|
if (event.target === event.currentTarget) close();
|
|
1574
|
-
}, children: /* @__PURE__ */ (0,
|
|
1756
|
+
}, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("aside", { children: history }) }) : null
|
|
1575
1757
|
] });
|
|
1576
1758
|
}
|
|
1577
1759
|
function FrankieChatPage({ className, style, header, footer, emptyState, renderMessage, renderTool, placeholder, autoFocus, showHistory = true, navigation, ...provider }) {
|
|
1578
|
-
const [id, setId] = (0,
|
|
1760
|
+
const [id, setId] = (0, import_react12.useState)(() => provider.conversationId ?? crypto.randomUUID());
|
|
1579
1761
|
const onNew = () => setId(crypto.randomUUID());
|
|
1580
|
-
|
|
1762
|
+
const pageRef = (0, import_react12.useRef)(null);
|
|
1763
|
+
const stylesReady = useStylesReady(pageRef);
|
|
1764
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { ref: pageRef, className: ["frankie-root", "frankie-chat-page", className].filter(Boolean).join(" "), style: { height: "100%", minHeight: "min(36rem, 100dvh)", width: "100%", ...style, ...stylesReady ? null : { opacity: 0 } }, "data-frankie-styles": stylesReady ? "ready" : "pending", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(FrankieProvider, { ...provider, conversationId: id, storageNamespace: (provider.storageNamespace ?? "frankie." + provider.publishableKey) + "." + id, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(PageChrome, { showHistory, navigation, currentId: id, onSelect: setId, onNew, header, footer, emptyState, renderMessage, renderTool, placeholder, autoFocus }) }) });
|
|
1581
1765
|
}
|
|
1582
1766
|
|
|
1583
1767
|
// src/components/ui/separator.tsx
|
|
1584
|
-
var
|
|
1768
|
+
var import_jsx_runtime19 = require("react/jsx-runtime");
|
|
1585
1769
|
function Separator({ className }) {
|
|
1586
|
-
return /* @__PURE__ */ (0,
|
|
1770
|
+
return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { role: "separator", className: cn("h-px w-full bg-border", className) });
|
|
1587
1771
|
}
|
|
1588
1772
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1589
1773
|
0 && (module.exports = {
|
|
@@ -1597,6 +1781,7 @@ function Separator({ className }) {
|
|
|
1597
1781
|
CardHeader,
|
|
1598
1782
|
CardTitle,
|
|
1599
1783
|
ChatPanel,
|
|
1784
|
+
Choices,
|
|
1600
1785
|
Composer,
|
|
1601
1786
|
FRANKIE_CLOUD_URL,
|
|
1602
1787
|
FrankieApiError,
|
|
@@ -1617,6 +1802,7 @@ function Separator({ className }) {
|
|
|
1617
1802
|
applyEvent,
|
|
1618
1803
|
buttonVariants,
|
|
1619
1804
|
cn,
|
|
1805
|
+
extractChoices,
|
|
1620
1806
|
newAssistantMessage,
|
|
1621
1807
|
newUserMessage,
|
|
1622
1808
|
readEventStream,
|