@frankieai/react 0.2.0 → 0.2.2

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
@@ -4,7 +4,7 @@
4
4
  import { createContext as createContext2, useContext as useContext2, useEffect as useEffect2, useMemo, useRef as useRef2, useState as useState2 } from "react";
5
5
 
6
6
  // src/lib/client.ts
7
- var FRANKIE_CLOUD_URL = "https://frankie-data-gq5o5syq2q-uc.a.run.app";
7
+ var FRANKIE_CLOUD_URL = "https://frankie-data-665333762719.us-central1.run.app";
8
8
  var FrankieApiError = class extends Error {
9
9
  constructor(message, status, code) {
10
10
  super(message);
@@ -659,16 +659,51 @@ var ScrollArea = forwardRef(
659
659
  }
660
660
  );
661
661
 
662
+ // src/components/chat/AgentContext.tsx
663
+ import { ArrowUpRight, Sparkles } from "lucide-react";
664
+ import { jsx as jsx6, jsxs } from "react/jsx-runtime";
665
+ function AgentContext() {
666
+ const { loading, configError } = useFrankie();
667
+ const { loadedSkills, busy, restoring, error } = useFrankieChat();
668
+ const status = configError || error ? "Unavailable" : loading || restoring ? "Connecting" : busy && loadedSkills === null ? "Checking" : loadedSkills?.length ? "Active" : loadedSkills ? "Tools only" : "Ready";
669
+ return /* @__PURE__ */ jsxs("div", { className: "frankie-agent-context-wrap", children: [
670
+ /* @__PURE__ */ jsxs("details", { className: "frankie-agent-context", children: [
671
+ /* @__PURE__ */ jsxs("summary", { "aria-label": "Agent context", children: [
672
+ /* @__PURE__ */ jsx6(Sparkles, { size: 17, "aria-hidden": "true" }),
673
+ /* @__PURE__ */ jsx6("span", { children: "Agent context" }),
674
+ /* @__PURE__ */ jsx6("span", { className: "frankie-context-count", children: loadedSkills === null ? "Not checked" : `${loadedSkills.length} ${loadedSkills.length === 1 ? "skill" : "skills"}` }),
675
+ /* @__PURE__ */ jsxs("span", { className: "frankie-context-status", "data-active": status === "Active", children: [
676
+ /* @__PURE__ */ jsx6("i", { "aria-hidden": "true" }),
677
+ status
678
+ ] }),
679
+ /* @__PURE__ */ jsx6(ArrowUpRight, { size: 15, "aria-hidden": "true" })
680
+ ] }),
681
+ /* @__PURE__ */ jsxs("div", { className: "frankie-context-detail", children: [
682
+ /* @__PURE__ */ jsx6("strong", { children: loadedSkills === null ? "Skills are checked when you send a message" : loadedSkills.length ? `${loadedSkills.length} ${loadedSkills.length === 1 ? "skill" : "skills"} loaded` : "No skills loaded for this turn" }),
683
+ loadedSkills?.length ? /* @__PURE__ */ jsx6("ul", { children: loadedSkills.map((skill) => /* @__PURE__ */ jsxs("li", { children: [
684
+ /* @__PURE__ */ jsx6("span", { children: skill.name }),
685
+ /* @__PURE__ */ jsxs("small", { children: [
686
+ "v",
687
+ skill.revision
688
+ ] })
689
+ ] }, skill.id)) }) : /* @__PURE__ */ jsx6("p", { children: "Frankie can use the tools approved for this workspace. Drafts and skills without available tools are not loaded." }),
690
+ /* @__PURE__ */ jsx6("p", { children: "Published skills and updates are checked on every message. Only approved, available tools can run." })
691
+ ] })
692
+ ] }),
693
+ /* @__PURE__ */ jsx6("span", { className: "frankie-sr-only", role: "status", children: loadedSkills === null ? status : `${loadedSkills.length} ${loadedSkills.length === 1 ? "skill" : "skills"} loaded. ${status}.` })
694
+ ] });
695
+ }
696
+
662
697
  // src/components/chat/Composer.tsx
663
- import { ArrowUp, Paperclip, Square, X } from "lucide-react";
698
+ import { ArrowUp, Paperclip, Plus, Square, X } from "lucide-react";
664
699
  import { useCallback as useCallback2, useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
665
700
 
666
701
  // src/components/ui/textarea.tsx
667
702
  import { forwardRef as forwardRef2 } from "react";
668
- import { jsx as jsx6 } from "react/jsx-runtime";
703
+ import { jsx as jsx7 } from "react/jsx-runtime";
669
704
  var Textarea = forwardRef2(
670
705
  function Textarea2({ className, ...props }, ref) {
671
- return /* @__PURE__ */ jsx6(
706
+ return /* @__PURE__ */ jsx7(
672
707
  "textarea",
673
708
  {
674
709
  ref,
@@ -683,7 +718,7 @@ var Textarea = forwardRef2(
683
718
  );
684
719
 
685
720
  // src/components/chat/Composer.tsx
686
- import { Fragment, jsx as jsx7, jsxs } from "react/jsx-runtime";
721
+ import { Fragment, jsx as jsx8, jsxs as jsxs2 } from "react/jsx-runtime";
687
722
  var MAX_ROWS_PX = 140;
688
723
  function Composer({ onSend, onStop, busy, placeholder, disabled, autoFocus, attachments, modelPicker }) {
689
724
  const [value, setValue] = useState3("");
@@ -745,46 +780,46 @@ function Composer({ onSend, onStop, busy, placeholder, disabled, autoFocus, atta
745
780
  });
746
781
  }, [busy, disabled, files, modelPicker?.value, onSend, value]);
747
782
  const onKeyDown = (event) => {
748
- if (event.key === "Enter" && !event.shiftKey) {
783
+ if (event.key === "Enter" && !event.shiftKey && !event.nativeEvent.isComposing) {
749
784
  event.preventDefault();
750
785
  submit();
751
786
  }
752
787
  };
753
788
  const nothingToSend = value.trim().length === 0 && files.length === 0;
754
- return /* @__PURE__ */ jsxs(
789
+ return /* @__PURE__ */ jsxs2(
755
790
  "form",
756
791
  {
757
- className: "flex flex-col gap-2",
792
+ className: "frankie-composer flex flex-col gap-2",
758
793
  onSubmit: (event) => {
759
794
  event.preventDefault();
760
795
  submit();
761
796
  },
762
797
  children: [
763
- files.length > 0 ? /* @__PURE__ */ jsx7("div", { className: "flex flex-wrap gap-2", children: files.map((file) => /* @__PURE__ */ jsxs(
798
+ files.length > 0 ? /* @__PURE__ */ jsx8("div", { className: "flex flex-wrap gap-2", children: files.map((file) => /* @__PURE__ */ jsxs2(
764
799
  "span",
765
800
  {
766
801
  className: "inline-flex max-w-[14rem] items-center gap-1.5 rounded-[var(--frankie-radius)] border border-border bg-muted px-2 py-1 text-xs",
767
802
  children: [
768
- /* @__PURE__ */ jsx7(Paperclip, { className: "size-3 shrink-0 opacity-70" }),
769
- /* @__PURE__ */ jsx7("span", { className: "truncate", children: file.name }),
770
- /* @__PURE__ */ jsx7(
803
+ /* @__PURE__ */ jsx8(Paperclip, { className: "size-3 shrink-0 opacity-70" }),
804
+ /* @__PURE__ */ jsx8("span", { className: "truncate", children: file.name }),
805
+ /* @__PURE__ */ jsx8(
771
806
  "button",
772
807
  {
773
808
  type: "button",
774
809
  onClick: () => removeFile(file.id),
775
810
  "aria-label": `Remove ${file.name}`,
776
811
  className: "frankie-focus shrink-0 opacity-60 hover:opacity-100",
777
- children: /* @__PURE__ */ jsx7(X, { className: "size-3" })
812
+ children: /* @__PURE__ */ jsx8(X, { className: "size-3" })
778
813
  }
779
814
  )
780
815
  ]
781
816
  },
782
817
  file.id
783
818
  )) }) : null,
784
- fileError ? /* @__PURE__ */ jsx7("p", { className: "text-xs text-red-600 dark:text-red-400", children: fileError }) : null,
785
- /* @__PURE__ */ jsxs("div", { className: "flex items-end gap-2", children: [
786
- canAttach ? /* @__PURE__ */ jsxs(Fragment, { children: [
787
- /* @__PURE__ */ jsx7(
819
+ fileError ? /* @__PURE__ */ jsx8("p", { className: "text-xs text-red-600 dark:text-red-400", children: fileError }) : null,
820
+ /* @__PURE__ */ jsxs2("div", { className: "frankie-composer-controls flex items-end gap-2", children: [
821
+ canAttach ? /* @__PURE__ */ jsxs2(Fragment, { children: [
822
+ /* @__PURE__ */ jsx8(
788
823
  "input",
789
824
  {
790
825
  ref: fileInputRef,
@@ -798,7 +833,7 @@ function Composer({ onSend, onStop, busy, placeholder, disabled, autoFocus, atta
798
833
  }
799
834
  }
800
835
  ),
801
- /* @__PURE__ */ jsx7(
836
+ /* @__PURE__ */ jsx8(
802
837
  Button,
803
838
  {
804
839
  type: "button",
@@ -808,11 +843,11 @@ function Composer({ onSend, onStop, busy, placeholder, disabled, autoFocus, atta
808
843
  onClick: () => fileInputRef.current?.click(),
809
844
  "aria-label": "Attach files",
810
845
  title: "Attach files",
811
- children: /* @__PURE__ */ jsx7(Paperclip, {})
846
+ children: /* @__PURE__ */ jsx8(Plus, {})
812
847
  }
813
848
  )
814
849
  ] }) : null,
815
- /* @__PURE__ */ jsx7(
850
+ /* @__PURE__ */ jsx8(
816
851
  Textarea,
817
852
  {
818
853
  ref: textareaRef,
@@ -826,7 +861,7 @@ function Composer({ onSend, onStop, busy, placeholder, disabled, autoFocus, atta
826
861
  "aria-label": "Message"
827
862
  }
828
863
  ),
829
- models.length > 0 ? /* @__PURE__ */ jsx7(
864
+ models.length > 0 ? /* @__PURE__ */ jsx8(
830
865
  "select",
831
866
  {
832
867
  value: modelPicker?.value ?? models[0]?.id,
@@ -834,10 +869,10 @@ function Composer({ onSend, onStop, busy, placeholder, disabled, autoFocus, atta
834
869
  disabled,
835
870
  "aria-label": "Model",
836
871
  className: "frankie-focus h-9 max-w-[8rem] rounded-[var(--frankie-radius)] border border-border bg-background px-2 text-sm",
837
- children: models.map((model) => /* @__PURE__ */ jsx7("option", { value: model.id, children: model.label }, model.id))
872
+ children: models.map((model) => /* @__PURE__ */ jsx8("option", { value: model.id, children: model.label }, model.id))
838
873
  }
839
874
  ) : null,
840
- busy ? /* @__PURE__ */ jsx7(Button, { type: "button", size: "icon", variant: "outline", onClick: onStop, "aria-label": "Stop", children: /* @__PURE__ */ jsx7(Square, {}) }) : /* @__PURE__ */ jsx7(Button, { type: "submit", size: "icon", disabled: disabled || nothingToSend, "aria-label": "Send", children: /* @__PURE__ */ jsx7(ArrowUp, {}) })
875
+ busy ? /* @__PURE__ */ jsx8(Button, { type: "button", size: "icon", variant: "outline", onClick: onStop, "aria-label": "Stop", children: /* @__PURE__ */ jsx8(Square, {}) }) : /* @__PURE__ */ jsx8(Button, { type: "submit", size: "icon", disabled: disabled || nothingToSend, "aria-label": "Send", children: /* @__PURE__ */ jsx8(ArrowUp, {}) })
841
876
  ] })
842
877
  ]
843
878
  }
@@ -867,9 +902,9 @@ import { useState as useState4 } from "react";
867
902
 
868
903
  // src/components/chat/Markdown.tsx
869
904
  import { Fragment as Fragment2 } from "react";
870
- import { jsx as jsx8 } from "react/jsx-runtime";
905
+ import { jsx as jsx9 } from "react/jsx-runtime";
871
906
  function Markdown({ text, className }) {
872
- return /* @__PURE__ */ jsx8("div", { className: cn("frankie-prose", className), children: renderBlocks(text) });
907
+ return /* @__PURE__ */ jsx9("div", { className: cn("frankie-prose", className), children: renderBlocks(text) });
873
908
  }
874
909
  function renderBlocks(text) {
875
910
  const lines = text.split("\n");
@@ -879,13 +914,13 @@ function renderBlocks(text) {
879
914
  let code = null;
880
915
  const flushParagraph = () => {
881
916
  if (paragraph.length === 0) return;
882
- blocks.push(/* @__PURE__ */ jsx8("p", { children: renderInline(paragraph.join(" ")) }, `p${blocks.length}`));
917
+ blocks.push(/* @__PURE__ */ jsx9("p", { children: renderInline(paragraph.join(" ")) }, `p${blocks.length}`));
883
918
  paragraph = [];
884
919
  };
885
920
  const flushList = () => {
886
921
  if (!list) return;
887
- const items = list.items.map((item, index) => /* @__PURE__ */ jsx8("li", { children: renderInline(item) }, index));
888
- blocks.push(list.ordered ? /* @__PURE__ */ jsx8("ol", { children: items }, `l${blocks.length}`) : /* @__PURE__ */ jsx8("ul", { children: items }, `l${blocks.length}`));
922
+ const items = list.items.map((item, index) => /* @__PURE__ */ jsx9("li", { children: renderInline(item) }, index));
923
+ blocks.push(list.ordered ? /* @__PURE__ */ jsx9("ol", { children: items }, `l${blocks.length}`) : /* @__PURE__ */ jsx9("ul", { children: items }, `l${blocks.length}`));
889
924
  list = null;
890
925
  };
891
926
  for (const line of lines) {
@@ -893,7 +928,7 @@ function renderBlocks(text) {
893
928
  if (fence) {
894
929
  if (code) {
895
930
  blocks.push(
896
- /* @__PURE__ */ jsx8("pre", { children: /* @__PURE__ */ jsx8("code", { children: code.lines.join("\n") }) }, `c${blocks.length}`)
931
+ /* @__PURE__ */ jsx9("pre", { children: /* @__PURE__ */ jsx9("code", { children: code.lines.join("\n") }) }, `c${blocks.length}`)
897
932
  );
898
933
  code = null;
899
934
  } else {
@@ -928,7 +963,7 @@ function renderBlocks(text) {
928
963
  if (heading) {
929
964
  flushParagraph();
930
965
  flushList();
931
- blocks.push(/* @__PURE__ */ jsx8("p", { className: "font-semibold", children: renderInline(heading[1] ?? "") }, `h${blocks.length}`));
966
+ blocks.push(/* @__PURE__ */ jsx9("p", { className: "font-semibold", children: renderInline(heading[1] ?? "") }, `h${blocks.length}`));
932
967
  continue;
933
968
  }
934
969
  flushList();
@@ -936,7 +971,7 @@ function renderBlocks(text) {
936
971
  }
937
972
  if (code) {
938
973
  blocks.push(
939
- /* @__PURE__ */ jsx8("pre", { children: /* @__PURE__ */ jsx8("code", { children: code.lines.join("\n") }) }, `c${blocks.length}`)
974
+ /* @__PURE__ */ jsx9("pre", { children: /* @__PURE__ */ jsx9("code", { children: code.lines.join("\n") }) }, `c${blocks.length}`)
940
975
  );
941
976
  }
942
977
  flushParagraph();
@@ -951,20 +986,20 @@ function renderInline(text) {
951
986
  const token = match[0];
952
987
  const start = match.index;
953
988
  if (start > lastIndex) nodes.push(text.slice(lastIndex, start));
954
- nodes.push(/* @__PURE__ */ jsx8(Fragment2, { children: renderToken(token) }, start));
989
+ nodes.push(/* @__PURE__ */ jsx9(Fragment2, { children: renderToken(token) }, start));
955
990
  lastIndex = start + token.length;
956
991
  }
957
992
  if (lastIndex < text.length) nodes.push(text.slice(lastIndex));
958
993
  return nodes;
959
994
  }
960
995
  function renderToken(token) {
961
- if (token.startsWith("**")) return /* @__PURE__ */ jsx8("strong", { children: token.slice(2, -2) });
962
- if (token.startsWith("`")) return /* @__PURE__ */ jsx8("code", { children: token.slice(1, -1) });
996
+ if (token.startsWith("**")) return /* @__PURE__ */ jsx9("strong", { children: token.slice(2, -2) });
997
+ if (token.startsWith("`")) return /* @__PURE__ */ jsx9("code", { children: token.slice(1, -1) });
963
998
  const link = /^\[([^\]]+)\]\(([^)]+)\)$/.exec(token);
964
999
  const href = link ? link[2] ?? "" : token;
965
1000
  const label = link ? link[1] ?? "" : token;
966
1001
  if (!/^(https?:\/\/|\/)/.test(href)) return token;
967
- return /* @__PURE__ */ jsx8("a", { href, target: "_blank", rel: "noopener noreferrer nofollow", children: label });
1002
+ return /* @__PURE__ */ jsx9("a", { href, target: "_blank", rel: "noopener noreferrer nofollow", children: label });
968
1003
  }
969
1004
 
970
1005
  // src/components/chat/ToolActivityList.tsx
@@ -972,7 +1007,7 @@ import { Check, ChevronDown, Loader2, ShieldAlert, X as X2 } from "lucide-react"
972
1007
 
973
1008
  // src/components/ui/badge.tsx
974
1009
  import { cva as cva2 } from "class-variance-authority";
975
- import { jsx as jsx9 } from "react/jsx-runtime";
1010
+ import { jsx as jsx10 } from "react/jsx-runtime";
976
1011
  var badgeVariants = cva2(
977
1012
  "inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-[0.6875rem] font-medium",
978
1013
  {
@@ -992,44 +1027,44 @@ function Badge({
992
1027
  variant,
993
1028
  ...props
994
1029
  }) {
995
- return /* @__PURE__ */ jsx9("span", { className: cn(badgeVariants({ variant }), className), ...props });
1030
+ return /* @__PURE__ */ jsx10("span", { className: cn(badgeVariants({ variant }), className), ...props });
996
1031
  }
997
1032
 
998
1033
  // src/components/chat/ToolActivityList.tsx
999
- import { jsx as jsx10, jsxs as jsxs2 } from "react/jsx-runtime";
1034
+ import { jsx as jsx11, jsxs as jsxs3 } from "react/jsx-runtime";
1000
1035
  function ToolActivityList({ tools, renderTool }) {
1001
1036
  if (tools.length === 0) return null;
1002
1037
  const running = tools.some((tool) => tool.status === "running");
1003
- return /* @__PURE__ */ jsxs2("details", { className: "frankie-tool-activity mt-2", open: running, children: [
1004
- /* @__PURE__ */ jsxs2("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: [
1005
- /* @__PURE__ */ jsx10(ChevronDown, { className: "frankie-tool-chevron size-3.5 transition-transform", "aria-hidden": "true" }),
1006
- /* @__PURE__ */ jsx10("span", { children: running ? "Working" : "Activity" }),
1007
- /* @__PURE__ */ jsxs2("span", { className: "text-[11px]", children: [
1038
+ return /* @__PURE__ */ jsxs3("details", { className: "frankie-tool-activity mt-2", open: running, children: [
1039
+ /* @__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: [
1040
+ /* @__PURE__ */ jsx11(ChevronDown, { className: "frankie-tool-chevron size-3.5 transition-transform", "aria-hidden": "true" }),
1041
+ /* @__PURE__ */ jsx11("span", { children: running ? "Working" : "Activity" }),
1042
+ /* @__PURE__ */ jsxs3("span", { className: "text-[11px]", children: [
1008
1043
  "\xB7 ",
1009
1044
  tools.length,
1010
1045
  " tool call",
1011
1046
  tools.length === 1 ? "" : "s"
1012
1047
  ] })
1013
1048
  ] }),
1014
- /* @__PURE__ */ jsx10("ul", { className: "mt-1 flex flex-col gap-1 border-l border-border pl-2", children: tools.map((tool) => /* @__PURE__ */ jsx10("li", { children: renderTool ? renderTool(tool) : /* @__PURE__ */ jsx10(ToolActivityRow, { tool }) }, tool.id)) })
1049
+ /* @__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)) })
1015
1050
  ] });
1016
1051
  }
1017
1052
  function ToolActivityRow({ tool }) {
1018
- return /* @__PURE__ */ jsxs2(Badge, { variant: badgeVariant(tool.status), title: tool.detail ?? tool.name, children: [
1019
- /* @__PURE__ */ jsx10(ToolIcon, { status: tool.status }),
1020
- /* @__PURE__ */ jsx10("span", { className: "truncate", children: tool.label })
1053
+ return /* @__PURE__ */ jsxs3(Badge, { variant: badgeVariant(tool.status), title: tool.detail ?? tool.name, children: [
1054
+ /* @__PURE__ */ jsx11(ToolIcon, { status: tool.status }),
1055
+ /* @__PURE__ */ jsx11("span", { className: "truncate", children: tool.label })
1021
1056
  ] });
1022
1057
  }
1023
1058
  function ToolIcon({ status }) {
1024
1059
  switch (status) {
1025
1060
  case "running":
1026
- return /* @__PURE__ */ jsx10(Loader2, { className: "size-3 animate-spin", "aria-hidden": "true" });
1061
+ return /* @__PURE__ */ jsx11(Loader2, { className: "size-3 animate-spin", "aria-hidden": "true" });
1027
1062
  case "done":
1028
- return /* @__PURE__ */ jsx10(Check, { className: "size-3", "aria-hidden": "true" });
1063
+ return /* @__PURE__ */ jsx11(Check, { className: "size-3", "aria-hidden": "true" });
1029
1064
  case "denied":
1030
- return /* @__PURE__ */ jsx10(ShieldAlert, { className: "size-3", "aria-hidden": "true" });
1065
+ return /* @__PURE__ */ jsx11(ShieldAlert, { className: "size-3", "aria-hidden": "true" });
1031
1066
  default:
1032
- return /* @__PURE__ */ jsx10(X2, { className: "size-3", "aria-hidden": "true" });
1067
+ return /* @__PURE__ */ jsx11(X2, { className: "size-3", "aria-hidden": "true" });
1033
1068
  }
1034
1069
  }
1035
1070
  function badgeVariant(status) {
@@ -1040,7 +1075,7 @@ function badgeVariant(status) {
1040
1075
  }
1041
1076
 
1042
1077
  // src/components/chat/MessageBubble.tsx
1043
- import { jsx as jsx11, jsxs as jsxs3 } from "react/jsx-runtime";
1078
+ import { jsx as jsx12, jsxs as jsxs4 } from "react/jsx-runtime";
1044
1079
  function MessageBubble({
1045
1080
  message,
1046
1081
  assistantName,
@@ -1049,33 +1084,33 @@ function MessageBubble({
1049
1084
  renderTool
1050
1085
  }) {
1051
1086
  if (message.role === "user") {
1052
- return /* @__PURE__ */ jsxs3("div", { className: "flex flex-col items-end gap-1", children: [
1053
- message.attachments?.length ? /* @__PURE__ */ jsx11("div", { className: "flex max-w-[85%] flex-wrap justify-end gap-1", children: message.attachments.map((file, index) => /* @__PURE__ */ jsxs3(
1087
+ return /* @__PURE__ */ jsxs4("div", { className: "frankie-user-message flex flex-col items-end gap-1", children: [
1088
+ 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(
1054
1089
  "span",
1055
1090
  {
1056
1091
  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",
1057
1092
  children: [
1058
- /* @__PURE__ */ jsx11(Paperclip2, { className: "size-3 shrink-0 opacity-70" }),
1059
- /* @__PURE__ */ jsx11("span", { className: "truncate", children: file.name })
1093
+ /* @__PURE__ */ jsx12(Paperclip2, { className: "size-3 shrink-0 opacity-70" }),
1094
+ /* @__PURE__ */ jsx12("span", { className: "truncate", children: file.name })
1060
1095
  ]
1061
1096
  },
1062
1097
  `${file.name}-${index}`
1063
1098
  )) }) : null,
1064
- message.text ? /* @__PURE__ */ jsxs3("div", { className: "flex max-w-[85%] items-end gap-1", children: [
1065
- /* @__PURE__ */ jsx11("div", { className: "rounded-[var(--frankie-radius)] bg-primary px-3 py-2 text-sm text-primary-foreground", children: message.text }),
1066
- /* @__PURE__ */ jsx11(MessageCopyButton, { text: message.text })
1099
+ message.text ? /* @__PURE__ */ jsxs4("div", { className: "flex max-w-[85%] items-end gap-1", children: [
1100
+ /* @__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 }),
1101
+ /* @__PURE__ */ jsx12(MessageCopyButton, { text: message.text })
1067
1102
  ] }) : null
1068
1103
  ] });
1069
1104
  }
1070
1105
  const empty = message.text.length === 0 && message.tools.length === 0;
1071
- return /* @__PURE__ */ jsxs3("div", { className: "flex gap-2", children: [
1072
- /* @__PURE__ */ jsxs3("div", { className: "frankie-assistant-actions mt-0.5 flex shrink-0 flex-col items-center gap-1", children: [
1073
- /* @__PURE__ */ jsx11(Avatar, { ...avatarUrl ? { src: avatarUrl } : {}, name: assistantName }),
1074
- message.text ? /* @__PURE__ */ jsx11(MessageCopyButton, { text: message.text }) : null
1106
+ return /* @__PURE__ */ jsxs4("div", { className: "frankie-assistant-message flex gap-2", children: [
1107
+ /* @__PURE__ */ jsxs4("div", { className: "frankie-assistant-actions mt-0.5 flex shrink-0 flex-col items-center gap-1", children: [
1108
+ /* @__PURE__ */ jsx12(Avatar, { ...avatarUrl ? { src: avatarUrl } : {}, name: assistantName }),
1109
+ message.text ? /* @__PURE__ */ jsx12(MessageCopyButton, { text: message.text }) : null
1075
1110
  ] }),
1076
- /* @__PURE__ */ jsxs3("div", { className: "min-w-0 max-w-[85%]", children: [
1077
- message.status === "error" ? /* @__PURE__ */ jsx11("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__ */ jsx11(TypingDots, {}) : /* @__PURE__ */ jsx11(Markdown, { text: message.text, className: "text-sm" }),
1078
- showToolActivity ? /* @__PURE__ */ jsx11(ToolActivityList, { tools: message.tools, ...renderTool ? { renderTool } : {} }) : null
1111
+ /* @__PURE__ */ jsxs4("div", { className: "frankie-assistant-content min-w-0 max-w-[85%]", children: [
1112
+ 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" }),
1113
+ showToolActivity ? /* @__PURE__ */ jsx12(ToolActivityList, { tools: message.tools, ...renderTool ? { renderTool } : {} }) : null
1079
1114
  ] })
1080
1115
  ] });
1081
1116
  }
@@ -1089,28 +1124,31 @@ function MessageCopyButton({ text }) {
1089
1124
  } catch {
1090
1125
  }
1091
1126
  };
1092
- return /* @__PURE__ */ jsx11("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__ */ jsx11(Check2, { className: "size-3.5", "aria-hidden": "true" }) : /* @__PURE__ */ jsx11(Copy, { className: "size-3.5", "aria-hidden": "true" }) });
1127
+ 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" }) });
1093
1128
  }
1094
1129
  function TypingDots() {
1095
- return /* @__PURE__ */ jsx11("span", { className: "inline-flex items-center gap-1 py-2", role: "status", "aria-label": "Thinking", children: [0, 1, 2].map((index) => /* @__PURE__ */ jsx11(
1096
- "span",
1097
- {
1098
- className: cn("size-1.5 animate-pulse rounded-full bg-muted-foreground"),
1099
- style: { animationDelay: `${index * 150}ms` }
1100
- },
1101
- index
1102
- )) });
1130
+ return /* @__PURE__ */ jsxs4("span", { className: "frankie-thinking inline-flex items-center gap-1 py-2", role: "status", "aria-label": "Thinking", children: [
1131
+ /* @__PURE__ */ jsx12("span", { className: "frankie-thinking-label", children: "Thinking" }),
1132
+ [0, 1, 2].map((index) => /* @__PURE__ */ jsx12(
1133
+ "span",
1134
+ {
1135
+ className: cn("size-1.5 animate-pulse rounded-full bg-muted-foreground"),
1136
+ style: { animationDelay: `${index * 150}ms` }
1137
+ },
1138
+ index
1139
+ ))
1140
+ ] });
1103
1141
  }
1104
1142
 
1105
1143
  // src/components/chat/SuggestedPrompts.tsx
1106
- import { jsx as jsx12 } from "react/jsx-runtime";
1144
+ import { jsx as jsx13 } from "react/jsx-runtime";
1107
1145
  function SuggestedPrompts({
1108
1146
  prompts,
1109
1147
  onPick,
1110
1148
  disabled
1111
1149
  }) {
1112
1150
  if (prompts.length === 0) return null;
1113
- return /* @__PURE__ */ jsx12("div", { className: "flex flex-wrap gap-2", children: prompts.map((prompt) => /* @__PURE__ */ jsx12(
1151
+ return /* @__PURE__ */ jsx13("div", { className: "flex flex-wrap gap-2", children: prompts.map((prompt) => /* @__PURE__ */ jsx13(
1114
1152
  Button,
1115
1153
  {
1116
1154
  type: "button",
@@ -1125,7 +1163,7 @@ function SuggestedPrompts({
1125
1163
  }
1126
1164
 
1127
1165
  // src/components/chat/ChatPanel.tsx
1128
- import { jsx as jsx13, jsxs as jsxs4 } from "react/jsx-runtime";
1166
+ import { jsx as jsx14, jsxs as jsxs5 } from "react/jsx-runtime";
1129
1167
  function ChatPanel({
1130
1168
  className,
1131
1169
  header,
@@ -1140,7 +1178,7 @@ function ChatPanel({
1140
1178
  }) {
1141
1179
  const { config, configError, loading } = useFrankie();
1142
1180
  const theme = useFrankieTheme();
1143
- const { messages, busy, restoring, loadedSkills, error, send, stop, reset } = useFrankieChat();
1181
+ const { messages, busy, restoring, error, send, stop, reset } = useFrankieChat();
1144
1182
  const scrollRef = useRef4(null);
1145
1183
  const features = config?.features;
1146
1184
  const pickerModels = features?.modelPicker.enabled ? features.modelPicker.models : [];
@@ -1153,11 +1191,12 @@ function ChatPanel({
1153
1191
  }, [messages]);
1154
1192
  const name = config?.branding.name ?? "Assistant";
1155
1193
  const mode = resolveMode(theme?.mode);
1156
- return /* @__PURE__ */ jsxs4(
1194
+ return /* @__PURE__ */ jsxs5(
1157
1195
  Card,
1158
1196
  {
1159
1197
  className: cn(
1160
- "frankie-root",
1198
+ "frankie-root frankie-chat-panel",
1199
+ fill && "frankie-chat-panel-fill",
1161
1200
  fill ? "h-full w-full rounded-none border-0 shadow-none" : "h-[32rem] w-[22rem] max-w-[calc(100vw-2rem)]",
1162
1201
  mode === "dark" && "dark",
1163
1202
  className
@@ -1165,24 +1204,25 @@ function ChatPanel({
1165
1204
  style: themeToCssVars(theme),
1166
1205
  "data-frankie-mode": mode,
1167
1206
  children: [
1168
- header === false ? null : header !== void 0 ? header : /* @__PURE__ */ jsxs4(CardHeader, { children: [
1169
- /* @__PURE__ */ jsx13(Avatar, { ...config?.branding.avatarUrl ? { src: config.branding.avatarUrl } : {}, name }),
1170
- /* @__PURE__ */ jsxs4("div", { className: "min-w-0 flex-1", children: [
1171
- /* @__PURE__ */ jsx13(CardTitle, { children: name }),
1172
- config?.branding.tagline ? /* @__PURE__ */ jsx13(CardDescription, { children: config.branding.tagline }) : null
1207
+ header === false ? null : header !== void 0 ? header : /* @__PURE__ */ jsxs5(CardHeader, { children: [
1208
+ /* @__PURE__ */ jsx14(Avatar, { ...config?.branding.avatarUrl ? { src: config.branding.avatarUrl } : {}, name }),
1209
+ /* @__PURE__ */ jsxs5("div", { className: "min-w-0 flex-1", children: [
1210
+ /* @__PURE__ */ jsx14(CardTitle, { children: name }),
1211
+ config?.branding.tagline ? /* @__PURE__ */ jsx14(CardDescription, { children: config.branding.tagline }) : null
1173
1212
  ] }),
1174
- /* @__PURE__ */ jsx13(Button, { size: "icon", variant: "ghost", onClick: reset, "aria-label": "Start a new conversation", title: "New conversation", children: /* @__PURE__ */ jsx13(RotateCcw, {}) }),
1175
- onClose ? /* @__PURE__ */ jsx13(Button, { size: "icon", variant: "ghost", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ jsx13(X3, {}) }) : null
1213
+ /* @__PURE__ */ jsx14(Button, { size: "icon", variant: "ghost", onClick: reset, "aria-label": "Start a new conversation", title: "New conversation", children: /* @__PURE__ */ jsx14(RotateCcw, {}) }),
1214
+ onClose ? /* @__PURE__ */ jsx14(Button, { size: "icon", variant: "ghost", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ jsx14(X3, {}) }) : null
1176
1215
  ] }),
1177
- /* @__PURE__ */ jsx13(CardContent, { children: /* @__PURE__ */ jsx13(ScrollArea, { ref: scrollRef, className: "flex h-full flex-col gap-4 p-4", "aria-live": "polite", children: configError ? /* @__PURE__ */ jsxs4("div", { className: "frankie-config-error", role: "alert", children: [
1178
- /* @__PURE__ */ jsx13("strong", { children: "Frankie couldn\u2019t start" }),
1179
- /* @__PURE__ */ jsx13("p", { children: configError }),
1180
- /* @__PURE__ */ jsx13("p", { children: "Refresh this page after checking the workspace\u2019s allowed origin and sign-in settings." })
1181
- ] }) : loading ? /* @__PURE__ */ jsx13("p", { className: "text-sm text-muted-foreground", children: "Starting Frankie\u2026" }) : messages.length === 0 ? emptyState ?? /* @__PURE__ */ jsxs4("div", { className: "flex flex-col gap-4", children: [
1182
- /* @__PURE__ */ jsx13("p", { className: "text-sm text-muted-foreground", children: config?.greeting }),
1183
- /* @__PURE__ */ jsx13(SuggestedPrompts, { prompts: config?.suggestedPrompts ?? [], onPick: send, disabled: busy })
1184
- ] }) : /* @__PURE__ */ jsx13("div", { className: "flex flex-col gap-4", children: messages.map(
1185
- (message) => renderMessage ? /* @__PURE__ */ jsx13("div", { children: renderMessage(message) }, message.id) : /* @__PURE__ */ jsx13(
1216
+ /* @__PURE__ */ jsx14(AgentContext, {}),
1217
+ /* @__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: [
1218
+ /* @__PURE__ */ jsx14("strong", { children: "Frankie couldn\u2019t start" }),
1219
+ /* @__PURE__ */ jsx14("p", { children: configError }),
1220
+ /* @__PURE__ */ jsx14("p", { children: "Refresh this page after checking the workspace\u2019s allowed origin and sign-in settings." })
1221
+ ] }) : 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: [
1222
+ /* @__PURE__ */ jsx14("p", { className: "text-sm text-muted-foreground", children: config?.greeting }),
1223
+ /* @__PURE__ */ jsx14(SuggestedPrompts, { prompts: config?.suggestedPrompts ?? [], onPick: send, disabled: busy })
1224
+ ] }) : /* @__PURE__ */ jsx14("div", { className: "frankie-message-list flex flex-col gap-4", children: messages.map(
1225
+ (message) => renderMessage ? /* @__PURE__ */ jsx14("div", { children: renderMessage(message) }, message.id) : /* @__PURE__ */ jsx14(
1186
1226
  MessageBubble,
1187
1227
  {
1188
1228
  message,
@@ -1194,19 +1234,9 @@ function ChatPanel({
1194
1234
  message.id
1195
1235
  )
1196
1236
  ) }) }) }),
1197
- /* @__PURE__ */ jsxs4(CardFooter, { className: "flex flex-col gap-2", children: [
1198
- loadedSkills !== null ? /* @__PURE__ */ jsxs4("details", { className: "frankie-loaded-skills", children: [
1199
- /* @__PURE__ */ jsx13("summary", { role: "status", children: loadedSkills.length ? `${loadedSkills.length} ${loadedSkills.length === 1 ? "skill" : "skills"} loaded` : "No skills loaded for this turn" }),
1200
- loadedSkills.length ? /* @__PURE__ */ jsx13("ul", { children: loadedSkills.map((skill) => /* @__PURE__ */ jsxs4("li", { children: [
1201
- skill.name,
1202
- /* @__PURE__ */ jsxs4("small", { children: [
1203
- "v",
1204
- skill.revision
1205
- ] })
1206
- ] }, skill.id)) }) : /* @__PURE__ */ jsx13("p", { children: "Your assistant can still answer using its available tools." })
1207
- ] }) : null,
1208
- error ? /* @__PURE__ */ jsx13("p", { className: "w-full text-xs text-red-600 dark:text-red-400", children: error }) : null,
1209
- /* @__PURE__ */ jsx13("div", { className: "w-full", children: /* @__PURE__ */ jsx13(
1237
+ /* @__PURE__ */ jsxs5(CardFooter, { className: "frankie-composer-footer flex flex-col gap-2", children: [
1238
+ error ? /* @__PURE__ */ jsx14("p", { className: "w-full text-xs text-red-600 dark:text-red-400", children: error }) : null,
1239
+ /* @__PURE__ */ jsx14("div", { className: "w-full", children: /* @__PURE__ */ jsx14(
1210
1240
  Composer,
1211
1241
  {
1212
1242
  onSend: send,
@@ -1227,7 +1257,7 @@ function ChatPanel({
1227
1257
  }
1228
1258
 
1229
1259
  // src/components/chat/FrankieWidget.tsx
1230
- import { jsx as jsx14, jsxs as jsxs5 } from "react/jsx-runtime";
1260
+ import { jsx as jsx15, jsxs as jsxs6 } from "react/jsx-runtime";
1231
1261
  function FrankieWidget({
1232
1262
  position = "bottom-right",
1233
1263
  offset = 24,
@@ -1257,15 +1287,15 @@ function FrankieWidget({
1257
1287
  const label = config?.branding.launcherLabel ?? "Need a hand?";
1258
1288
  const mode = resolveMode(theme?.mode);
1259
1289
  const side = position === "bottom-right" ? { right: offset } : { left: offset };
1260
- return /* @__PURE__ */ jsx14(
1290
+ return /* @__PURE__ */ jsx15(
1261
1291
  "div",
1262
1292
  {
1263
1293
  className: cn("frankie-root frankie-widget", mode === "dark" && "dark"),
1264
1294
  style: { ...themeToCssVars(theme), position: "fixed", bottom: offset, ...side, zIndex, pointerEvents: "none" },
1265
1295
  "data-frankie-mode": mode,
1266
- children: /* @__PURE__ */ jsxs5("div", { className: cn("flex flex-col gap-3", position === "bottom-left" ? "items-start" : "items-end"), children: [
1267
- open ? /* @__PURE__ */ jsx14("div", { style: { pointerEvents: "auto", maxHeight: "calc(100dvh - 100px)", overflow: "hidden" }, children: /* @__PURE__ */ jsx14(ChatPanel, { ...panelProps, className: cn("frankie-widget-panel", panelProps.className), onClose: () => setOpen(false), autoFocus: true }) }) : null,
1268
- /* @__PURE__ */ jsx14("div", { style: { pointerEvents: "auto" }, children: renderLauncher ? renderLauncher({ open, toggle: () => setOpen(!open), label }) : /* @__PURE__ */ jsx14(
1296
+ children: /* @__PURE__ */ jsxs6("div", { className: cn("flex flex-col gap-3", position === "bottom-left" ? "items-start" : "items-end"), children: [
1297
+ 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,
1298
+ /* @__PURE__ */ jsx15("div", { style: { pointerEvents: "auto" }, children: renderLauncher ? renderLauncher({ open, toggle: () => setOpen(!open), label }) : /* @__PURE__ */ jsx15(
1269
1299
  Button,
1270
1300
  {
1271
1301
  size: "icon",
@@ -1274,7 +1304,7 @@ function FrankieWidget({
1274
1304
  "aria-expanded": open,
1275
1305
  "aria-label": open ? "Close the assistant" : label,
1276
1306
  title: label,
1277
- children: open ? /* @__PURE__ */ jsx14(X4, { className: "size-5" }) : /* @__PURE__ */ jsx14(MessageCircle, { className: "size-5" })
1307
+ children: open ? /* @__PURE__ */ jsx15(X4, { className: "size-5" }) : /* @__PURE__ */ jsx15(MessageCircle, { className: "size-5" })
1278
1308
  }
1279
1309
  ) })
1280
1310
  ] })
@@ -1283,45 +1313,45 @@ function FrankieWidget({
1283
1313
  }
1284
1314
 
1285
1315
  // src/components/chat/FrankieInline.tsx
1286
- import { jsx as jsx15 } from "react/jsx-runtime";
1316
+ import { jsx as jsx16 } from "react/jsx-runtime";
1287
1317
  function FrankieInline({ className, style, ...panelProps }) {
1288
1318
  const theme = useFrankieTheme();
1289
1319
  const mode = resolveMode(theme?.mode);
1290
- return /* @__PURE__ */ jsx15(
1320
+ return /* @__PURE__ */ jsx16(
1291
1321
  "div",
1292
1322
  {
1293
1323
  className: cn("frankie-root frankie-inline", mode === "dark" && "dark", className),
1294
1324
  style: { ...themeToCssVars(theme), display: "flex", height: "100%", width: "100%", ...style },
1295
1325
  "data-frankie-mode": mode,
1296
- children: /* @__PURE__ */ jsx15(ChatPanel, { ...panelProps, fill: true })
1326
+ children: /* @__PURE__ */ jsx16(ChatPanel, { ...panelProps, fill: true })
1297
1327
  }
1298
1328
  );
1299
1329
  }
1300
1330
 
1301
1331
  // src/components/chat/FrankieChatPage.tsx
1302
1332
  import { useCallback as useCallback3, useEffect as useEffect6, useRef as useRef5, useState as useState7 } from "react";
1303
- import { History, Menu, Plus, Settings, X as X5 } from "lucide-react";
1304
- import { Fragment as Fragment3, jsx as jsx16, jsxs as jsxs6 } from "react/jsx-runtime";
1333
+ import { History, Menu, Plus as Plus2, Settings, X as X5 } from "lucide-react";
1334
+ import { Fragment as Fragment3, jsx as jsx17, jsxs as jsxs7 } from "react/jsx-runtime";
1305
1335
  function PageEmptyState() {
1306
1336
  const { config } = useFrankie();
1307
1337
  const { send, busy, restoring } = useFrankieChat();
1308
1338
  const name = config?.branding.name ?? "Frankie";
1309
- return /* @__PURE__ */ jsxs6("section", { className: "frankie-page-empty", "aria-label": "Start a conversation", children: [
1310
- /* @__PURE__ */ jsx16("span", { className: "frankie-page-avatar", "aria-hidden": true, children: name.trim().slice(0, 1).toUpperCase() }),
1311
- /* @__PURE__ */ jsxs6("div", { children: [
1312
- /* @__PURE__ */ jsx16("p", { className: "frankie-page-kicker", children: "Your product assistant" }),
1313
- /* @__PURE__ */ jsx16("h1", { children: "How can I help?" }),
1314
- /* @__PURE__ */ jsx16("p", { children: config?.greeting ?? "Ask a question or choose a task to get started." })
1339
+ return /* @__PURE__ */ jsxs7("section", { className: "frankie-page-empty", "aria-label": "Start a conversation", children: [
1340
+ /* @__PURE__ */ jsx17("span", { className: "frankie-page-avatar", "aria-hidden": true, children: name.trim().slice(0, 1).toUpperCase() }),
1341
+ /* @__PURE__ */ jsxs7("div", { children: [
1342
+ /* @__PURE__ */ jsx17("p", { className: "frankie-page-kicker", children: "Your product assistant" }),
1343
+ /* @__PURE__ */ jsx17("h1", { children: "How can I help?" }),
1344
+ /* @__PURE__ */ jsx17("p", { children: config?.greeting ?? "Ask a question or choose a task to get started." })
1315
1345
  ] }),
1316
- /* @__PURE__ */ jsx16("div", { className: "frankie-page-prompts", children: config?.suggestedPrompts?.slice(0, 3).map((prompt) => /* @__PURE__ */ jsxs6("button", { type: "button", disabled: busy || restoring, onClick: () => void send(prompt), children: [
1317
- /* @__PURE__ */ jsx16("strong", { children: prompt }),
1318
- /* @__PURE__ */ jsx16("b", { "aria-hidden": true, children: "\u2197" })
1346
+ /* @__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: [
1347
+ /* @__PURE__ */ jsx17("strong", { children: prompt }),
1348
+ /* @__PURE__ */ jsx17("b", { "aria-hidden": true, children: "\u2197" })
1319
1349
  ] }, prompt)) })
1320
1350
  ] });
1321
1351
  }
1322
1352
  function PageChrome({ showHistory, navigation, currentId, onSelect, onNew, ...panel }) {
1323
1353
  const { client, config } = useFrankie();
1324
- const { busy } = useFrankieChat();
1354
+ const { busy, messages } = useFrankieChat();
1325
1355
  const theme = useFrankieTheme();
1326
1356
  const [conversations, setConversations] = useState7([]);
1327
1357
  const [historyError, setHistoryError] = useState7("");
@@ -1331,7 +1361,6 @@ function PageChrome({ showHistory, navigation, currentId, onSelect, onNew, ...pa
1331
1361
  const dialog = useRef5(null);
1332
1362
  const menu = useRef5(null);
1333
1363
  const requestVersion = useRef5(0);
1334
- const name = config?.branding.name ?? "Frankie";
1335
1364
  const refresh = useCallback3(async () => {
1336
1365
  if (!showHistory || !config || config.allowAnonymous) {
1337
1366
  setHistoryError("");
@@ -1373,77 +1402,76 @@ function PageChrome({ showHistory, navigation, currentId, onSelect, onNew, ...pa
1373
1402
  close();
1374
1403
  }
1375
1404
  };
1376
- const history = /* @__PURE__ */ jsxs6(Fragment3, { children: [
1377
- /* @__PURE__ */ jsxs6("div", { className: "frankie-history-heading", children: [
1378
- /* @__PURE__ */ jsx16("strong", { children: "Conversations" }),
1379
- /* @__PURE__ */ jsx16("button", { type: "button", className: "frankie-mobile-only", "aria-label": "Close conversations", onClick: close, children: /* @__PURE__ */ jsx16(X5, { size: 18 }) })
1405
+ const history = /* @__PURE__ */ jsxs7(Fragment3, { children: [
1406
+ /* @__PURE__ */ jsxs7("div", { className: "frankie-history-heading", children: [
1407
+ /* @__PURE__ */ jsx17("strong", { children: "Conversations" }),
1408
+ /* @__PURE__ */ jsx17("button", { type: "button", className: "frankie-mobile-only", "aria-label": "Close conversations", onClick: close, children: /* @__PURE__ */ jsx17(X5, { size: 18 }) })
1380
1409
  ] }),
1381
- /* @__PURE__ */ jsxs6("button", { type: "button", className: "frankie-new-chat", disabled: busy, onClick: () => {
1410
+ /* @__PURE__ */ jsxs7("button", { type: "button", className: "frankie-new-chat", disabled: busy, onClick: () => {
1382
1411
  onNew();
1383
1412
  close();
1384
1413
  }, children: [
1385
- /* @__PURE__ */ jsx16(Plus, { size: 17 }),
1414
+ /* @__PURE__ */ jsx17(Plus2, { size: 17 }),
1386
1415
  "New chat"
1387
1416
  ] }),
1388
- /* @__PURE__ */ jsxs6("label", { className: "frankie-history-search", children: [
1389
- /* @__PURE__ */ jsx16("span", { children: "Search conversations" }),
1390
- /* @__PURE__ */ jsx16("input", { type: "search", placeholder: "Search chats\u2026", value: query, onChange: (event) => setQuery(event.target.value) })
1417
+ /* @__PURE__ */ jsxs7("label", { className: "frankie-history-search", children: [
1418
+ /* @__PURE__ */ jsx17("span", { children: "Search conversations" }),
1419
+ /* @__PURE__ */ jsx17("input", { type: "search", placeholder: "Search chats\u2026", value: query, onChange: (event) => setQuery(event.target.value) })
1391
1420
  ] }),
1392
- /* @__PURE__ */ jsx16("nav", { className: "frankie-history-list", "aria-label": "Conversations", children: conversations.filter((item) => item.title.toLowerCase().includes(query.toLowerCase())).map((item) => /* @__PURE__ */ jsxs6("button", { type: "button", "aria-current": item.id === currentId ? "page" : void 0, disabled: busy, onClick: () => select(item.id), children: [
1393
- /* @__PURE__ */ jsx16("span", { children: item.title }),
1394
- /* @__PURE__ */ jsx16("small", { children: new Date(item.updatedAt).toLocaleDateString() })
1421
+ /* @__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: [
1422
+ /* @__PURE__ */ jsx17("span", { children: item.title }),
1423
+ /* @__PURE__ */ jsx17("small", { children: new Date(item.updatedAt).toLocaleDateString() })
1395
1424
  ] }, item.id)) }),
1396
- historyLoading ? /* @__PURE__ */ jsx16("p", { role: "status", children: "Loading history\u2026" }) : historyError ? /* @__PURE__ */ jsxs6("p", { role: "status", children: [
1425
+ historyLoading ? /* @__PURE__ */ jsx17("p", { role: "status", children: "Loading history\u2026" }) : historyError ? /* @__PURE__ */ jsxs7("p", { role: "status", children: [
1397
1426
  historyError,
1398
- /* @__PURE__ */ jsx16("button", { type: "button", onClick: () => void refresh(), children: "Try again" })
1399
- ] }) : !conversations.length ? /* @__PURE__ */ jsx16("p", { children: config?.allowAnonymous ? "Sign in to sync conversations across devices." : "Your conversations will appear here." }) : null,
1400
- navigation ? /* @__PURE__ */ jsx16("div", { className: "frankie-host-navigation", children: navigation }) : null
1427
+ /* @__PURE__ */ jsx17("button", { type: "button", onClick: () => void refresh(), children: "Try again" })
1428
+ ] }) : !conversations.length ? /* @__PURE__ */ jsx17("p", { children: config?.allowAnonymous ? "Sign in to sync conversations across devices." : "Your conversations will appear here." }) : null,
1429
+ navigation ? /* @__PURE__ */ jsx17("div", { className: "frankie-host-navigation", children: navigation }) : null
1401
1430
  ] });
1402
- return /* @__PURE__ */ jsxs6("div", { className: "frankie-page-shell" + (resolveMode(theme?.mode) === "dark" ? " dark" : ""), style: themeToCssVars(theme), children: [
1403
- showHistory ? /* @__PURE__ */ jsx16("aside", { className: "frankie-history-desktop", children: history }) : null,
1404
- /* @__PURE__ */ jsxs6("div", { className: "frankie-page-main", children: [
1405
- /* @__PURE__ */ jsxs6("header", { className: "frankie-page-header", children: [
1406
- /* @__PURE__ */ jsxs6("div", { children: [
1407
- showHistory ? /* @__PURE__ */ jsx16("button", { type: "button", className: "frankie-mobile-only", ref: menu, "aria-label": "Open conversations", "aria-haspopup": "dialog", "aria-expanded": drawerOpen, onClick: () => setDrawerOpen(true), children: /* @__PURE__ */ jsx16(Menu, { size: 20 }) }) : null,
1408
- /* @__PURE__ */ jsx16("strong", { children: name }),
1409
- /* @__PURE__ */ jsx16("span", { className: "frankie-header-caption", children: "Product assistant" })
1431
+ return /* @__PURE__ */ jsxs7("div", { className: "frankie-page-shell" + (resolveMode(theme?.mode) === "dark" ? " dark" : ""), style: themeToCssVars(theme), children: [
1432
+ showHistory ? /* @__PURE__ */ jsx17("aside", { className: "frankie-history-desktop", children: history }) : null,
1433
+ /* @__PURE__ */ jsxs7("div", { className: "frankie-page-main", children: [
1434
+ /* @__PURE__ */ jsxs7("header", { className: "frankie-page-header", children: [
1435
+ /* @__PURE__ */ jsxs7("div", { children: [
1436
+ 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,
1437
+ /* @__PURE__ */ jsx17("strong", { className: "frankie-thread-title", children: conversations.find((item) => item.id === currentId)?.title ?? messages.find((message) => message.role === "user")?.text ?? "New chat" })
1410
1438
  ] }),
1411
- /* @__PURE__ */ jsxs6("div", { children: [
1412
- /* @__PURE__ */ jsxs6("button", { type: "button", disabled: busy, onClick: onNew, "aria-label": "Start a new conversation", children: [
1413
- /* @__PURE__ */ jsx16(Plus, { size: 16 }),
1414
- /* @__PURE__ */ jsx16("span", { children: "New chat" })
1439
+ /* @__PURE__ */ jsxs7("div", { children: [
1440
+ /* @__PURE__ */ jsxs7("button", { type: "button", disabled: busy, onClick: onNew, "aria-label": "Start a new conversation", children: [
1441
+ /* @__PURE__ */ jsx17(Plus2, { size: 16 }),
1442
+ /* @__PURE__ */ jsx17("span", { children: "New chat" })
1415
1443
  ] }),
1416
- /* @__PURE__ */ jsxs6("details", { className: "frankie-chat-settings", children: [
1417
- /* @__PURE__ */ jsx16("summary", { "aria-label": "Chat settings", children: /* @__PURE__ */ jsx16(Settings, { size: 19 }) }),
1418
- /* @__PURE__ */ jsxs6("div", { children: [
1419
- /* @__PURE__ */ jsx16("strong", { children: "Chat settings" }),
1420
- /* @__PURE__ */ jsx16("p", { children: "History syncs automatically for signed-in users." }),
1421
- /* @__PURE__ */ jsxs6("button", { type: "button", onClick: () => void refresh(), disabled: historyLoading || !showHistory, children: [
1422
- /* @__PURE__ */ jsx16(History, { size: 16 }),
1444
+ /* @__PURE__ */ jsxs7("details", { className: "frankie-chat-settings", children: [
1445
+ /* @__PURE__ */ jsx17("summary", { "aria-label": "Chat settings", children: /* @__PURE__ */ jsx17(Settings, { size: 19 }) }),
1446
+ /* @__PURE__ */ jsxs7("div", { children: [
1447
+ /* @__PURE__ */ jsx17("strong", { children: "Chat settings" }),
1448
+ /* @__PURE__ */ jsx17("p", { children: "History syncs automatically for signed-in users." }),
1449
+ /* @__PURE__ */ jsxs7("button", { type: "button", onClick: () => void refresh(), disabled: historyLoading || !showHistory, children: [
1450
+ /* @__PURE__ */ jsx17(History, { size: 16 }),
1423
1451
  "Refresh history"
1424
1452
  ] }),
1425
- /* @__PURE__ */ jsx16("small", { children: "Skills are checked at the start of each message." })
1453
+ /* @__PURE__ */ jsx17("small", { children: "Skills are checked at the start of each message." })
1426
1454
  ] })
1427
1455
  ] })
1428
1456
  ] })
1429
1457
  ] }),
1430
- /* @__PURE__ */ jsx16("div", { className: "frankie-page-canvas", children: /* @__PURE__ */ jsx16(FrankieInline, { ...panel, header: panel.header ?? false, emptyState: panel.emptyState ?? /* @__PURE__ */ jsx16(PageEmptyState, {}) }) })
1458
+ /* @__PURE__ */ jsx17("div", { className: "frankie-page-canvas", children: /* @__PURE__ */ jsx17(FrankieInline, { ...panel, header: panel.header ?? false, emptyState: panel.emptyState ?? /* @__PURE__ */ jsx17(PageEmptyState, {}) }) })
1431
1459
  ] }),
1432
- showHistory ? /* @__PURE__ */ jsx16("dialog", { ref: dialog, className: "frankie-history-dialog frankie-root", "aria-label": "Chat conversations", style: themeToCssVars(theme), onCancel: close, onClick: (event) => {
1460
+ showHistory ? /* @__PURE__ */ jsx17("dialog", { ref: dialog, className: "frankie-history-dialog frankie-root", "aria-label": "Chat conversations", style: themeToCssVars(theme), onCancel: close, onClick: (event) => {
1433
1461
  if (event.target === event.currentTarget) close();
1434
- }, children: /* @__PURE__ */ jsx16("aside", { children: history }) }) : null
1462
+ }, children: /* @__PURE__ */ jsx17("aside", { children: history }) }) : null
1435
1463
  ] });
1436
1464
  }
1437
1465
  function FrankieChatPage({ className, style, header, footer, emptyState, renderMessage, renderTool, placeholder, autoFocus, showHistory = true, navigation, ...provider }) {
1438
1466
  const [id, setId] = useState7(() => provider.conversationId ?? crypto.randomUUID());
1439
1467
  const onNew = () => setId(crypto.randomUUID());
1440
- return /* @__PURE__ */ jsx16("div", { className: ["frankie-root", "frankie-chat-page", className].filter(Boolean).join(" "), style: { height: "100%", minHeight: "min(36rem, 100dvh)", width: "100%", ...style }, children: /* @__PURE__ */ jsx16(FrankieProvider, { ...provider, conversationId: id, storageNamespace: (provider.storageNamespace ?? "frankie." + provider.publishableKey) + "." + id, children: /* @__PURE__ */ jsx16(PageChrome, { showHistory, navigation, currentId: id, onSelect: setId, onNew, header, footer, emptyState, renderMessage, renderTool, placeholder, autoFocus }) }) });
1468
+ 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 }) }) });
1441
1469
  }
1442
1470
 
1443
1471
  // src/components/ui/separator.tsx
1444
- import { jsx as jsx17 } from "react/jsx-runtime";
1472
+ import { jsx as jsx18 } from "react/jsx-runtime";
1445
1473
  function Separator({ className }) {
1446
- return /* @__PURE__ */ jsx17("div", { role: "separator", className: cn("h-px w-full bg-border", className) });
1474
+ return /* @__PURE__ */ jsx18("div", { role: "separator", className: cn("h-px w-full bg-border", className) });
1447
1475
  }
1448
1476
  export {
1449
1477
  Avatar,