@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/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") receivedText = true;
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 useEffect5, useState as useState6 } from "react";
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 useEffect4, useRef as useRef4, useState as useState5 } from "react";
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 useEffect3, useRef as useRef3, useState as useState3 } from "react";
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] = useState3("");
728
- const [files, setFiles] = useState3([]);
729
- const [fileError, setFileError] = useState3(void 0);
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
- useEffect3(() => {
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 useState4 } from "react";
1015
+ import { memo as memo2, useState as useState5 } from "react";
905
1016
 
906
- // src/components/chat/Markdown.tsx
907
- import { Fragment as Fragment2 } from "react";
908
- import { jsx as jsx9 } from "react/jsx-runtime";
909
- function Markdown({ text, className }) {
910
- return /* @__PURE__ */ jsx9("div", { className: cn("frankie-prose", className), children: renderBlocks(text) });
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__ */ jsx9("p", { children: renderInline(paragraph.join(" ")) }, `p${blocks.length}`));
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__ */ jsx9("li", { children: renderInline(item) }, index));
926
- blocks.push(list.ordered ? /* @__PURE__ */ jsx9("ol", { children: items }, `l${blocks.length}`) : /* @__PURE__ */ jsx9("ul", { children: items }, `l${blocks.length}`));
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__ */ jsx9("pre", { children: /* @__PURE__ */ jsx9("code", { children: code.lines.join("\n") }) }, `c${blocks.length}`)
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 = /^#{1,6}\s+(.*)$/.exec(line);
1138
+ const heading = /^(#{1,6})\s+(.*)$/.exec(line);
966
1139
  if (heading) {
967
1140
  flushParagraph();
968
1141
  flushList();
969
- blocks.push(/* @__PURE__ */ jsx9("p", { className: "font-semibold", children: renderInline(heading[1] ?? "") }, `h${blocks.length}`));
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__ */ jsx9("pre", { children: /* @__PURE__ */ jsx9("code", { children: code.lines.join("\n") }) }, `c${blocks.length}`)
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__ */ jsx9(Fragment2, { children: renderToken(token) }, start));
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__ */ jsx9("strong", { children: token.slice(2, -2) });
1000
- if (token.startsWith("`")) return /* @__PURE__ */ jsx9("code", { children: token.slice(1, -1) });
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__ */ jsx9("a", { href, target: "_blank", rel: "noopener noreferrer nofollow", children: label });
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 jsx10 } from "react/jsx-runtime";
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__ */ jsx10("span", { className: cn(badgeVariants({ variant }), className), ...props });
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 jsx11, jsxs as jsxs3 } from "react/jsx-runtime";
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__ */ jsxs3("details", { className: "frankie-tool-activity mt-2", open: running, children: [
1042
- /* @__PURE__ */ jsxs3("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: [
1043
- /* @__PURE__ */ jsx11(ChevronDown, { className: "frankie-tool-chevron size-3.5 transition-transform", "aria-hidden": "true" }),
1044
- /* @__PURE__ */ jsx11("span", { children: running ? "Working" : "Activity" }),
1045
- /* @__PURE__ */ jsxs3("span", { className: "text-[11px]", children: [
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__ */ jsx11("ul", { className: "mt-1 flex flex-col gap-1 border-l border-border pl-2", children: tools.map((tool) => /* @__PURE__ */ jsx11("li", { children: renderTool ? renderTool(tool) : /* @__PURE__ */ jsx11(ToolActivityRow, { tool }) }, tool.id)) })
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__ */ jsxs3(Badge, { variant: badgeVariant(tool.status), title: tool.detail ?? tool.name, children: [
1057
- /* @__PURE__ */ jsx11(ToolIcon, { status: tool.status }),
1058
- /* @__PURE__ */ jsx11("span", { className: "truncate", children: tool.label })
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__ */ jsx11(Loader2, { className: "size-3 animate-spin", "aria-hidden": "true" });
1239
+ return /* @__PURE__ */ jsx12(Loader2, { className: "size-3 animate-spin", "aria-hidden": "true" });
1065
1240
  case "done":
1066
- return /* @__PURE__ */ jsx11(Check, { className: "size-3", "aria-hidden": "true" });
1241
+ return /* @__PURE__ */ jsx12(Check, { className: "size-3", "aria-hidden": "true" });
1067
1242
  case "denied":
1068
- return /* @__PURE__ */ jsx11(ShieldAlert, { className: "size-3", "aria-hidden": "true" });
1243
+ return /* @__PURE__ */ jsx12(ShieldAlert, { className: "size-3", "aria-hidden": "true" });
1069
1244
  default:
1070
- return /* @__PURE__ */ jsx11(X2, { className: "size-3", "aria-hidden": "true" });
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 jsx12, jsxs as jsxs4 } from "react/jsx-runtime";
1082
- function MessageBubble({
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__ */ jsxs4("div", { className: "frankie-user-message flex flex-col items-end gap-1", children: [
1091
- message.attachments?.length ? /* @__PURE__ */ jsx12("div", { className: "flex max-w-[85%] flex-wrap justify-end gap-1", children: message.attachments.map((file, index) => /* @__PURE__ */ jsxs4(
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__ */ jsx12(Paperclip2, { className: "size-3 shrink-0 opacity-70" }),
1097
- /* @__PURE__ */ jsx12("span", { className: "truncate", children: file.name })
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__ */ jsxs4("div", { className: "flex max-w-[85%] items-end gap-1", children: [
1103
- /* @__PURE__ */ jsx12("div", { className: "frankie-user-bubble rounded-[var(--frankie-radius)] bg-primary px-3 py-2 text-sm text-primary-foreground", children: message.text }),
1104
- /* @__PURE__ */ jsx12(MessageCopyButton, { text: message.text })
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
- return /* @__PURE__ */ jsxs4("div", { className: "frankie-assistant-message flex gap-2", children: [
1110
- /* @__PURE__ */ jsxs4("div", { className: "frankie-assistant-actions mt-0.5 flex shrink-0 flex-col items-center gap-1", children: [
1111
- /* @__PURE__ */ jsx12(Avatar, { ...avatarUrl ? { src: avatarUrl } : {}, name: assistantName }),
1112
- message.text ? /* @__PURE__ */ jsx12(MessageCopyButton, { text: message.text }) : null
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__ */ jsxs4("div", { className: "frankie-assistant-content min-w-0 max-w-[85%]", children: [
1115
- message.status === "error" ? /* @__PURE__ */ jsx12("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__ */ jsx12(TypingDots, {}) : /* @__PURE__ */ jsx12(Markdown, { text: message.text, className: "text-sm" }),
1116
- showToolActivity ? /* @__PURE__ */ jsx12(ToolActivityList, { tools: message.tools, ...renderTool ? { renderTool } : {} }) : null
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] = useState4(false);
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__ */ jsx12("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__ */ jsx12(Check2, { className: "size-3.5", "aria-hidden": "true" }) : /* @__PURE__ */ jsx12(Copy, { className: "size-3.5", "aria-hidden": "true" }) });
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__ */ jsxs4("span", { className: "frankie-thinking inline-flex items-center gap-1 py-2", role: "status", "aria-label": "Thinking", children: [
1134
- /* @__PURE__ */ jsx12("span", { className: "frankie-thinking-label", children: "Thinking" }),
1135
- [0, 1, 2].map((index) => /* @__PURE__ */ jsx12(
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 jsx13 } from "react/jsx-runtime";
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__ */ jsx13("div", { className: "flex flex-wrap gap-2", children: prompts.map((prompt) => /* @__PURE__ */ jsx13(
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 jsx14, jsxs as jsxs5 } from "react/jsx-runtime";
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] = useState5(void 0);
1190
- useEffect4(() => {
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
- useEffect4(() => {
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__ */ jsxs5(
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__ */ jsxs5(CardHeader, { children: [
1220
- /* @__PURE__ */ jsx14(Avatar, { ...config?.branding.avatarUrl ? { src: config.branding.avatarUrl } : {}, name }),
1221
- /* @__PURE__ */ jsxs5("div", { className: "min-w-0 flex-1", children: [
1222
- /* @__PURE__ */ jsx14(CardTitle, { children: name }),
1223
- config?.branding.tagline ? /* @__PURE__ */ jsx14(CardDescription, { children: config.branding.tagline }) : null
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__ */ jsx14(Button, { size: "icon", variant: "ghost", onClick: reset, "aria-label": "Start a new conversation", title: "New conversation", children: /* @__PURE__ */ jsx14(RotateCcw, {}) }),
1226
- onClose ? /* @__PURE__ */ jsx14(Button, { size: "icon", variant: "ghost", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ jsx14(X3, {}) }) : null
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__ */ jsx14(AgentContext, {}),
1229
- /* @__PURE__ */ jsx14(CardContent, { className: "frankie-conversation-content", children: /* @__PURE__ */ jsx14(ScrollArea, { ref: scrollRef, className: "frankie-transcript flex h-full flex-col gap-4 p-4", "aria-live": "polite", children: configError ? /* @__PURE__ */ jsxs5("div", { className: "frankie-config-error", role: "alert", children: [
1230
- /* @__PURE__ */ jsx14("strong", { children: "Frankie couldn\u2019t start" }),
1231
- /* @__PURE__ */ jsx14("p", { children: configError }),
1232
- /* @__PURE__ */ jsx14("p", { children: "Refresh this page after checking the workspace\u2019s allowed origin and sign-in settings." })
1233
- ] }) : loading ? /* @__PURE__ */ jsx14("p", { className: "text-sm text-muted-foreground", children: "Starting Frankie\u2026" }) : messages.length === 0 ? emptyState ?? /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-4", children: [
1234
- /* @__PURE__ */ jsx14("p", { className: "text-sm text-muted-foreground", children: config?.greeting }),
1235
- /* @__PURE__ */ jsx14(SuggestedPrompts, { prompts: config?.suggestedPrompts ?? [], onPick: send, disabled: busy })
1236
- ] }) : /* @__PURE__ */ jsx14("div", { className: "frankie-message-list flex flex-col gap-4", children: messages.map(
1237
- (message) => renderMessage ? /* @__PURE__ */ jsx14("div", { children: renderMessage(message) }, message.id) : /* @__PURE__ */ jsx14(
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__ */ jsxs5(CardFooter, { className: "frankie-composer-footer flex flex-col gap-2", children: [
1250
- error ? /* @__PURE__ */ jsx14("p", { className: "w-full text-xs text-red-600 dark:text-red-400", children: error }) : null,
1251
- /* @__PURE__ */ jsx14("div", { className: "w-full", children: /* @__PURE__ */ jsx14(
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 jsx15, jsxs as jsxs6 } from "react/jsx-runtime";
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 [uncontrolledOpen, setUncontrolledOpen] = useState6(defaultOpen);
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
- useEffect5(() => {
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__ */ jsx15(
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
- children: /* @__PURE__ */ jsxs6("div", { className: cn("flex flex-col gap-3", position === "bottom-left" ? "items-start" : "items-end"), children: [
1309
- open ? /* @__PURE__ */ jsx15("div", { style: { pointerEvents: "auto", maxHeight: "calc(100dvh - 100px)", overflow: "hidden" }, children: /* @__PURE__ */ jsx15(ChatPanel, { ...panelProps, className: cn("frankie-widget-panel", panelProps.className), onClose: () => setOpen(false), autoFocus: true }) }) : null,
1310
- /* @__PURE__ */ jsx15("div", { style: { pointerEvents: "auto" }, children: renderLauncher ? renderLauncher({ open, toggle: () => setOpen(!open), label }) : /* @__PURE__ */ jsx15(
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__ */ jsx15(X4, { className: "size-5" }) : /* @__PURE__ */ jsx15(MessageCircle, { className: "size-5" })
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 { jsx as jsx16 } from "react/jsx-runtime";
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
- return /* @__PURE__ */ jsx16(
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
- children: /* @__PURE__ */ jsx16(ChatPanel, { ...panelProps, fill: true })
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 useEffect6, useRef as useRef5, useState as useState7 } from "react";
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 jsx17, jsxs as jsxs7 } from "react/jsx-runtime";
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__ */ jsxs7("section", { className: "frankie-page-empty", "aria-label": "Start a conversation", children: [
1352
- /* @__PURE__ */ jsx17("span", { className: "frankie-page-avatar", "aria-hidden": true, children: name.trim().slice(0, 1).toUpperCase() }),
1353
- /* @__PURE__ */ jsxs7("div", { children: [
1354
- /* @__PURE__ */ jsx17("p", { className: "frankie-page-kicker", children: "Your product assistant" }),
1355
- /* @__PURE__ */ jsx17("h1", { children: "How can I help?" }),
1356
- /* @__PURE__ */ jsx17("p", { children: config?.greeting ?? "Ask a question or choose a task to get started." })
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__ */ jsx17("div", { className: "frankie-page-prompts", children: config?.suggestedPrompts?.slice(0, 3).map((prompt) => /* @__PURE__ */ jsxs7("button", { type: "button", disabled: busy || restoring, onClick: () => void send(prompt), children: [
1359
- /* @__PURE__ */ jsx17("strong", { children: prompt }),
1360
- /* @__PURE__ */ jsx17("b", { "aria-hidden": true, children: "\u2197" })
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] = useState7([]);
1369
- const [historyError, setHistoryError] = useState7("");
1370
- const [historyLoading, setHistoryLoading] = useState7(false);
1371
- const [drawerOpen, setDrawerOpen] = useState7(false);
1372
- const [collapsed, setCollapsed] = useState7(() => {
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] = useState7("");
1380
- const dialog = useRef5(null);
1381
- const menu = useRef5(null);
1382
- const requestVersion = useRef5(0);
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
- useEffect6(() => {
1602
+ useEffect7(() => {
1405
1603
  if (!busy) void refresh();
1406
1604
  return () => {
1407
1605
  requestVersion.current++;
1408
1606
  };
1409
1607
  }, [refresh, busy]);
1410
- useEffect6(() => {
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
- useEffect6(() => {
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__ */ jsxs7(Fragment3, { children: [
1435
- /* @__PURE__ */ jsxs7("div", { className: "frankie-history-heading", children: [
1436
- /* @__PURE__ */ jsx17("strong", { children: "Conversations" }),
1437
- /* @__PURE__ */ jsx17("button", { type: "button", className: "frankie-mobile-only", "aria-label": "Close conversations", onClick: close, children: /* @__PURE__ */ jsx17(X5, { size: 18 }) })
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__ */ jsxs7("button", { type: "button", className: "frankie-new-chat", disabled: busy, onClick: () => {
1637
+ /* @__PURE__ */ jsxs8("button", { type: "button", className: "frankie-new-chat", disabled: busy, onClick: () => {
1440
1638
  onNew();
1441
1639
  close();
1442
1640
  }, children: [
1443
- /* @__PURE__ */ jsx17(Plus2, { size: 17 }),
1641
+ /* @__PURE__ */ jsx18(Plus2, { size: 17 }),
1444
1642
  "New chat"
1445
1643
  ] }),
1446
- /* @__PURE__ */ jsxs7("label", { className: "frankie-history-search", children: [
1447
- /* @__PURE__ */ jsx17("span", { children: "Search conversations" }),
1448
- /* @__PURE__ */ jsx17("input", { type: "search", placeholder: "Search chats\u2026", value: query, onChange: (event) => setQuery(event.target.value) })
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__ */ jsx17("nav", { className: "frankie-history-list", "aria-label": "Conversations", children: conversations.filter((item) => item.title.toLowerCase().includes(query.toLowerCase())).map((item) => /* @__PURE__ */ jsxs7("button", { type: "button", "aria-current": item.id === currentId ? "page" : void 0, disabled: busy, onClick: () => select(item.id), children: [
1451
- /* @__PURE__ */ jsx17("span", { children: item.title }),
1452
- /* @__PURE__ */ jsx17("small", { children: new Date(item.updatedAt).toLocaleDateString() })
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__ */ jsx17("p", { role: "status", children: "Loading history\u2026" }) : historyError ? /* @__PURE__ */ jsxs7("p", { role: "status", children: [
1652
+ historyLoading ? /* @__PURE__ */ jsx18("p", { role: "status", children: "Loading history\u2026" }) : historyError ? /* @__PURE__ */ jsxs8("p", { role: "status", children: [
1455
1653
  historyError,
1456
- /* @__PURE__ */ jsx17("button", { type: "button", onClick: () => void refresh(), children: "Try again" })
1457
- ] }) : !conversations.length ? /* @__PURE__ */ jsx17("p", { children: config?.allowAnonymous ? "Sign in to sync conversations across devices." : "Your conversations will appear here." }) : null,
1458
- navigation ? /* @__PURE__ */ jsx17("div", { className: "frankie-host-navigation", children: navigation }) : null
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__ */ jsxs7("div", { className: "frankie-page-shell" + (resolveMode(theme?.mode) === "dark" ? " dark" : ""), style: themeToCssVars(theme), children: [
1461
- showHistory ? /* @__PURE__ */ jsxs7("aside", { className: "frankie-history-desktop" + (collapsed ? " frankie-history-collapsed" : ""), children: [
1462
- /* @__PURE__ */ jsx17("button", { type: "button", className: "frankie-history-toggle", "aria-label": collapsed ? "Expand conversations" : "Collapse conversations", "aria-expanded": !collapsed, onClick: () => setCollapsed((value) => !value), children: collapsed ? /* @__PURE__ */ jsx17(ChevronsRight, { size: 16 }) : /* @__PURE__ */ jsx17(ChevronsLeft, { size: 16 }) }),
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__ */ jsxs7("div", { className: "frankie-page-main", children: [
1466
- /* @__PURE__ */ jsxs7("header", { className: "frankie-page-header", children: [
1467
- /* @__PURE__ */ jsxs7("div", { children: [
1468
- showHistory ? /* @__PURE__ */ jsx17("button", { type: "button", className: "frankie-mobile-only", ref: menu, "aria-label": "Open conversations", "aria-haspopup": "dialog", "aria-expanded": drawerOpen, onClick: () => setDrawerOpen(true), children: /* @__PURE__ */ jsx17(Menu, { size: 20 }) }) : null,
1469
- /* @__PURE__ */ jsx17("strong", { className: "frankie-thread-title", children: conversations.find((item) => item.id === currentId)?.title ?? messages.find((message) => message.role === "user")?.text ?? "New chat" })
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__ */ jsxs7("div", { children: [
1472
- /* @__PURE__ */ jsxs7("button", { type: "button", disabled: busy, onClick: onNew, "aria-label": "Start a new conversation", children: [
1473
- /* @__PURE__ */ jsx17(Plus2, { size: 16 }),
1474
- /* @__PURE__ */ jsx17("span", { children: "New chat" })
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__ */ jsxs7("details", { className: "frankie-chat-settings", children: [
1477
- /* @__PURE__ */ jsx17("summary", { "aria-label": "Chat settings", children: /* @__PURE__ */ jsx17(Settings, { size: 19 }) }),
1478
- /* @__PURE__ */ jsxs7("div", { children: [
1479
- /* @__PURE__ */ jsx17("strong", { children: "Chat settings" }),
1480
- /* @__PURE__ */ jsx17("p", { children: "History syncs automatically for signed-in users." }),
1481
- /* @__PURE__ */ jsxs7("button", { type: "button", onClick: () => void refresh(), disabled: historyLoading || !showHistory, children: [
1482
- /* @__PURE__ */ jsx17(History, { size: 16 }),
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__ */ jsx17("small", { children: "Skills are checked at the start of each message." })
1683
+ /* @__PURE__ */ jsx18("small", { children: "Skills are checked at the start of each message." })
1486
1684
  ] })
1487
1685
  ] })
1488
1686
  ] })
1489
1687
  ] }),
1490
- /* @__PURE__ */ jsx17("div", { className: "frankie-page-canvas", children: /* @__PURE__ */ jsx17(FrankieInline, { ...panel, header: panel.header ?? false, emptyState: panel.emptyState ?? /* @__PURE__ */ jsx17(PageEmptyState, {}) }) })
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__ */ jsx17("dialog", { ref: dialog, className: "frankie-history-dialog frankie-root", "aria-label": "Chat conversations", style: themeToCssVars(theme), onCancel: close, onClick: (event) => {
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__ */ jsx17("aside", { children: history }) }) : null
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] = useState7(() => provider.conversationId ?? crypto.randomUUID());
1696
+ const [id, setId] = useState8(() => provider.conversationId ?? crypto.randomUUID());
1499
1697
  const onNew = () => setId(crypto.randomUUID());
1500
- return /* @__PURE__ */ jsx17("div", { className: ["frankie-root", "frankie-chat-page", className].filter(Boolean).join(" "), style: { height: "100%", minHeight: "min(36rem, 100dvh)", width: "100%", ...style }, children: /* @__PURE__ */ jsx17(FrankieProvider, { ...provider, conversationId: id, storageNamespace: (provider.storageNamespace ?? "frankie." + provider.publishableKey) + "." + id, children: /* @__PURE__ */ jsx17(PageChrome, { showHistory, navigation, currentId: id, onSelect: setId, onNew, header, footer, emptyState, renderMessage, renderTool, placeholder, autoFocus }) }) });
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 jsx18 } from "react/jsx-runtime";
1704
+ import { jsx as jsx19 } from "react/jsx-runtime";
1505
1705
  function Separator({ className }) {
1506
- return /* @__PURE__ */ jsx18("div", { role: "separator", className: cn("h-px w-full bg-border", className) });
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,