@frankieai/react 0.2.7 → 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 +394 -190
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +63 -9
- package/dist/index.d.ts +63 -9
- package/dist/index.js +377 -175
- package/dist/index.js.map +1 -1
- package/package.json +5 -2
- package/src/components/chat/AgentContext.tsx +2 -1
- 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 +65 -3
- 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/src/widget.test.tsx +17 -0
package/dist/index.js
CHANGED
|
@@ -314,6 +314,7 @@ function useFrankieChatState() {
|
|
|
314
314
|
const [error, setError] = useState(void 0);
|
|
315
315
|
const [restored, setRestored] = useState(false);
|
|
316
316
|
const [loadedSkills, setLoadedSkills] = useState(null);
|
|
317
|
+
const [activeApis, setActiveApis] = useState(null);
|
|
317
318
|
const abortRef = useRef(null);
|
|
318
319
|
const mounted = useRef(true);
|
|
319
320
|
const messagesRef = useRef([]);
|
|
@@ -385,6 +386,7 @@ function useFrankieChatState() {
|
|
|
385
386
|
const assistantId = nextId("a");
|
|
386
387
|
setError(void 0);
|
|
387
388
|
setLoadedSkills(null);
|
|
389
|
+
setActiveApis(null);
|
|
388
390
|
setBusy(true);
|
|
389
391
|
const next = [
|
|
390
392
|
...messagesRef.current,
|
|
@@ -399,6 +401,41 @@ function useFrankieChatState() {
|
|
|
399
401
|
setMessages(next);
|
|
400
402
|
const abort = new AbortController();
|
|
401
403
|
abortRef.current = abort;
|
|
404
|
+
let bufferedText = "";
|
|
405
|
+
let pendingThinking = "";
|
|
406
|
+
let frame = null;
|
|
407
|
+
const raf = typeof requestAnimationFrame === "function" ? requestAnimationFrame : (cb) => setTimeout(() => cb(performance.now()), 16);
|
|
408
|
+
const cancelRaf = typeof cancelAnimationFrame === "function" ? cancelAnimationFrame : clearTimeout;
|
|
409
|
+
const revealTick = () => {
|
|
410
|
+
frame = null;
|
|
411
|
+
let addText = "";
|
|
412
|
+
if (bufferedText) {
|
|
413
|
+
const step = Math.max(3, Math.ceil(bufferedText.length / 6));
|
|
414
|
+
addText = bufferedText.slice(0, step);
|
|
415
|
+
bufferedText = bufferedText.slice(step);
|
|
416
|
+
}
|
|
417
|
+
const addThinking = pendingThinking;
|
|
418
|
+
pendingThinking = "";
|
|
419
|
+
if (addText || addThinking) {
|
|
420
|
+
patchAssistant(assistantId, (message) => ({ ...message, text: message.text + addText, thinking: message.thinking + addThinking }));
|
|
421
|
+
}
|
|
422
|
+
if (bufferedText) scheduleReveal();
|
|
423
|
+
};
|
|
424
|
+
const scheduleReveal = () => {
|
|
425
|
+
if (frame === null) frame = raf(revealTick);
|
|
426
|
+
};
|
|
427
|
+
const flushDeltas = () => {
|
|
428
|
+
if (frame !== null) {
|
|
429
|
+
cancelRaf(frame);
|
|
430
|
+
frame = null;
|
|
431
|
+
}
|
|
432
|
+
if (!bufferedText && !pendingThinking) return;
|
|
433
|
+
const addText = bufferedText;
|
|
434
|
+
const addThinking = pendingThinking;
|
|
435
|
+
bufferedText = "";
|
|
436
|
+
pendingThinking = "";
|
|
437
|
+
patchAssistant(assistantId, (message) => ({ ...message, text: message.text + addText, thinking: message.thinking + addThinking }));
|
|
438
|
+
};
|
|
402
439
|
try {
|
|
403
440
|
let receivedText = false;
|
|
404
441
|
let receivedToolActivity = false;
|
|
@@ -415,12 +452,28 @@ function useFrankieChatState() {
|
|
|
415
452
|
setLoadedSkills(event.skills);
|
|
416
453
|
return;
|
|
417
454
|
}
|
|
455
|
+
if (event.type === "tools_active") {
|
|
456
|
+
setActiveApis(event.apis);
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
418
459
|
if (event.type === "ping" || event.type === "session") return;
|
|
419
|
-
if (event.type === "text_delta")
|
|
460
|
+
if (event.type === "text_delta") {
|
|
461
|
+
receivedText = true;
|
|
462
|
+
bufferedText += event.text;
|
|
463
|
+
scheduleReveal();
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
if (event.type === "thinking_delta") {
|
|
467
|
+
pendingThinking += event.text;
|
|
468
|
+
scheduleReveal();
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
420
471
|
if (event.type === "tool_start" || event.type === "tool_end") receivedToolActivity = true;
|
|
472
|
+
flushDeltas();
|
|
421
473
|
patchAssistant(assistantId, (message) => applyEvent(message, event));
|
|
422
474
|
}
|
|
423
475
|
);
|
|
476
|
+
flushDeltas();
|
|
424
477
|
if (!receivedText) {
|
|
425
478
|
patchAssistant(assistantId, (message) => ({
|
|
426
479
|
...message,
|
|
@@ -429,6 +482,7 @@ function useFrankieChatState() {
|
|
|
429
482
|
}));
|
|
430
483
|
}
|
|
431
484
|
} catch (caught) {
|
|
485
|
+
flushDeltas();
|
|
432
486
|
if (abort.signal.aborted) {
|
|
433
487
|
patchAssistant(assistantId, (message) => ({
|
|
434
488
|
...message,
|
|
@@ -441,6 +495,7 @@ function useFrankieChatState() {
|
|
|
441
495
|
patchAssistant(assistantId, (current) => ({ ...current, status: "error", error: message }));
|
|
442
496
|
}
|
|
443
497
|
} finally {
|
|
498
|
+
flushDeltas();
|
|
444
499
|
patchAssistant(
|
|
445
500
|
assistantId,
|
|
446
501
|
(message) => message.status === "streaming" ? { ...message, status: "done" } : message
|
|
@@ -465,12 +520,13 @@ function useFrankieChatState() {
|
|
|
465
520
|
setBusy(false);
|
|
466
521
|
setRestored(true);
|
|
467
522
|
setLoadedSkills(null);
|
|
523
|
+
setActiveApis(null);
|
|
468
524
|
try {
|
|
469
525
|
if (client.persist) window.localStorage.removeItem(`${client.namespace}.messages`);
|
|
470
526
|
} catch {
|
|
471
527
|
}
|
|
472
528
|
}, [client]);
|
|
473
|
-
return { messages, busy, loadedSkills, restoring: !restored, ...error ? { error } : {}, send, stop, reset };
|
|
529
|
+
return { messages, busy, loadedSkills, activeApis, restoring: !restored, ...error ? { error } : {}, send, stop, reset };
|
|
474
530
|
}
|
|
475
531
|
function conversationTitle(messages) {
|
|
476
532
|
const first = messages.find((message) => message.role === "user" && message.text.trim());
|
|
@@ -543,7 +599,44 @@ function useFrankieTheme() {
|
|
|
543
599
|
|
|
544
600
|
// src/components/chat/FrankieWidget.tsx
|
|
545
601
|
import { MessageCircle, X as X4 } from "lucide-react";
|
|
546
|
-
import { useEffect as
|
|
602
|
+
import { useEffect as useEffect6, useRef as useRef5, useState as useState7 } from "react";
|
|
603
|
+
|
|
604
|
+
// src/lib/styles-ready.ts
|
|
605
|
+
import { useEffect as useEffect3, useLayoutEffect, useState as useState3 } from "react";
|
|
606
|
+
var useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect3 : useLayoutEffect;
|
|
607
|
+
var SENTINEL = "--frankie-styles-ready";
|
|
608
|
+
function useStylesReady(ref, timeoutMs = 600) {
|
|
609
|
+
const [ready, setReady] = useState3(false);
|
|
610
|
+
useIsomorphicLayoutEffect(() => {
|
|
611
|
+
if (ready) return;
|
|
612
|
+
const node = ref.current;
|
|
613
|
+
if (!node || typeof window === "undefined" || typeof window.getComputedStyle !== "function") {
|
|
614
|
+
setReady(true);
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
const applied = () => window.getComputedStyle(node).getPropertyValue(SENTINEL).trim() === "1";
|
|
618
|
+
if (applied()) {
|
|
619
|
+
setReady(true);
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
const deadline = Date.now() + timeoutMs;
|
|
623
|
+
let frame = 0;
|
|
624
|
+
const poll = () => {
|
|
625
|
+
if (applied() || Date.now() >= deadline) {
|
|
626
|
+
setReady(true);
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
frame = window.requestAnimationFrame(poll);
|
|
630
|
+
};
|
|
631
|
+
if (typeof window.requestAnimationFrame !== "function") {
|
|
632
|
+
setReady(true);
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
frame = window.requestAnimationFrame(poll);
|
|
636
|
+
return () => window.cancelAnimationFrame(frame);
|
|
637
|
+
}, [ref, ready, timeoutMs]);
|
|
638
|
+
return ready;
|
|
639
|
+
}
|
|
547
640
|
|
|
548
641
|
// src/lib/theme.ts
|
|
549
642
|
function themeToCssVars(theme) {
|
|
@@ -604,7 +697,7 @@ function Button({ className, variant, size, asChild = false, ...props }) {
|
|
|
604
697
|
|
|
605
698
|
// src/components/chat/ChatPanel.tsx
|
|
606
699
|
import { RotateCcw, X as X3 } from "lucide-react";
|
|
607
|
-
import { useEffect as
|
|
700
|
+
import { useEffect as useEffect5, useRef as useRef4, useState as useState6 } from "react";
|
|
608
701
|
|
|
609
702
|
// src/components/ui/avatar.tsx
|
|
610
703
|
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
@@ -667,7 +760,7 @@ import { ArrowUpRight, Sparkles } from "lucide-react";
|
|
|
667
760
|
import { jsx as jsx6, jsxs } from "react/jsx-runtime";
|
|
668
761
|
function AgentContext() {
|
|
669
762
|
const { loading, configError } = useFrankie();
|
|
670
|
-
const { loadedSkills, busy, restoring, error } = useFrankieChat();
|
|
763
|
+
const { loadedSkills, activeApis, busy, restoring, error } = useFrankieChat();
|
|
671
764
|
const status = configError || error ? "Unavailable" : loading || restoring ? "Connecting" : busy && loadedSkills === null ? "Checking" : loadedSkills?.length ? "Active" : loadedSkills ? "Tools only" : "Ready";
|
|
672
765
|
return /* @__PURE__ */ jsxs("div", { className: "frankie-agent-context-wrap", children: [
|
|
673
766
|
/* @__PURE__ */ jsxs("details", { className: "frankie-agent-context", children: [
|
|
@@ -690,7 +783,18 @@ function AgentContext() {
|
|
|
690
783
|
skill.revision
|
|
691
784
|
] })
|
|
692
785
|
] }, skill.id)) }) : /* @__PURE__ */ jsx6("p", { children: "Frankie can use the tools approved for this workspace. Drafts and skills without available tools are not loaded." }),
|
|
693
|
-
/* @__PURE__ */ jsx6("p", { children: "Published skills and updates are checked on every message. Only approved, available tools can run." })
|
|
786
|
+
/* @__PURE__ */ jsx6("p", { children: "Published skills and updates are checked on every message. Only approved, available tools can run." }),
|
|
787
|
+
activeApis?.length ? /* @__PURE__ */ jsx6("ul", { className: "frankie-active-apis", children: activeApis.map((api) => /* @__PURE__ */ jsxs("li", { children: [
|
|
788
|
+
/* @__PURE__ */ jsx6("span", { children: api.name }),
|
|
789
|
+
/* @__PURE__ */ jsxs("small", { children: [
|
|
790
|
+
"spec ",
|
|
791
|
+
api.specHash,
|
|
792
|
+
" \xB7 ",
|
|
793
|
+
api.operationCount,
|
|
794
|
+
" ",
|
|
795
|
+
api.operationCount === 1 ? "operation" : "operations"
|
|
796
|
+
] })
|
|
797
|
+
] }, api.name)) }) : null
|
|
694
798
|
] })
|
|
695
799
|
] }),
|
|
696
800
|
/* @__PURE__ */ jsx6("span", { className: "frankie-sr-only", role: "status", children: loadedSkills === null ? status : `${loadedSkills.length} ${loadedSkills.length === 1 ? "skill" : "skills"} loaded. ${status}.` })
|
|
@@ -699,7 +803,7 @@ function AgentContext() {
|
|
|
699
803
|
|
|
700
804
|
// src/components/chat/Composer.tsx
|
|
701
805
|
import { ArrowUp, Paperclip, Plus, Square, X } from "lucide-react";
|
|
702
|
-
import { useCallback as useCallback2, useEffect as
|
|
806
|
+
import { useCallback as useCallback2, useEffect as useEffect4, useRef as useRef3, useState as useState4 } from "react";
|
|
703
807
|
|
|
704
808
|
// src/components/ui/textarea.tsx
|
|
705
809
|
import { forwardRef as forwardRef2 } from "react";
|
|
@@ -723,15 +827,22 @@ var Textarea = forwardRef2(
|
|
|
723
827
|
// src/components/chat/Composer.tsx
|
|
724
828
|
import { Fragment, jsx as jsx8, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
725
829
|
var MAX_ROWS_PX = 140;
|
|
726
|
-
function Composer({ onSend, onStop, busy, placeholder, disabled, autoFocus, attachments, modelPicker }) {
|
|
727
|
-
const [value, setValue] =
|
|
728
|
-
const [files, setFiles] =
|
|
729
|
-
const [fileError, setFileError] =
|
|
830
|
+
function Composer({ onSend, onStop, busy, placeholder, disabled, autoFocus, attachments, modelPicker, focusHandleRef }) {
|
|
831
|
+
const [value, setValue] = useState4("");
|
|
832
|
+
const [files, setFiles] = useState4([]);
|
|
833
|
+
const [fileError, setFileError] = useState4(void 0);
|
|
730
834
|
const textareaRef = useRef3(null);
|
|
731
835
|
const fileInputRef = useRef3(null);
|
|
836
|
+
useEffect4(() => {
|
|
837
|
+
if (!focusHandleRef) return;
|
|
838
|
+
focusHandleRef.current = () => textareaRef.current?.focus();
|
|
839
|
+
return () => {
|
|
840
|
+
focusHandleRef.current = null;
|
|
841
|
+
};
|
|
842
|
+
}, [focusHandleRef]);
|
|
732
843
|
const canAttach = Boolean(attachments?.enabled);
|
|
733
844
|
const models = modelPicker?.models ?? [];
|
|
734
|
-
|
|
845
|
+
useEffect4(() => {
|
|
735
846
|
const node = textareaRef.current;
|
|
736
847
|
if (!node) return;
|
|
737
848
|
node.style.height = "auto";
|
|
@@ -901,14 +1012,76 @@ function readAsBase64(file) {
|
|
|
901
1012
|
|
|
902
1013
|
// src/components/chat/MessageBubble.tsx
|
|
903
1014
|
import { Check as Check2, Copy, Paperclip as Paperclip2 } from "lucide-react";
|
|
904
|
-
import { useState as
|
|
1015
|
+
import { memo as memo2, useState as useState5 } from "react";
|
|
905
1016
|
|
|
906
|
-
// src/
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
function
|
|
910
|
-
|
|
1017
|
+
// src/lib/choices.ts
|
|
1018
|
+
var CLOSED = /```frankie:choices[ \t]*\r?\n([\s\S]*?)```/;
|
|
1019
|
+
var OPEN = /```frankie:choices[ \t]*\r?\n?[\s\S]*$/;
|
|
1020
|
+
function extractChoices(text) {
|
|
1021
|
+
const closed = CLOSED.exec(text);
|
|
1022
|
+
if (closed) {
|
|
1023
|
+
const options = parseOptionLines(closed[1] ?? "");
|
|
1024
|
+
const body = (text.slice(0, closed.index) + text.slice(closed.index + closed[0].length)).replace(/\n{3,}/g, "\n\n").trimEnd();
|
|
1025
|
+
return { body, options: options.length ? options : null };
|
|
1026
|
+
}
|
|
1027
|
+
const open = OPEN.exec(text);
|
|
1028
|
+
if (open) return { body: text.slice(0, open.index).trimEnd(), options: null };
|
|
1029
|
+
return { body: text, options: null };
|
|
1030
|
+
}
|
|
1031
|
+
function parseOptionLines(block) {
|
|
1032
|
+
return block.split("\n").map((line) => line.trim().replace(/^([-*+]|\d+[.)])\s+/, "").trim()).filter(Boolean);
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
// src/components/chat/Choices.tsx
|
|
1036
|
+
import { ArrowRight, PencilLine } from "lucide-react";
|
|
1037
|
+
import { jsx as jsx9, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1038
|
+
function Choices({
|
|
1039
|
+
options,
|
|
1040
|
+
onPick,
|
|
1041
|
+
onSomethingElse,
|
|
1042
|
+
disabled
|
|
1043
|
+
}) {
|
|
1044
|
+
if (options.length === 0) return null;
|
|
1045
|
+
return /* @__PURE__ */ jsxs3("div", { className: "frankie-choices", role: "group", "aria-label": "Choose an option", children: [
|
|
1046
|
+
options.map((option, index) => /* @__PURE__ */ jsxs3(
|
|
1047
|
+
"button",
|
|
1048
|
+
{
|
|
1049
|
+
type: "button",
|
|
1050
|
+
className: "frankie-choice",
|
|
1051
|
+
disabled,
|
|
1052
|
+
onClick: () => onPick(option),
|
|
1053
|
+
children: [
|
|
1054
|
+
/* @__PURE__ */ jsx9("span", { className: "frankie-choice-index", "aria-hidden": true, children: index + 1 }),
|
|
1055
|
+
/* @__PURE__ */ jsx9("span", { className: "frankie-choice-label", children: option }),
|
|
1056
|
+
/* @__PURE__ */ jsx9(ArrowRight, { className: "frankie-choice-go size-4", "aria-hidden": true })
|
|
1057
|
+
]
|
|
1058
|
+
},
|
|
1059
|
+
`${index}-${option}`
|
|
1060
|
+
)),
|
|
1061
|
+
onSomethingElse ? /* @__PURE__ */ jsxs3(
|
|
1062
|
+
"button",
|
|
1063
|
+
{
|
|
1064
|
+
type: "button",
|
|
1065
|
+
className: "frankie-choice frankie-choice-other",
|
|
1066
|
+
disabled,
|
|
1067
|
+
onClick: onSomethingElse,
|
|
1068
|
+
children: [
|
|
1069
|
+
/* @__PURE__ */ jsx9("span", { className: "frankie-choice-index", "aria-hidden": true, children: /* @__PURE__ */ jsx9(PencilLine, { className: "size-3.5" }) }),
|
|
1070
|
+
/* @__PURE__ */ jsx9("span", { className: "frankie-choice-label", children: "Something else\u2026" }),
|
|
1071
|
+
/* @__PURE__ */ jsx9("span", { className: "frankie-choice-hint", "aria-hidden": true, children: "Type your own" })
|
|
1072
|
+
]
|
|
1073
|
+
}
|
|
1074
|
+
) : null
|
|
1075
|
+
] });
|
|
911
1076
|
}
|
|
1077
|
+
|
|
1078
|
+
// src/components/chat/Markdown.tsx
|
|
1079
|
+
import { Fragment as Fragment2, memo, useMemo as useMemo2 } from "react";
|
|
1080
|
+
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
1081
|
+
var Markdown = memo(function Markdown2({ text, className }) {
|
|
1082
|
+
const blocks = useMemo2(() => renderBlocks(text), [text]);
|
|
1083
|
+
return /* @__PURE__ */ jsx10("div", { className: cn("frankie-prose", className), children: blocks });
|
|
1084
|
+
});
|
|
912
1085
|
function renderBlocks(text) {
|
|
913
1086
|
const lines = text.split("\n");
|
|
914
1087
|
const blocks = [];
|
|
@@ -917,13 +1090,13 @@ function renderBlocks(text) {
|
|
|
917
1090
|
let code = null;
|
|
918
1091
|
const flushParagraph = () => {
|
|
919
1092
|
if (paragraph.length === 0) return;
|
|
920
|
-
blocks.push(/* @__PURE__ */
|
|
1093
|
+
blocks.push(/* @__PURE__ */ jsx10("p", { children: renderInline(paragraph.join(" ")) }, `p${blocks.length}`));
|
|
921
1094
|
paragraph = [];
|
|
922
1095
|
};
|
|
923
1096
|
const flushList = () => {
|
|
924
1097
|
if (!list) return;
|
|
925
|
-
const items = list.items.map((item, index) => /* @__PURE__ */
|
|
926
|
-
blocks.push(list.ordered ? /* @__PURE__ */
|
|
1098
|
+
const items = list.items.map((item, index) => /* @__PURE__ */ jsx10("li", { children: renderInline(item) }, index));
|
|
1099
|
+
blocks.push(list.ordered ? /* @__PURE__ */ jsx10("ol", { children: items }, `l${blocks.length}`) : /* @__PURE__ */ jsx10("ul", { children: items }, `l${blocks.length}`));
|
|
927
1100
|
list = null;
|
|
928
1101
|
};
|
|
929
1102
|
for (const line of lines) {
|
|
@@ -931,7 +1104,7 @@ function renderBlocks(text) {
|
|
|
931
1104
|
if (fence) {
|
|
932
1105
|
if (code) {
|
|
933
1106
|
blocks.push(
|
|
934
|
-
/* @__PURE__ */
|
|
1107
|
+
/* @__PURE__ */ jsx10("pre", { children: /* @__PURE__ */ jsx10("code", { children: code.lines.join("\n") }) }, `c${blocks.length}`)
|
|
935
1108
|
);
|
|
936
1109
|
code = null;
|
|
937
1110
|
} else {
|
|
@@ -962,11 +1135,13 @@ function renderBlocks(text) {
|
|
|
962
1135
|
flushList();
|
|
963
1136
|
continue;
|
|
964
1137
|
}
|
|
965
|
-
const heading =
|
|
1138
|
+
const heading = /^(#{1,6})\s+(.*)$/.exec(line);
|
|
966
1139
|
if (heading) {
|
|
967
1140
|
flushParagraph();
|
|
968
1141
|
flushList();
|
|
969
|
-
|
|
1142
|
+
const level = (heading[1] ?? "#").length;
|
|
1143
|
+
const Tag = level <= 1 ? "h3" : level === 2 ? "h4" : "h5";
|
|
1144
|
+
blocks.push(/* @__PURE__ */ jsx10(Tag, { className: "frankie-h", children: renderInline(heading[2] ?? "") }, `h${blocks.length}`));
|
|
970
1145
|
continue;
|
|
971
1146
|
}
|
|
972
1147
|
flushList();
|
|
@@ -974,7 +1149,7 @@ function renderBlocks(text) {
|
|
|
974
1149
|
}
|
|
975
1150
|
if (code) {
|
|
976
1151
|
blocks.push(
|
|
977
|
-
/* @__PURE__ */
|
|
1152
|
+
/* @__PURE__ */ jsx10("pre", { children: /* @__PURE__ */ jsx10("code", { children: code.lines.join("\n") }) }, `c${blocks.length}`)
|
|
978
1153
|
);
|
|
979
1154
|
}
|
|
980
1155
|
flushParagraph();
|
|
@@ -989,20 +1164,20 @@ function renderInline(text) {
|
|
|
989
1164
|
const token = match[0];
|
|
990
1165
|
const start = match.index;
|
|
991
1166
|
if (start > lastIndex) nodes.push(text.slice(lastIndex, start));
|
|
992
|
-
nodes.push(/* @__PURE__ */
|
|
1167
|
+
nodes.push(/* @__PURE__ */ jsx10(Fragment2, { children: renderToken(token) }, start));
|
|
993
1168
|
lastIndex = start + token.length;
|
|
994
1169
|
}
|
|
995
1170
|
if (lastIndex < text.length) nodes.push(text.slice(lastIndex));
|
|
996
1171
|
return nodes;
|
|
997
1172
|
}
|
|
998
1173
|
function renderToken(token) {
|
|
999
|
-
if (token.startsWith("**")) return /* @__PURE__ */
|
|
1000
|
-
if (token.startsWith("`")) return /* @__PURE__ */
|
|
1174
|
+
if (token.startsWith("**")) return /* @__PURE__ */ jsx10("strong", { children: token.slice(2, -2) });
|
|
1175
|
+
if (token.startsWith("`")) return /* @__PURE__ */ jsx10("code", { children: token.slice(1, -1) });
|
|
1001
1176
|
const link = /^\[([^\]]+)\]\(([^)]+)\)$/.exec(token);
|
|
1002
1177
|
const href = link ? link[2] ?? "" : token;
|
|
1003
1178
|
const label = link ? link[1] ?? "" : token;
|
|
1004
1179
|
if (!/^(https?:\/\/|\/)/.test(href)) return token;
|
|
1005
|
-
return /* @__PURE__ */
|
|
1180
|
+
return /* @__PURE__ */ jsx10("a", { href, target: "_blank", rel: "noopener noreferrer nofollow", children: label });
|
|
1006
1181
|
}
|
|
1007
1182
|
|
|
1008
1183
|
// src/components/chat/ToolActivityList.tsx
|
|
@@ -1010,7 +1185,7 @@ import { Check, ChevronDown, Loader2, ShieldAlert, X as X2 } from "lucide-react"
|
|
|
1010
1185
|
|
|
1011
1186
|
// src/components/ui/badge.tsx
|
|
1012
1187
|
import { cva as cva2 } from "class-variance-authority";
|
|
1013
|
-
import { jsx as
|
|
1188
|
+
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
1014
1189
|
var badgeVariants = cva2(
|
|
1015
1190
|
"inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-[0.6875rem] font-medium",
|
|
1016
1191
|
{
|
|
@@ -1030,44 +1205,44 @@ function Badge({
|
|
|
1030
1205
|
variant,
|
|
1031
1206
|
...props
|
|
1032
1207
|
}) {
|
|
1033
|
-
return /* @__PURE__ */
|
|
1208
|
+
return /* @__PURE__ */ jsx11("span", { className: cn(badgeVariants({ variant }), className), ...props });
|
|
1034
1209
|
}
|
|
1035
1210
|
|
|
1036
1211
|
// src/components/chat/ToolActivityList.tsx
|
|
1037
|
-
import { jsx as
|
|
1212
|
+
import { jsx as jsx12, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1038
1213
|
function ToolActivityList({ tools, renderTool }) {
|
|
1039
1214
|
if (tools.length === 0) return null;
|
|
1040
1215
|
const running = tools.some((tool) => tool.status === "running");
|
|
1041
|
-
return /* @__PURE__ */
|
|
1042
|
-
/* @__PURE__ */
|
|
1043
|
-
/* @__PURE__ */
|
|
1044
|
-
/* @__PURE__ */
|
|
1045
|
-
/* @__PURE__ */
|
|
1216
|
+
return /* @__PURE__ */ jsxs4("details", { className: "frankie-tool-activity mt-2", open: running, children: [
|
|
1217
|
+
/* @__PURE__ */ jsxs4("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: [
|
|
1218
|
+
/* @__PURE__ */ jsx12(ChevronDown, { className: "frankie-tool-chevron size-3.5 transition-transform", "aria-hidden": "true" }),
|
|
1219
|
+
/* @__PURE__ */ jsx12("span", { children: running ? "Working" : "Activity" }),
|
|
1220
|
+
/* @__PURE__ */ jsxs4("span", { className: "text-[11px]", children: [
|
|
1046
1221
|
"\xB7 ",
|
|
1047
1222
|
tools.length,
|
|
1048
1223
|
" tool call",
|
|
1049
1224
|
tools.length === 1 ? "" : "s"
|
|
1050
1225
|
] })
|
|
1051
1226
|
] }),
|
|
1052
|
-
/* @__PURE__ */
|
|
1227
|
+
/* @__PURE__ */ jsx12("ul", { className: "mt-1 flex flex-col gap-1 border-l border-border pl-2", children: tools.map((tool) => /* @__PURE__ */ jsx12("li", { children: renderTool ? renderTool(tool) : /* @__PURE__ */ jsx12(ToolActivityRow, { tool }) }, tool.id)) })
|
|
1053
1228
|
] });
|
|
1054
1229
|
}
|
|
1055
1230
|
function ToolActivityRow({ tool }) {
|
|
1056
|
-
return /* @__PURE__ */
|
|
1057
|
-
/* @__PURE__ */
|
|
1058
|
-
/* @__PURE__ */
|
|
1231
|
+
return /* @__PURE__ */ jsxs4(Badge, { variant: badgeVariant(tool.status), title: tool.detail ?? tool.name, children: [
|
|
1232
|
+
/* @__PURE__ */ jsx12(ToolIcon, { status: tool.status }),
|
|
1233
|
+
/* @__PURE__ */ jsx12("span", { className: "truncate", children: tool.label })
|
|
1059
1234
|
] });
|
|
1060
1235
|
}
|
|
1061
1236
|
function ToolIcon({ status }) {
|
|
1062
1237
|
switch (status) {
|
|
1063
1238
|
case "running":
|
|
1064
|
-
return /* @__PURE__ */
|
|
1239
|
+
return /* @__PURE__ */ jsx12(Loader2, { className: "size-3 animate-spin", "aria-hidden": "true" });
|
|
1065
1240
|
case "done":
|
|
1066
|
-
return /* @__PURE__ */
|
|
1241
|
+
return /* @__PURE__ */ jsx12(Check, { className: "size-3", "aria-hidden": "true" });
|
|
1067
1242
|
case "denied":
|
|
1068
|
-
return /* @__PURE__ */
|
|
1243
|
+
return /* @__PURE__ */ jsx12(ShieldAlert, { className: "size-3", "aria-hidden": "true" });
|
|
1069
1244
|
default:
|
|
1070
|
-
return /* @__PURE__ */
|
|
1245
|
+
return /* @__PURE__ */ jsx12(X2, { className: "size-3", "aria-hidden": "true" });
|
|
1071
1246
|
}
|
|
1072
1247
|
}
|
|
1073
1248
|
function badgeVariant(status) {
|
|
@@ -1078,47 +1253,54 @@ function badgeVariant(status) {
|
|
|
1078
1253
|
}
|
|
1079
1254
|
|
|
1080
1255
|
// src/components/chat/MessageBubble.tsx
|
|
1081
|
-
import { jsx as
|
|
1082
|
-
|
|
1256
|
+
import { jsx as jsx13, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1257
|
+
var MessageBubble = memo2(function MessageBubble2({
|
|
1083
1258
|
message,
|
|
1084
1259
|
assistantName,
|
|
1085
1260
|
avatarUrl,
|
|
1086
1261
|
showToolActivity,
|
|
1087
|
-
renderTool
|
|
1262
|
+
renderTool,
|
|
1263
|
+
onChoose,
|
|
1264
|
+
onRequestInput,
|
|
1265
|
+
active,
|
|
1266
|
+
busy
|
|
1088
1267
|
}) {
|
|
1089
1268
|
if (message.role === "user") {
|
|
1090
|
-
return /* @__PURE__ */
|
|
1091
|
-
message.attachments?.length ? /* @__PURE__ */
|
|
1269
|
+
return /* @__PURE__ */ jsxs5("div", { className: "frankie-user-message flex flex-col items-end gap-1", children: [
|
|
1270
|
+
message.attachments?.length ? /* @__PURE__ */ jsx13("div", { className: "flex max-w-[85%] flex-wrap justify-end gap-1", children: message.attachments.map((file, index) => /* @__PURE__ */ jsxs5(
|
|
1092
1271
|
"span",
|
|
1093
1272
|
{
|
|
1094
1273
|
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",
|
|
1095
1274
|
children: [
|
|
1096
|
-
/* @__PURE__ */
|
|
1097
|
-
/* @__PURE__ */
|
|
1275
|
+
/* @__PURE__ */ jsx13(Paperclip2, { className: "size-3 shrink-0 opacity-70" }),
|
|
1276
|
+
/* @__PURE__ */ jsx13("span", { className: "truncate", children: file.name })
|
|
1098
1277
|
]
|
|
1099
1278
|
},
|
|
1100
1279
|
`${file.name}-${index}`
|
|
1101
1280
|
)) }) : null,
|
|
1102
|
-
message.text ? /* @__PURE__ */
|
|
1103
|
-
/* @__PURE__ */
|
|
1104
|
-
/* @__PURE__ */
|
|
1281
|
+
message.text ? /* @__PURE__ */ jsxs5("div", { className: "flex max-w-[85%] items-end gap-1", children: [
|
|
1282
|
+
/* @__PURE__ */ jsx13("div", { className: "frankie-user-bubble rounded-[var(--frankie-radius)] bg-primary px-3 py-2 text-sm text-primary-foreground", children: message.text }),
|
|
1283
|
+
/* @__PURE__ */ jsx13(MessageCopyButton, { text: message.text })
|
|
1105
1284
|
] }) : null
|
|
1106
1285
|
] });
|
|
1107
1286
|
}
|
|
1108
1287
|
const empty = message.text.length === 0 && message.tools.length === 0;
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1288
|
+
const { body, options } = extractChoices(message.text);
|
|
1289
|
+
const showChoices = Boolean(active && onChoose && options && message.status === "done");
|
|
1290
|
+
return /* @__PURE__ */ jsxs5("div", { className: "frankie-assistant-message flex gap-2", children: [
|
|
1291
|
+
/* @__PURE__ */ jsxs5("div", { className: "frankie-assistant-actions mt-0.5 flex shrink-0 flex-col items-center gap-1", children: [
|
|
1292
|
+
/* @__PURE__ */ jsx13(Avatar, { ...avatarUrl ? { src: avatarUrl } : {}, name: assistantName }),
|
|
1293
|
+
body ? /* @__PURE__ */ jsx13(MessageCopyButton, { text: body }) : null
|
|
1113
1294
|
] }),
|
|
1114
|
-
/* @__PURE__ */
|
|
1115
|
-
message.status === "error" ? /* @__PURE__ */
|
|
1116
|
-
|
|
1295
|
+
/* @__PURE__ */ jsxs5("div", { className: "frankie-assistant-content min-w-0 max-w-[85%]", children: [
|
|
1296
|
+
message.status === "error" ? /* @__PURE__ */ jsx13("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__ */ jsx13(TypingDots, {}) : /* @__PURE__ */ jsx13(Markdown, { text: body, className: "text-sm" }),
|
|
1297
|
+
showChoices ? /* @__PURE__ */ jsx13(Choices, { options: options ?? [], onPick: onChoose, ...onRequestInput ? { onSomethingElse: onRequestInput } : {}, disabled: busy }) : null,
|
|
1298
|
+
showToolActivity ? /* @__PURE__ */ jsx13(ToolActivityList, { tools: message.tools, ...renderTool ? { renderTool } : {} }) : null
|
|
1117
1299
|
] })
|
|
1118
1300
|
] });
|
|
1119
|
-
}
|
|
1301
|
+
});
|
|
1120
1302
|
function MessageCopyButton({ text }) {
|
|
1121
|
-
const [copied, setCopied] =
|
|
1303
|
+
const [copied, setCopied] = useState5(false);
|
|
1122
1304
|
const copy = async () => {
|
|
1123
1305
|
try {
|
|
1124
1306
|
await navigator.clipboard.writeText(text);
|
|
@@ -1127,12 +1309,12 @@ function MessageCopyButton({ text }) {
|
|
|
1127
1309
|
} catch {
|
|
1128
1310
|
}
|
|
1129
1311
|
};
|
|
1130
|
-
return /* @__PURE__ */
|
|
1312
|
+
return /* @__PURE__ */ jsx13("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__ */ jsx13(Check2, { className: "size-3.5", "aria-hidden": "true" }) : /* @__PURE__ */ jsx13(Copy, { className: "size-3.5", "aria-hidden": "true" }) });
|
|
1131
1313
|
}
|
|
1132
1314
|
function TypingDots() {
|
|
1133
|
-
return /* @__PURE__ */
|
|
1134
|
-
/* @__PURE__ */
|
|
1135
|
-
[0, 1, 2].map((index) => /* @__PURE__ */
|
|
1315
|
+
return /* @__PURE__ */ jsxs5("span", { className: "frankie-thinking inline-flex items-center gap-1 py-2", role: "status", "aria-label": "Thinking", children: [
|
|
1316
|
+
/* @__PURE__ */ jsx13("span", { className: "frankie-thinking-label", children: "Thinking" }),
|
|
1317
|
+
[0, 1, 2].map((index) => /* @__PURE__ */ jsx13(
|
|
1136
1318
|
"span",
|
|
1137
1319
|
{
|
|
1138
1320
|
className: cn("size-1.5 animate-pulse rounded-full bg-muted-foreground"),
|
|
@@ -1144,14 +1326,14 @@ function TypingDots() {
|
|
|
1144
1326
|
}
|
|
1145
1327
|
|
|
1146
1328
|
// src/components/chat/SuggestedPrompts.tsx
|
|
1147
|
-
import { jsx as
|
|
1329
|
+
import { jsx as jsx14 } from "react/jsx-runtime";
|
|
1148
1330
|
function SuggestedPrompts({
|
|
1149
1331
|
prompts,
|
|
1150
1332
|
onPick,
|
|
1151
1333
|
disabled
|
|
1152
1334
|
}) {
|
|
1153
1335
|
if (prompts.length === 0) return null;
|
|
1154
|
-
return /* @__PURE__ */
|
|
1336
|
+
return /* @__PURE__ */ jsx14("div", { className: "flex flex-wrap gap-2", children: prompts.map((prompt) => /* @__PURE__ */ jsx14(
|
|
1155
1337
|
Button,
|
|
1156
1338
|
{
|
|
1157
1339
|
type: "button",
|
|
@@ -1166,7 +1348,7 @@ function SuggestedPrompts({
|
|
|
1166
1348
|
}
|
|
1167
1349
|
|
|
1168
1350
|
// src/components/chat/ChatPanel.tsx
|
|
1169
|
-
import { jsx as
|
|
1351
|
+
import { jsx as jsx15, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1170
1352
|
function ChatPanel({
|
|
1171
1353
|
className,
|
|
1172
1354
|
header,
|
|
@@ -1186,8 +1368,10 @@ function ChatPanel({
|
|
|
1186
1368
|
const atBottomRef = useRef4(true);
|
|
1187
1369
|
const features = config?.features;
|
|
1188
1370
|
const pickerModels = features?.modelPicker.enabled ? features.modelPicker.models : [];
|
|
1189
|
-
const [model, setModel] =
|
|
1190
|
-
|
|
1371
|
+
const [model, setModel] = useState6(void 0);
|
|
1372
|
+
const composerFocus = useRef4(null);
|
|
1373
|
+
const focusComposer = () => composerFocus.current?.();
|
|
1374
|
+
useEffect5(() => {
|
|
1191
1375
|
const node = scrollRef.current;
|
|
1192
1376
|
if (!node) return;
|
|
1193
1377
|
const onScroll = () => {
|
|
@@ -1196,14 +1380,14 @@ function ChatPanel({
|
|
|
1196
1380
|
node.addEventListener("scroll", onScroll, { passive: true });
|
|
1197
1381
|
return () => node.removeEventListener("scroll", onScroll);
|
|
1198
1382
|
}, []);
|
|
1199
|
-
|
|
1383
|
+
useEffect5(() => {
|
|
1200
1384
|
const node = scrollRef.current;
|
|
1201
1385
|
if (!node || !atBottomRef.current) return;
|
|
1202
1386
|
node.scrollTop = node.scrollHeight;
|
|
1203
1387
|
}, [messages]);
|
|
1204
1388
|
const name = config?.branding.name ?? "Assistant";
|
|
1205
1389
|
const mode = resolveMode(theme?.mode);
|
|
1206
|
-
return /* @__PURE__ */
|
|
1390
|
+
return /* @__PURE__ */ jsxs6(
|
|
1207
1391
|
Card,
|
|
1208
1392
|
{
|
|
1209
1393
|
className: cn(
|
|
@@ -1216,44 +1400,49 @@ function ChatPanel({
|
|
|
1216
1400
|
style: themeToCssVars(theme),
|
|
1217
1401
|
"data-frankie-mode": mode,
|
|
1218
1402
|
children: [
|
|
1219
|
-
header === false ? null : header !== void 0 ? header : /* @__PURE__ */
|
|
1220
|
-
/* @__PURE__ */
|
|
1221
|
-
/* @__PURE__ */
|
|
1222
|
-
/* @__PURE__ */
|
|
1223
|
-
config?.branding.tagline ? /* @__PURE__ */
|
|
1403
|
+
header === false ? null : header !== void 0 ? header : /* @__PURE__ */ jsxs6(CardHeader, { children: [
|
|
1404
|
+
/* @__PURE__ */ jsx15(Avatar, { ...config?.branding.avatarUrl ? { src: config.branding.avatarUrl } : {}, name }),
|
|
1405
|
+
/* @__PURE__ */ jsxs6("div", { className: "min-w-0 flex-1", children: [
|
|
1406
|
+
/* @__PURE__ */ jsx15(CardTitle, { children: name }),
|
|
1407
|
+
config?.branding.tagline ? /* @__PURE__ */ jsx15(CardDescription, { children: config.branding.tagline }) : null
|
|
1224
1408
|
] }),
|
|
1225
|
-
/* @__PURE__ */
|
|
1226
|
-
onClose ? /* @__PURE__ */
|
|
1409
|
+
/* @__PURE__ */ jsx15(Button, { size: "icon", variant: "ghost", onClick: reset, "aria-label": "Start a new conversation", title: "New conversation", children: /* @__PURE__ */ jsx15(RotateCcw, {}) }),
|
|
1410
|
+
onClose ? /* @__PURE__ */ jsx15(Button, { size: "icon", variant: "ghost", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ jsx15(X3, {}) }) : null
|
|
1227
1411
|
] }),
|
|
1228
|
-
/* @__PURE__ */
|
|
1229
|
-
/* @__PURE__ */
|
|
1230
|
-
/* @__PURE__ */
|
|
1231
|
-
/* @__PURE__ */
|
|
1232
|
-
/* @__PURE__ */
|
|
1233
|
-
] }) : loading ? /* @__PURE__ */
|
|
1234
|
-
/* @__PURE__ */
|
|
1235
|
-
/* @__PURE__ */
|
|
1236
|
-
] }) : /* @__PURE__ */
|
|
1237
|
-
(message) => renderMessage ? /* @__PURE__ */
|
|
1412
|
+
/* @__PURE__ */ jsx15(AgentContext, {}),
|
|
1413
|
+
/* @__PURE__ */ jsx15(CardContent, { className: "frankie-conversation-content", children: /* @__PURE__ */ jsx15(ScrollArea, { ref: scrollRef, className: "frankie-transcript flex h-full flex-col gap-4 p-4", "aria-live": "polite", children: configError ? /* @__PURE__ */ jsxs6("div", { className: "frankie-config-error", role: "alert", children: [
|
|
1414
|
+
/* @__PURE__ */ jsx15("strong", { children: "Frankie couldn\u2019t start" }),
|
|
1415
|
+
/* @__PURE__ */ jsx15("p", { children: configError }),
|
|
1416
|
+
/* @__PURE__ */ jsx15("p", { children: "Refresh this page after checking the workspace\u2019s allowed origin and sign-in settings." })
|
|
1417
|
+
] }) : loading ? /* @__PURE__ */ jsx15("p", { className: "text-sm text-muted-foreground", children: "Starting Frankie\u2026" }) : messages.length === 0 ? emptyState ?? /* @__PURE__ */ jsxs6("div", { className: "flex flex-col gap-4", children: [
|
|
1418
|
+
/* @__PURE__ */ jsx15("p", { className: "text-sm text-muted-foreground", children: config?.greeting }),
|
|
1419
|
+
/* @__PURE__ */ jsx15(SuggestedPrompts, { prompts: config?.suggestedPrompts ?? [], onPick: send, disabled: busy })
|
|
1420
|
+
] }) : /* @__PURE__ */ jsx15("div", { className: "frankie-message-list flex flex-col gap-4", children: messages.map(
|
|
1421
|
+
(message, index) => renderMessage ? /* @__PURE__ */ jsx15("div", { children: renderMessage(message) }, message.id) : /* @__PURE__ */ jsx15(
|
|
1238
1422
|
MessageBubble,
|
|
1239
1423
|
{
|
|
1240
1424
|
message,
|
|
1241
1425
|
assistantName: name,
|
|
1242
1426
|
...config?.branding.avatarUrl ? { avatarUrl: config.branding.avatarUrl } : {},
|
|
1243
1427
|
showToolActivity: config?.showToolActivity ?? true,
|
|
1244
|
-
...renderTool ? { renderTool } : {}
|
|
1428
|
+
...renderTool ? { renderTool } : {},
|
|
1429
|
+
onChoose: send,
|
|
1430
|
+
onRequestInput: focusComposer,
|
|
1431
|
+
active: index === messages.length - 1,
|
|
1432
|
+
busy
|
|
1245
1433
|
},
|
|
1246
1434
|
message.id
|
|
1247
1435
|
)
|
|
1248
1436
|
) }) }) }),
|
|
1249
|
-
/* @__PURE__ */
|
|
1250
|
-
error ? /* @__PURE__ */
|
|
1251
|
-
/* @__PURE__ */
|
|
1437
|
+
/* @__PURE__ */ jsxs6(CardFooter, { className: "frankie-composer-footer flex flex-col gap-2", children: [
|
|
1438
|
+
error ? /* @__PURE__ */ jsx15("p", { className: "w-full text-xs text-red-600 dark:text-red-400", children: error }) : null,
|
|
1439
|
+
/* @__PURE__ */ jsx15("div", { className: "w-full", children: /* @__PURE__ */ jsx15(
|
|
1252
1440
|
Composer,
|
|
1253
1441
|
{
|
|
1254
1442
|
onSend: send,
|
|
1255
1443
|
onStop: stop,
|
|
1256
1444
|
busy,
|
|
1445
|
+
focusHandleRef: composerFocus,
|
|
1257
1446
|
disabled: loading || restoring || Boolean(configError),
|
|
1258
1447
|
...placeholder ? { placeholder } : {},
|
|
1259
1448
|
...autoFocus ? { autoFocus } : {},
|
|
@@ -1269,7 +1458,7 @@ function ChatPanel({
|
|
|
1269
1458
|
}
|
|
1270
1459
|
|
|
1271
1460
|
// src/components/chat/FrankieWidget.tsx
|
|
1272
|
-
import { jsx as
|
|
1461
|
+
import { jsx as jsx16, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1273
1462
|
function FrankieWidget({
|
|
1274
1463
|
position = "bottom-right",
|
|
1275
1464
|
offset = 24,
|
|
@@ -1282,13 +1471,15 @@ function FrankieWidget({
|
|
|
1282
1471
|
}) {
|
|
1283
1472
|
const { config } = useFrankie();
|
|
1284
1473
|
const theme = useFrankieTheme();
|
|
1285
|
-
const
|
|
1474
|
+
const rootRef = useRef5(null);
|
|
1475
|
+
const stylesReady = useStylesReady(rootRef);
|
|
1476
|
+
const [uncontrolledOpen, setUncontrolledOpen] = useState7(defaultOpen);
|
|
1286
1477
|
const open = controlledOpen ?? uncontrolledOpen;
|
|
1287
1478
|
const setOpen = (next) => {
|
|
1288
1479
|
if (controlledOpen === void 0) setUncontrolledOpen(next);
|
|
1289
1480
|
onOpenChange?.(next);
|
|
1290
1481
|
};
|
|
1291
|
-
|
|
1482
|
+
useEffect6(() => {
|
|
1292
1483
|
if (!open) return;
|
|
1293
1484
|
const onKeyDown = (event) => {
|
|
1294
1485
|
if (event.key === "Escape") setOpen(false);
|
|
@@ -1299,15 +1490,17 @@ function FrankieWidget({
|
|
|
1299
1490
|
const label = config?.branding.launcherLabel ?? "Need a hand?";
|
|
1300
1491
|
const mode = resolveMode(theme?.mode);
|
|
1301
1492
|
const side = position === "bottom-right" ? { right: offset } : { left: offset };
|
|
1302
|
-
return /* @__PURE__ */
|
|
1493
|
+
return /* @__PURE__ */ jsx16(
|
|
1303
1494
|
"div",
|
|
1304
1495
|
{
|
|
1496
|
+
ref: rootRef,
|
|
1305
1497
|
className: cn("frankie-root frankie-widget", mode === "dark" && "dark"),
|
|
1306
|
-
style: { ...themeToCssVars(theme), position: "fixed", bottom: offset, ...side, zIndex, pointerEvents: "none" },
|
|
1498
|
+
style: { ...themeToCssVars(theme), position: "fixed", bottom: offset, ...side, zIndex, pointerEvents: "none", ...stylesReady ? null : { opacity: 0 } },
|
|
1307
1499
|
"data-frankie-mode": mode,
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
/* @__PURE__ */
|
|
1500
|
+
"data-frankie-styles": stylesReady ? "ready" : "pending",
|
|
1501
|
+
children: /* @__PURE__ */ jsxs7("div", { className: cn("flex flex-col gap-3", position === "bottom-left" ? "items-start" : "items-end"), children: [
|
|
1502
|
+
open ? /* @__PURE__ */ jsx16("div", { style: { pointerEvents: "auto", maxHeight: "calc(100dvh - 100px)", overflow: "hidden" }, children: /* @__PURE__ */ jsx16(ChatPanel, { ...panelProps, className: cn("frankie-widget-panel", panelProps.className), onClose: () => setOpen(false), autoFocus: true }) }) : null,
|
|
1503
|
+
/* @__PURE__ */ jsx16("div", { style: { pointerEvents: "auto" }, children: renderLauncher ? renderLauncher({ open, toggle: () => setOpen(!open), label }) : /* @__PURE__ */ jsx16(
|
|
1311
1504
|
Button,
|
|
1312
1505
|
{
|
|
1313
1506
|
size: "icon",
|
|
@@ -1316,7 +1509,7 @@ function FrankieWidget({
|
|
|
1316
1509
|
"aria-expanded": open,
|
|
1317
1510
|
"aria-label": open ? "Close the assistant" : label,
|
|
1318
1511
|
title: label,
|
|
1319
|
-
children: open ? /* @__PURE__ */
|
|
1512
|
+
children: open ? /* @__PURE__ */ jsx16(X4, { className: "size-5" }) : /* @__PURE__ */ jsx16(MessageCircle, { className: "size-5" })
|
|
1320
1513
|
}
|
|
1321
1514
|
) })
|
|
1322
1515
|
] })
|
|
@@ -1325,39 +1518,44 @@ function FrankieWidget({
|
|
|
1325
1518
|
}
|
|
1326
1519
|
|
|
1327
1520
|
// src/components/chat/FrankieInline.tsx
|
|
1328
|
-
import {
|
|
1521
|
+
import { useRef as useRef6 } from "react";
|
|
1522
|
+
import { jsx as jsx17 } from "react/jsx-runtime";
|
|
1329
1523
|
function FrankieInline({ className, style, ...panelProps }) {
|
|
1330
1524
|
const theme = useFrankieTheme();
|
|
1331
1525
|
const mode = resolveMode(theme?.mode);
|
|
1332
|
-
|
|
1526
|
+
const rootRef = useRef6(null);
|
|
1527
|
+
const stylesReady = useStylesReady(rootRef);
|
|
1528
|
+
return /* @__PURE__ */ jsx17(
|
|
1333
1529
|
"div",
|
|
1334
1530
|
{
|
|
1531
|
+
ref: rootRef,
|
|
1335
1532
|
className: cn("frankie-root frankie-inline", mode === "dark" && "dark", className),
|
|
1336
|
-
style: { ...themeToCssVars(theme), display: "flex", height: "100%", width: "100%", ...style },
|
|
1533
|
+
style: { ...themeToCssVars(theme), display: "flex", height: "100%", width: "100%", ...style, ...stylesReady ? null : { opacity: 0 } },
|
|
1337
1534
|
"data-frankie-mode": mode,
|
|
1338
|
-
|
|
1535
|
+
"data-frankie-styles": stylesReady ? "ready" : "pending",
|
|
1536
|
+
children: /* @__PURE__ */ jsx17(ChatPanel, { ...panelProps, fill: true })
|
|
1339
1537
|
}
|
|
1340
1538
|
);
|
|
1341
1539
|
}
|
|
1342
1540
|
|
|
1343
1541
|
// src/components/chat/FrankieChatPage.tsx
|
|
1344
|
-
import { useCallback as useCallback3, useEffect as
|
|
1542
|
+
import { useCallback as useCallback3, useEffect as useEffect7, useRef as useRef7, useState as useState8 } from "react";
|
|
1345
1543
|
import { ChevronsLeft, ChevronsRight, History, Menu, Plus as Plus2, Settings, X as X5 } from "lucide-react";
|
|
1346
|
-
import { Fragment as Fragment3, jsx as
|
|
1544
|
+
import { Fragment as Fragment3, jsx as jsx18, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1347
1545
|
function PageEmptyState() {
|
|
1348
1546
|
const { config } = useFrankie();
|
|
1349
1547
|
const { send, busy, restoring } = useFrankieChat();
|
|
1350
1548
|
const name = config?.branding.name ?? "Frankie";
|
|
1351
|
-
return /* @__PURE__ */
|
|
1352
|
-
/* @__PURE__ */
|
|
1353
|
-
/* @__PURE__ */
|
|
1354
|
-
/* @__PURE__ */
|
|
1355
|
-
/* @__PURE__ */
|
|
1356
|
-
/* @__PURE__ */
|
|
1549
|
+
return /* @__PURE__ */ jsxs8("section", { className: "frankie-page-empty", "aria-label": "Start a conversation", children: [
|
|
1550
|
+
/* @__PURE__ */ jsx18("span", { className: "frankie-page-avatar", "aria-hidden": true, children: name.trim().slice(0, 1).toUpperCase() }),
|
|
1551
|
+
/* @__PURE__ */ jsxs8("div", { children: [
|
|
1552
|
+
/* @__PURE__ */ jsx18("p", { className: "frankie-page-kicker", children: "Your product assistant" }),
|
|
1553
|
+
/* @__PURE__ */ jsx18("h1", { children: "How can I help?" }),
|
|
1554
|
+
/* @__PURE__ */ jsx18("p", { children: config?.greeting ?? "Ask a question or choose a task to get started." })
|
|
1357
1555
|
] }),
|
|
1358
|
-
/* @__PURE__ */
|
|
1359
|
-
/* @__PURE__ */
|
|
1360
|
-
/* @__PURE__ */
|
|
1556
|
+
/* @__PURE__ */ jsx18("div", { className: "frankie-page-prompts", children: config?.suggestedPrompts?.slice(0, 3).map((prompt) => /* @__PURE__ */ jsxs8("button", { type: "button", disabled: busy || restoring, onClick: () => void send(prompt), children: [
|
|
1557
|
+
/* @__PURE__ */ jsx18("strong", { children: prompt }),
|
|
1558
|
+
/* @__PURE__ */ jsx18("b", { "aria-hidden": true, children: "\u2197" })
|
|
1361
1559
|
] }, prompt)) })
|
|
1362
1560
|
] });
|
|
1363
1561
|
}
|
|
@@ -1365,21 +1563,21 @@ function PageChrome({ showHistory, navigation, currentId, onSelect, onNew, ...pa
|
|
|
1365
1563
|
const { client, config } = useFrankie();
|
|
1366
1564
|
const { busy, messages } = useFrankieChat();
|
|
1367
1565
|
const theme = useFrankieTheme();
|
|
1368
|
-
const [conversations, setConversations] =
|
|
1369
|
-
const [historyError, setHistoryError] =
|
|
1370
|
-
const [historyLoading, setHistoryLoading] =
|
|
1371
|
-
const [drawerOpen, setDrawerOpen] =
|
|
1372
|
-
const [collapsed, setCollapsed] =
|
|
1566
|
+
const [conversations, setConversations] = useState8([]);
|
|
1567
|
+
const [historyError, setHistoryError] = useState8("");
|
|
1568
|
+
const [historyLoading, setHistoryLoading] = useState8(false);
|
|
1569
|
+
const [drawerOpen, setDrawerOpen] = useState8(false);
|
|
1570
|
+
const [collapsed, setCollapsed] = useState8(() => {
|
|
1373
1571
|
try {
|
|
1374
1572
|
return window.localStorage.getItem("frankie.history-collapsed") === "1";
|
|
1375
1573
|
} catch {
|
|
1376
1574
|
return false;
|
|
1377
1575
|
}
|
|
1378
1576
|
});
|
|
1379
|
-
const [query, setQuery] =
|
|
1380
|
-
const dialog =
|
|
1381
|
-
const menu =
|
|
1382
|
-
const requestVersion =
|
|
1577
|
+
const [query, setQuery] = useState8("");
|
|
1578
|
+
const dialog = useRef7(null);
|
|
1579
|
+
const menu = useRef7(null);
|
|
1580
|
+
const requestVersion = useRef7(0);
|
|
1383
1581
|
const refresh = useCallback3(async () => {
|
|
1384
1582
|
if (!showHistory || !config || config.allowAnonymous) {
|
|
1385
1583
|
setHistoryError("");
|
|
@@ -1401,19 +1599,19 @@ function PageChrome({ showHistory, navigation, currentId, onSelect, onNew, ...pa
|
|
|
1401
1599
|
if (version === requestVersion.current) setHistoryLoading(false);
|
|
1402
1600
|
}
|
|
1403
1601
|
}, [client, showHistory, config?.allowAnonymous]);
|
|
1404
|
-
|
|
1602
|
+
useEffect7(() => {
|
|
1405
1603
|
if (!busy) void refresh();
|
|
1406
1604
|
return () => {
|
|
1407
1605
|
requestVersion.current++;
|
|
1408
1606
|
};
|
|
1409
1607
|
}, [refresh, busy]);
|
|
1410
|
-
|
|
1608
|
+
useEffect7(() => {
|
|
1411
1609
|
try {
|
|
1412
1610
|
window.localStorage.setItem("frankie.history-collapsed", collapsed ? "1" : "0");
|
|
1413
1611
|
} catch {
|
|
1414
1612
|
}
|
|
1415
1613
|
}, [collapsed]);
|
|
1416
|
-
|
|
1614
|
+
useEffect7(() => {
|
|
1417
1615
|
const node = dialog.current;
|
|
1418
1616
|
if (drawerOpen) {
|
|
1419
1617
|
node?.showModal?.();
|
|
@@ -1431,79 +1629,81 @@ function PageChrome({ showHistory, navigation, currentId, onSelect, onNew, ...pa
|
|
|
1431
1629
|
close();
|
|
1432
1630
|
}
|
|
1433
1631
|
};
|
|
1434
|
-
const history = /* @__PURE__ */
|
|
1435
|
-
/* @__PURE__ */
|
|
1436
|
-
/* @__PURE__ */
|
|
1437
|
-
/* @__PURE__ */
|
|
1632
|
+
const history = /* @__PURE__ */ jsxs8(Fragment3, { children: [
|
|
1633
|
+
/* @__PURE__ */ jsxs8("div", { className: "frankie-history-heading", children: [
|
|
1634
|
+
/* @__PURE__ */ jsx18("strong", { children: "Conversations" }),
|
|
1635
|
+
/* @__PURE__ */ jsx18("button", { type: "button", className: "frankie-mobile-only", "aria-label": "Close conversations", onClick: close, children: /* @__PURE__ */ jsx18(X5, { size: 18 }) })
|
|
1438
1636
|
] }),
|
|
1439
|
-
/* @__PURE__ */
|
|
1637
|
+
/* @__PURE__ */ jsxs8("button", { type: "button", className: "frankie-new-chat", disabled: busy, onClick: () => {
|
|
1440
1638
|
onNew();
|
|
1441
1639
|
close();
|
|
1442
1640
|
}, children: [
|
|
1443
|
-
/* @__PURE__ */
|
|
1641
|
+
/* @__PURE__ */ jsx18(Plus2, { size: 17 }),
|
|
1444
1642
|
"New chat"
|
|
1445
1643
|
] }),
|
|
1446
|
-
/* @__PURE__ */
|
|
1447
|
-
/* @__PURE__ */
|
|
1448
|
-
/* @__PURE__ */
|
|
1644
|
+
/* @__PURE__ */ jsxs8("label", { className: "frankie-history-search", children: [
|
|
1645
|
+
/* @__PURE__ */ jsx18("span", { children: "Search conversations" }),
|
|
1646
|
+
/* @__PURE__ */ jsx18("input", { type: "search", placeholder: "Search chats\u2026", value: query, onChange: (event) => setQuery(event.target.value) })
|
|
1449
1647
|
] }),
|
|
1450
|
-
/* @__PURE__ */
|
|
1451
|
-
/* @__PURE__ */
|
|
1452
|
-
/* @__PURE__ */
|
|
1648
|
+
/* @__PURE__ */ jsx18("nav", { className: "frankie-history-list", "aria-label": "Conversations", children: conversations.filter((item) => item.title.toLowerCase().includes(query.toLowerCase())).map((item) => /* @__PURE__ */ jsxs8("button", { type: "button", "aria-current": item.id === currentId ? "page" : void 0, disabled: busy, onClick: () => select(item.id), children: [
|
|
1649
|
+
/* @__PURE__ */ jsx18("span", { children: item.title }),
|
|
1650
|
+
/* @__PURE__ */ jsx18("small", { children: new Date(item.updatedAt).toLocaleDateString() })
|
|
1453
1651
|
] }, item.id)) }),
|
|
1454
|
-
historyLoading ? /* @__PURE__ */
|
|
1652
|
+
historyLoading ? /* @__PURE__ */ jsx18("p", { role: "status", children: "Loading history\u2026" }) : historyError ? /* @__PURE__ */ jsxs8("p", { role: "status", children: [
|
|
1455
1653
|
historyError,
|
|
1456
|
-
/* @__PURE__ */
|
|
1457
|
-
] }) : !conversations.length ? /* @__PURE__ */
|
|
1458
|
-
navigation ? /* @__PURE__ */
|
|
1654
|
+
/* @__PURE__ */ jsx18("button", { type: "button", onClick: () => void refresh(), children: "Try again" })
|
|
1655
|
+
] }) : !conversations.length ? /* @__PURE__ */ jsx18("p", { children: config?.allowAnonymous ? "Sign in to sync conversations across devices." : "Your conversations will appear here." }) : null,
|
|
1656
|
+
navigation ? /* @__PURE__ */ jsx18("div", { className: "frankie-host-navigation", children: navigation }) : null
|
|
1459
1657
|
] });
|
|
1460
|
-
return /* @__PURE__ */
|
|
1461
|
-
showHistory ? /* @__PURE__ */
|
|
1462
|
-
/* @__PURE__ */
|
|
1658
|
+
return /* @__PURE__ */ jsxs8("div", { className: "frankie-page-shell" + (resolveMode(theme?.mode) === "dark" ? " dark" : ""), style: themeToCssVars(theme), children: [
|
|
1659
|
+
showHistory ? /* @__PURE__ */ jsxs8("aside", { className: "frankie-history-desktop" + (collapsed ? " frankie-history-collapsed" : ""), children: [
|
|
1660
|
+
/* @__PURE__ */ jsx18("button", { type: "button", className: "frankie-history-toggle", "aria-label": collapsed ? "Expand conversations" : "Collapse conversations", "aria-expanded": !collapsed, onClick: () => setCollapsed((value) => !value), children: collapsed ? /* @__PURE__ */ jsx18(ChevronsRight, { size: 16 }) : /* @__PURE__ */ jsx18(ChevronsLeft, { size: 16 }) }),
|
|
1463
1661
|
collapsed ? null : history
|
|
1464
1662
|
] }) : null,
|
|
1465
|
-
/* @__PURE__ */
|
|
1466
|
-
/* @__PURE__ */
|
|
1467
|
-
/* @__PURE__ */
|
|
1468
|
-
showHistory ? /* @__PURE__ */
|
|
1469
|
-
/* @__PURE__ */
|
|
1663
|
+
/* @__PURE__ */ jsxs8("div", { className: "frankie-page-main", children: [
|
|
1664
|
+
/* @__PURE__ */ jsxs8("header", { className: "frankie-page-header", children: [
|
|
1665
|
+
/* @__PURE__ */ jsxs8("div", { children: [
|
|
1666
|
+
showHistory ? /* @__PURE__ */ jsx18("button", { type: "button", className: "frankie-mobile-only", ref: menu, "aria-label": "Open conversations", "aria-haspopup": "dialog", "aria-expanded": drawerOpen, onClick: () => setDrawerOpen(true), children: /* @__PURE__ */ jsx18(Menu, { size: 20 }) }) : null,
|
|
1667
|
+
/* @__PURE__ */ jsx18("strong", { className: "frankie-thread-title", children: conversations.find((item) => item.id === currentId)?.title ?? messages.find((message) => message.role === "user")?.text ?? "New chat" })
|
|
1470
1668
|
] }),
|
|
1471
|
-
/* @__PURE__ */
|
|
1472
|
-
/* @__PURE__ */
|
|
1473
|
-
/* @__PURE__ */
|
|
1474
|
-
/* @__PURE__ */
|
|
1669
|
+
/* @__PURE__ */ jsxs8("div", { children: [
|
|
1670
|
+
/* @__PURE__ */ jsxs8("button", { type: "button", disabled: busy, onClick: onNew, "aria-label": "Start a new conversation", children: [
|
|
1671
|
+
/* @__PURE__ */ jsx18(Plus2, { size: 16 }),
|
|
1672
|
+
/* @__PURE__ */ jsx18("span", { children: "New chat" })
|
|
1475
1673
|
] }),
|
|
1476
|
-
/* @__PURE__ */
|
|
1477
|
-
/* @__PURE__ */
|
|
1478
|
-
/* @__PURE__ */
|
|
1479
|
-
/* @__PURE__ */
|
|
1480
|
-
/* @__PURE__ */
|
|
1481
|
-
/* @__PURE__ */
|
|
1482
|
-
/* @__PURE__ */
|
|
1674
|
+
/* @__PURE__ */ jsxs8("details", { className: "frankie-chat-settings", children: [
|
|
1675
|
+
/* @__PURE__ */ jsx18("summary", { "aria-label": "Chat settings", children: /* @__PURE__ */ jsx18(Settings, { size: 19 }) }),
|
|
1676
|
+
/* @__PURE__ */ jsxs8("div", { children: [
|
|
1677
|
+
/* @__PURE__ */ jsx18("strong", { children: "Chat settings" }),
|
|
1678
|
+
/* @__PURE__ */ jsx18("p", { children: "History syncs automatically for signed-in users." }),
|
|
1679
|
+
/* @__PURE__ */ jsxs8("button", { type: "button", onClick: () => void refresh(), disabled: historyLoading || !showHistory, children: [
|
|
1680
|
+
/* @__PURE__ */ jsx18(History, { size: 16 }),
|
|
1483
1681
|
"Refresh history"
|
|
1484
1682
|
] }),
|
|
1485
|
-
/* @__PURE__ */
|
|
1683
|
+
/* @__PURE__ */ jsx18("small", { children: "Skills are checked at the start of each message." })
|
|
1486
1684
|
] })
|
|
1487
1685
|
] })
|
|
1488
1686
|
] })
|
|
1489
1687
|
] }),
|
|
1490
|
-
/* @__PURE__ */
|
|
1688
|
+
/* @__PURE__ */ jsx18("div", { className: "frankie-page-canvas", children: /* @__PURE__ */ jsx18(FrankieInline, { ...panel, header: panel.header ?? false, emptyState: panel.emptyState ?? /* @__PURE__ */ jsx18(PageEmptyState, {}) }) })
|
|
1491
1689
|
] }),
|
|
1492
|
-
showHistory ? /* @__PURE__ */
|
|
1690
|
+
showHistory ? /* @__PURE__ */ jsx18("dialog", { ref: dialog, className: "frankie-history-dialog frankie-root", "aria-label": "Chat conversations", style: themeToCssVars(theme), onCancel: close, onClick: (event) => {
|
|
1493
1691
|
if (event.target === event.currentTarget) close();
|
|
1494
|
-
}, children: /* @__PURE__ */
|
|
1692
|
+
}, children: /* @__PURE__ */ jsx18("aside", { children: history }) }) : null
|
|
1495
1693
|
] });
|
|
1496
1694
|
}
|
|
1497
1695
|
function FrankieChatPage({ className, style, header, footer, emptyState, renderMessage, renderTool, placeholder, autoFocus, showHistory = true, navigation, ...provider }) {
|
|
1498
|
-
const [id, setId] =
|
|
1696
|
+
const [id, setId] = useState8(() => provider.conversationId ?? crypto.randomUUID());
|
|
1499
1697
|
const onNew = () => setId(crypto.randomUUID());
|
|
1500
|
-
|
|
1698
|
+
const pageRef = useRef7(null);
|
|
1699
|
+
const stylesReady = useStylesReady(pageRef);
|
|
1700
|
+
return /* @__PURE__ */ jsx18("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__ */ jsx18(FrankieProvider, { ...provider, conversationId: id, storageNamespace: (provider.storageNamespace ?? "frankie." + provider.publishableKey) + "." + id, children: /* @__PURE__ */ jsx18(PageChrome, { showHistory, navigation, currentId: id, onSelect: setId, onNew, header, footer, emptyState, renderMessage, renderTool, placeholder, autoFocus }) }) });
|
|
1501
1701
|
}
|
|
1502
1702
|
|
|
1503
1703
|
// src/components/ui/separator.tsx
|
|
1504
|
-
import { jsx as
|
|
1704
|
+
import { jsx as jsx19 } from "react/jsx-runtime";
|
|
1505
1705
|
function Separator({ className }) {
|
|
1506
|
-
return /* @__PURE__ */
|
|
1706
|
+
return /* @__PURE__ */ jsx19("div", { role: "separator", className: cn("h-px w-full bg-border", className) });
|
|
1507
1707
|
}
|
|
1508
1708
|
export {
|
|
1509
1709
|
Avatar,
|
|
@@ -1516,6 +1716,7 @@ export {
|
|
|
1516
1716
|
CardHeader,
|
|
1517
1717
|
CardTitle,
|
|
1518
1718
|
ChatPanel,
|
|
1719
|
+
Choices,
|
|
1519
1720
|
Composer,
|
|
1520
1721
|
FRANKIE_CLOUD_URL,
|
|
1521
1722
|
FrankieApiError,
|
|
@@ -1536,6 +1737,7 @@ export {
|
|
|
1536
1737
|
applyEvent,
|
|
1537
1738
|
buttonVariants,
|
|
1538
1739
|
cn,
|
|
1740
|
+
extractChoices,
|
|
1539
1741
|
newAssistantMessage,
|
|
1540
1742
|
newUserMessage,
|
|
1541
1743
|
readEventStream,
|