@frankieai/react 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/frankie.css +1 -1
- package/dist/index.cjs +205 -177
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +201 -173
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/chat/AgentContext.tsx +22 -0
- package/src/components/chat/ChatPanel.tsx +9 -7
- package/src/components/chat/Composer.tsx +6 -5
- package/src/components/chat/FrankieChatPage.tsx +2 -3
- package/src/components/chat/MessageBubble.tsx +7 -5
- package/src/styles/frankie.css +61 -0
- package/src/widget.test.tsx +29 -0
package/dist/index.cjs
CHANGED
|
@@ -601,7 +601,7 @@ function useFrankieTheme() {
|
|
|
601
601
|
}
|
|
602
602
|
|
|
603
603
|
// src/components/chat/FrankieWidget.tsx
|
|
604
|
-
var
|
|
604
|
+
var import_lucide_react6 = require("lucide-react");
|
|
605
605
|
var import_react9 = require("react");
|
|
606
606
|
|
|
607
607
|
// src/lib/theme.ts
|
|
@@ -662,7 +662,7 @@ function Button({ className, variant, size, asChild = false, ...props }) {
|
|
|
662
662
|
}
|
|
663
663
|
|
|
664
664
|
// src/components/chat/ChatPanel.tsx
|
|
665
|
-
var
|
|
665
|
+
var import_lucide_react5 = require("lucide-react");
|
|
666
666
|
var import_react8 = require("react");
|
|
667
667
|
|
|
668
668
|
// src/components/ui/avatar.tsx
|
|
@@ -721,16 +721,51 @@ var ScrollArea = (0, import_react3.forwardRef)(
|
|
|
721
721
|
}
|
|
722
722
|
);
|
|
723
723
|
|
|
724
|
-
// src/components/chat/
|
|
724
|
+
// src/components/chat/AgentContext.tsx
|
|
725
725
|
var import_lucide_react = require("lucide-react");
|
|
726
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
727
|
+
function AgentContext() {
|
|
728
|
+
const { loading, configError } = useFrankie();
|
|
729
|
+
const { loadedSkills, busy, restoring, error } = useFrankieChat();
|
|
730
|
+
const status = configError || error ? "Unavailable" : loading || restoring ? "Connecting" : busy && loadedSkills === null ? "Checking" : loadedSkills?.length ? "Active" : loadedSkills ? "Tools only" : "Ready";
|
|
731
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "frankie-agent-context-wrap", children: [
|
|
732
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("details", { className: "frankie-agent-context", children: [
|
|
733
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("summary", { "aria-label": "Agent context", children: [
|
|
734
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react.Sparkles, { size: 17, "aria-hidden": "true" }),
|
|
735
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: "Agent context" }),
|
|
736
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "frankie-context-count", children: loadedSkills === null ? "Not checked" : `${loadedSkills.length} ${loadedSkills.length === 1 ? "skill" : "skills"}` }),
|
|
737
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "frankie-context-status", "data-active": status === "Active", children: [
|
|
738
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("i", { "aria-hidden": "true" }),
|
|
739
|
+
status
|
|
740
|
+
] }),
|
|
741
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react.ArrowUpRight, { size: 15, "aria-hidden": "true" })
|
|
742
|
+
] }),
|
|
743
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "frankie-context-detail", children: [
|
|
744
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("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" }),
|
|
745
|
+
loadedSkills?.length ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("ul", { children: loadedSkills.map((skill) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("li", { children: [
|
|
746
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: skill.name }),
|
|
747
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("small", { children: [
|
|
748
|
+
"v",
|
|
749
|
+
skill.revision
|
|
750
|
+
] })
|
|
751
|
+
] }, skill.id)) }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { children: "Frankie can use the tools approved for this workspace. Drafts and skills without available tools are not loaded." }),
|
|
752
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { children: "Published skills and updates are checked on every message. Only approved, available tools can run." })
|
|
753
|
+
] })
|
|
754
|
+
] }),
|
|
755
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "frankie-sr-only", role: "status", children: loadedSkills === null ? status : `${loadedSkills.length} ${loadedSkills.length === 1 ? "skill" : "skills"} loaded. ${status}.` })
|
|
756
|
+
] });
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
// src/components/chat/Composer.tsx
|
|
760
|
+
var import_lucide_react2 = require("lucide-react");
|
|
726
761
|
var import_react5 = require("react");
|
|
727
762
|
|
|
728
763
|
// src/components/ui/textarea.tsx
|
|
729
764
|
var import_react4 = require("react");
|
|
730
|
-
var
|
|
765
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
731
766
|
var Textarea = (0, import_react4.forwardRef)(
|
|
732
767
|
function Textarea2({ className, ...props }, ref) {
|
|
733
|
-
return /* @__PURE__ */ (0,
|
|
768
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
734
769
|
"textarea",
|
|
735
770
|
{
|
|
736
771
|
ref,
|
|
@@ -745,7 +780,7 @@ var Textarea = (0, import_react4.forwardRef)(
|
|
|
745
780
|
);
|
|
746
781
|
|
|
747
782
|
// src/components/chat/Composer.tsx
|
|
748
|
-
var
|
|
783
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
749
784
|
var MAX_ROWS_PX = 140;
|
|
750
785
|
function Composer({ onSend, onStop, busy, placeholder, disabled, autoFocus, attachments, modelPicker }) {
|
|
751
786
|
const [value, setValue] = (0, import_react5.useState)("");
|
|
@@ -807,46 +842,46 @@ function Composer({ onSend, onStop, busy, placeholder, disabled, autoFocus, atta
|
|
|
807
842
|
});
|
|
808
843
|
}, [busy, disabled, files, modelPicker?.value, onSend, value]);
|
|
809
844
|
const onKeyDown = (event) => {
|
|
810
|
-
if (event.key === "Enter" && !event.shiftKey) {
|
|
845
|
+
if (event.key === "Enter" && !event.shiftKey && !event.nativeEvent.isComposing) {
|
|
811
846
|
event.preventDefault();
|
|
812
847
|
submit();
|
|
813
848
|
}
|
|
814
849
|
};
|
|
815
850
|
const nothingToSend = value.trim().length === 0 && files.length === 0;
|
|
816
|
-
return /* @__PURE__ */ (0,
|
|
851
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
817
852
|
"form",
|
|
818
853
|
{
|
|
819
|
-
className: "flex flex-col gap-2",
|
|
854
|
+
className: "frankie-composer flex flex-col gap-2",
|
|
820
855
|
onSubmit: (event) => {
|
|
821
856
|
event.preventDefault();
|
|
822
857
|
submit();
|
|
823
858
|
},
|
|
824
859
|
children: [
|
|
825
|
-
files.length > 0 ? /* @__PURE__ */ (0,
|
|
860
|
+
files.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "flex flex-wrap gap-2", children: files.map((file) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
826
861
|
"span",
|
|
827
862
|
{
|
|
828
863
|
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",
|
|
829
864
|
children: [
|
|
830
|
-
/* @__PURE__ */ (0,
|
|
831
|
-
/* @__PURE__ */ (0,
|
|
832
|
-
/* @__PURE__ */ (0,
|
|
865
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react2.Paperclip, { className: "size-3 shrink-0 opacity-70" }),
|
|
866
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "truncate", children: file.name }),
|
|
867
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
833
868
|
"button",
|
|
834
869
|
{
|
|
835
870
|
type: "button",
|
|
836
871
|
onClick: () => removeFile(file.id),
|
|
837
872
|
"aria-label": `Remove ${file.name}`,
|
|
838
873
|
className: "frankie-focus shrink-0 opacity-60 hover:opacity-100",
|
|
839
|
-
children: /* @__PURE__ */ (0,
|
|
874
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react2.X, { className: "size-3" })
|
|
840
875
|
}
|
|
841
876
|
)
|
|
842
877
|
]
|
|
843
878
|
},
|
|
844
879
|
file.id
|
|
845
880
|
)) }) : null,
|
|
846
|
-
fileError ? /* @__PURE__ */ (0,
|
|
847
|
-
/* @__PURE__ */ (0,
|
|
848
|
-
canAttach ? /* @__PURE__ */ (0,
|
|
849
|
-
/* @__PURE__ */ (0,
|
|
881
|
+
fileError ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "text-xs text-red-600 dark:text-red-400", children: fileError }) : null,
|
|
882
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "frankie-composer-controls flex items-end gap-2", children: [
|
|
883
|
+
canAttach ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
|
|
884
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
850
885
|
"input",
|
|
851
886
|
{
|
|
852
887
|
ref: fileInputRef,
|
|
@@ -860,7 +895,7 @@ function Composer({ onSend, onStop, busy, placeholder, disabled, autoFocus, atta
|
|
|
860
895
|
}
|
|
861
896
|
}
|
|
862
897
|
),
|
|
863
|
-
/* @__PURE__ */ (0,
|
|
898
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
864
899
|
Button,
|
|
865
900
|
{
|
|
866
901
|
type: "button",
|
|
@@ -870,11 +905,11 @@ function Composer({ onSend, onStop, busy, placeholder, disabled, autoFocus, atta
|
|
|
870
905
|
onClick: () => fileInputRef.current?.click(),
|
|
871
906
|
"aria-label": "Attach files",
|
|
872
907
|
title: "Attach files",
|
|
873
|
-
children: /* @__PURE__ */ (0,
|
|
908
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react2.Plus, {})
|
|
874
909
|
}
|
|
875
910
|
)
|
|
876
911
|
] }) : null,
|
|
877
|
-
/* @__PURE__ */ (0,
|
|
912
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
878
913
|
Textarea,
|
|
879
914
|
{
|
|
880
915
|
ref: textareaRef,
|
|
@@ -888,7 +923,7 @@ function Composer({ onSend, onStop, busy, placeholder, disabled, autoFocus, atta
|
|
|
888
923
|
"aria-label": "Message"
|
|
889
924
|
}
|
|
890
925
|
),
|
|
891
|
-
models.length > 0 ? /* @__PURE__ */ (0,
|
|
926
|
+
models.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
892
927
|
"select",
|
|
893
928
|
{
|
|
894
929
|
value: modelPicker?.value ?? models[0]?.id,
|
|
@@ -896,10 +931,10 @@ function Composer({ onSend, onStop, busy, placeholder, disabled, autoFocus, atta
|
|
|
896
931
|
disabled,
|
|
897
932
|
"aria-label": "Model",
|
|
898
933
|
className: "frankie-focus h-9 max-w-[8rem] rounded-[var(--frankie-radius)] border border-border bg-background px-2 text-sm",
|
|
899
|
-
children: models.map((model) => /* @__PURE__ */ (0,
|
|
934
|
+
children: models.map((model) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("option", { value: model.id, children: model.label }, model.id))
|
|
900
935
|
}
|
|
901
936
|
) : null,
|
|
902
|
-
busy ? /* @__PURE__ */ (0,
|
|
937
|
+
busy ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Button, { type: "button", size: "icon", variant: "outline", onClick: onStop, "aria-label": "Stop", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react2.Square, {}) }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Button, { type: "submit", size: "icon", disabled: disabled || nothingToSend, "aria-label": "Send", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react2.ArrowUp, {}) })
|
|
903
938
|
] })
|
|
904
939
|
]
|
|
905
940
|
}
|
|
@@ -924,14 +959,14 @@ function readAsBase64(file) {
|
|
|
924
959
|
}
|
|
925
960
|
|
|
926
961
|
// src/components/chat/MessageBubble.tsx
|
|
927
|
-
var
|
|
962
|
+
var import_lucide_react4 = require("lucide-react");
|
|
928
963
|
var import_react7 = require("react");
|
|
929
964
|
|
|
930
965
|
// src/components/chat/Markdown.tsx
|
|
931
966
|
var import_react6 = require("react");
|
|
932
|
-
var
|
|
967
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
933
968
|
function Markdown({ text, className }) {
|
|
934
|
-
return /* @__PURE__ */ (0,
|
|
969
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: cn("frankie-prose", className), children: renderBlocks(text) });
|
|
935
970
|
}
|
|
936
971
|
function renderBlocks(text) {
|
|
937
972
|
const lines = text.split("\n");
|
|
@@ -941,13 +976,13 @@ function renderBlocks(text) {
|
|
|
941
976
|
let code = null;
|
|
942
977
|
const flushParagraph = () => {
|
|
943
978
|
if (paragraph.length === 0) return;
|
|
944
|
-
blocks.push(/* @__PURE__ */ (0,
|
|
979
|
+
blocks.push(/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { children: renderInline(paragraph.join(" ")) }, `p${blocks.length}`));
|
|
945
980
|
paragraph = [];
|
|
946
981
|
};
|
|
947
982
|
const flushList = () => {
|
|
948
983
|
if (!list) return;
|
|
949
|
-
const items = list.items.map((item, index) => /* @__PURE__ */ (0,
|
|
950
|
-
blocks.push(list.ordered ? /* @__PURE__ */ (0,
|
|
984
|
+
const items = list.items.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("li", { children: renderInline(item) }, index));
|
|
985
|
+
blocks.push(list.ordered ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("ol", { children: items }, `l${blocks.length}`) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("ul", { children: items }, `l${blocks.length}`));
|
|
951
986
|
list = null;
|
|
952
987
|
};
|
|
953
988
|
for (const line of lines) {
|
|
@@ -955,7 +990,7 @@ function renderBlocks(text) {
|
|
|
955
990
|
if (fence) {
|
|
956
991
|
if (code) {
|
|
957
992
|
blocks.push(
|
|
958
|
-
/* @__PURE__ */ (0,
|
|
993
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("pre", { children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("code", { children: code.lines.join("\n") }) }, `c${blocks.length}`)
|
|
959
994
|
);
|
|
960
995
|
code = null;
|
|
961
996
|
} else {
|
|
@@ -990,7 +1025,7 @@ function renderBlocks(text) {
|
|
|
990
1025
|
if (heading) {
|
|
991
1026
|
flushParagraph();
|
|
992
1027
|
flushList();
|
|
993
|
-
blocks.push(/* @__PURE__ */ (0,
|
|
1028
|
+
blocks.push(/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "font-semibold", children: renderInline(heading[1] ?? "") }, `h${blocks.length}`));
|
|
994
1029
|
continue;
|
|
995
1030
|
}
|
|
996
1031
|
flushList();
|
|
@@ -998,7 +1033,7 @@ function renderBlocks(text) {
|
|
|
998
1033
|
}
|
|
999
1034
|
if (code) {
|
|
1000
1035
|
blocks.push(
|
|
1001
|
-
/* @__PURE__ */ (0,
|
|
1036
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("pre", { children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("code", { children: code.lines.join("\n") }) }, `c${blocks.length}`)
|
|
1002
1037
|
);
|
|
1003
1038
|
}
|
|
1004
1039
|
flushParagraph();
|
|
@@ -1013,28 +1048,28 @@ function renderInline(text) {
|
|
|
1013
1048
|
const token = match[0];
|
|
1014
1049
|
const start = match.index;
|
|
1015
1050
|
if (start > lastIndex) nodes.push(text.slice(lastIndex, start));
|
|
1016
|
-
nodes.push(/* @__PURE__ */ (0,
|
|
1051
|
+
nodes.push(/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react6.Fragment, { children: renderToken(token) }, start));
|
|
1017
1052
|
lastIndex = start + token.length;
|
|
1018
1053
|
}
|
|
1019
1054
|
if (lastIndex < text.length) nodes.push(text.slice(lastIndex));
|
|
1020
1055
|
return nodes;
|
|
1021
1056
|
}
|
|
1022
1057
|
function renderToken(token) {
|
|
1023
|
-
if (token.startsWith("**")) return /* @__PURE__ */ (0,
|
|
1024
|
-
if (token.startsWith("`")) return /* @__PURE__ */ (0,
|
|
1058
|
+
if (token.startsWith("**")) return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("strong", { children: token.slice(2, -2) });
|
|
1059
|
+
if (token.startsWith("`")) return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("code", { children: token.slice(1, -1) });
|
|
1025
1060
|
const link = /^\[([^\]]+)\]\(([^)]+)\)$/.exec(token);
|
|
1026
1061
|
const href = link ? link[2] ?? "" : token;
|
|
1027
1062
|
const label = link ? link[1] ?? "" : token;
|
|
1028
1063
|
if (!/^(https?:\/\/|\/)/.test(href)) return token;
|
|
1029
|
-
return /* @__PURE__ */ (0,
|
|
1064
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("a", { href, target: "_blank", rel: "noopener noreferrer nofollow", children: label });
|
|
1030
1065
|
}
|
|
1031
1066
|
|
|
1032
1067
|
// src/components/chat/ToolActivityList.tsx
|
|
1033
|
-
var
|
|
1068
|
+
var import_lucide_react3 = require("lucide-react");
|
|
1034
1069
|
|
|
1035
1070
|
// src/components/ui/badge.tsx
|
|
1036
1071
|
var import_class_variance_authority2 = require("class-variance-authority");
|
|
1037
|
-
var
|
|
1072
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
1038
1073
|
var badgeVariants = (0, import_class_variance_authority2.cva)(
|
|
1039
1074
|
"inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-[0.6875rem] font-medium",
|
|
1040
1075
|
{
|
|
@@ -1054,44 +1089,44 @@ function Badge({
|
|
|
1054
1089
|
variant,
|
|
1055
1090
|
...props
|
|
1056
1091
|
}) {
|
|
1057
|
-
return /* @__PURE__ */ (0,
|
|
1092
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: cn(badgeVariants({ variant }), className), ...props });
|
|
1058
1093
|
}
|
|
1059
1094
|
|
|
1060
1095
|
// src/components/chat/ToolActivityList.tsx
|
|
1061
|
-
var
|
|
1096
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
1062
1097
|
function ToolActivityList({ tools, renderTool }) {
|
|
1063
1098
|
if (tools.length === 0) return null;
|
|
1064
1099
|
const running = tools.some((tool) => tool.status === "running");
|
|
1065
|
-
return /* @__PURE__ */ (0,
|
|
1066
|
-
/* @__PURE__ */ (0,
|
|
1067
|
-
/* @__PURE__ */ (0,
|
|
1068
|
-
/* @__PURE__ */ (0,
|
|
1069
|
-
/* @__PURE__ */ (0,
|
|
1100
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("details", { className: "frankie-tool-activity mt-2", open: running, children: [
|
|
1101
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("summary", { className: "inline-flex min-h-7 cursor-pointer list-none items-center gap-1.5 rounded-md px-1 text-xs font-medium text-muted-foreground hover:bg-muted hover:text-foreground [&::-webkit-details-marker]:hidden", children: [
|
|
1102
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react3.ChevronDown, { className: "frankie-tool-chevron size-3.5 transition-transform", "aria-hidden": "true" }),
|
|
1103
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: running ? "Working" : "Activity" }),
|
|
1104
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { className: "text-[11px]", children: [
|
|
1070
1105
|
"\xB7 ",
|
|
1071
1106
|
tools.length,
|
|
1072
1107
|
" tool call",
|
|
1073
1108
|
tools.length === 1 ? "" : "s"
|
|
1074
1109
|
] })
|
|
1075
1110
|
] }),
|
|
1076
|
-
/* @__PURE__ */ (0,
|
|
1111
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("ul", { className: "mt-1 flex flex-col gap-1 border-l border-border pl-2", children: tools.map((tool) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("li", { children: renderTool ? renderTool(tool) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ToolActivityRow, { tool }) }, tool.id)) })
|
|
1077
1112
|
] });
|
|
1078
1113
|
}
|
|
1079
1114
|
function ToolActivityRow({ tool }) {
|
|
1080
|
-
return /* @__PURE__ */ (0,
|
|
1081
|
-
/* @__PURE__ */ (0,
|
|
1082
|
-
/* @__PURE__ */ (0,
|
|
1115
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Badge, { variant: badgeVariant(tool.status), title: tool.detail ?? tool.name, children: [
|
|
1116
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ToolIcon, { status: tool.status }),
|
|
1117
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "truncate", children: tool.label })
|
|
1083
1118
|
] });
|
|
1084
1119
|
}
|
|
1085
1120
|
function ToolIcon({ status }) {
|
|
1086
1121
|
switch (status) {
|
|
1087
1122
|
case "running":
|
|
1088
|
-
return /* @__PURE__ */ (0,
|
|
1123
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react3.Loader2, { className: "size-3 animate-spin", "aria-hidden": "true" });
|
|
1089
1124
|
case "done":
|
|
1090
|
-
return /* @__PURE__ */ (0,
|
|
1125
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react3.Check, { className: "size-3", "aria-hidden": "true" });
|
|
1091
1126
|
case "denied":
|
|
1092
|
-
return /* @__PURE__ */ (0,
|
|
1127
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react3.ShieldAlert, { className: "size-3", "aria-hidden": "true" });
|
|
1093
1128
|
default:
|
|
1094
|
-
return /* @__PURE__ */ (0,
|
|
1129
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react3.X, { className: "size-3", "aria-hidden": "true" });
|
|
1095
1130
|
}
|
|
1096
1131
|
}
|
|
1097
1132
|
function badgeVariant(status) {
|
|
@@ -1102,7 +1137,7 @@ function badgeVariant(status) {
|
|
|
1102
1137
|
}
|
|
1103
1138
|
|
|
1104
1139
|
// src/components/chat/MessageBubble.tsx
|
|
1105
|
-
var
|
|
1140
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
1106
1141
|
function MessageBubble({
|
|
1107
1142
|
message,
|
|
1108
1143
|
assistantName,
|
|
@@ -1111,33 +1146,33 @@ function MessageBubble({
|
|
|
1111
1146
|
renderTool
|
|
1112
1147
|
}) {
|
|
1113
1148
|
if (message.role === "user") {
|
|
1114
|
-
return /* @__PURE__ */ (0,
|
|
1115
|
-
message.attachments?.length ? /* @__PURE__ */ (0,
|
|
1149
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "frankie-user-message flex flex-col items-end gap-1", children: [
|
|
1150
|
+
message.attachments?.length ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "flex max-w-[85%] flex-wrap justify-end gap-1", children: message.attachments.map((file, index) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
|
|
1116
1151
|
"span",
|
|
1117
1152
|
{
|
|
1118
1153
|
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",
|
|
1119
1154
|
children: [
|
|
1120
|
-
/* @__PURE__ */ (0,
|
|
1121
|
-
/* @__PURE__ */ (0,
|
|
1155
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react4.Paperclip, { className: "size-3 shrink-0 opacity-70" }),
|
|
1156
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "truncate", children: file.name })
|
|
1122
1157
|
]
|
|
1123
1158
|
},
|
|
1124
1159
|
`${file.name}-${index}`
|
|
1125
1160
|
)) }) : null,
|
|
1126
|
-
message.text ? /* @__PURE__ */ (0,
|
|
1127
|
-
/* @__PURE__ */ (0,
|
|
1128
|
-
/* @__PURE__ */ (0,
|
|
1161
|
+
message.text ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex max-w-[85%] items-end gap-1", children: [
|
|
1162
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "frankie-user-bubble rounded-[var(--frankie-radius)] bg-primary px-3 py-2 text-sm text-primary-foreground", children: message.text }),
|
|
1163
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(MessageCopyButton, { text: message.text })
|
|
1129
1164
|
] }) : null
|
|
1130
1165
|
] });
|
|
1131
1166
|
}
|
|
1132
1167
|
const empty = message.text.length === 0 && message.tools.length === 0;
|
|
1133
|
-
return /* @__PURE__ */ (0,
|
|
1134
|
-
/* @__PURE__ */ (0,
|
|
1135
|
-
/* @__PURE__ */ (0,
|
|
1136
|
-
message.text ? /* @__PURE__ */ (0,
|
|
1168
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "frankie-assistant-message flex gap-2", children: [
|
|
1169
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "frankie-assistant-actions mt-0.5 flex shrink-0 flex-col items-center gap-1", children: [
|
|
1170
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Avatar, { ...avatarUrl ? { src: avatarUrl } : {}, name: assistantName }),
|
|
1171
|
+
message.text ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(MessageCopyButton, { text: message.text }) : null
|
|
1137
1172
|
] }),
|
|
1138
|
-
/* @__PURE__ */ (0,
|
|
1139
|
-
message.status === "error" ? /* @__PURE__ */ (0,
|
|
1140
|
-
showToolActivity ? /* @__PURE__ */ (0,
|
|
1173
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "frankie-assistant-content min-w-0 max-w-[85%]", children: [
|
|
1174
|
+
message.status === "error" ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "rounded-[var(--frankie-radius)] bg-red-500/10 px-3 py-2 text-sm text-red-600 dark:text-red-400", children: message.error }) : empty && message.status === "streaming" ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(TypingDots, {}) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Markdown, { text: message.text, className: "text-sm" }),
|
|
1175
|
+
showToolActivity ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ToolActivityList, { tools: message.tools, ...renderTool ? { renderTool } : {} }) : null
|
|
1141
1176
|
] })
|
|
1142
1177
|
] });
|
|
1143
1178
|
}
|
|
@@ -1151,28 +1186,31 @@ function MessageCopyButton({ text }) {
|
|
|
1151
1186
|
} catch {
|
|
1152
1187
|
}
|
|
1153
1188
|
};
|
|
1154
|
-
return /* @__PURE__ */ (0,
|
|
1189
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { type: "button", onClick: () => void copy(), className: "frankie-message-copy inline-flex size-8 shrink-0 items-center justify-center rounded-md border border-transparent text-muted-foreground transition hover:border-border hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary", "aria-label": copied ? "Copied" : "Copy message", title: copied ? "Copied" : "Copy message", children: copied ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react4.Check, { className: "size-3.5", "aria-hidden": "true" }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react4.Copy, { className: "size-3.5", "aria-hidden": "true" }) });
|
|
1155
1190
|
}
|
|
1156
1191
|
function TypingDots() {
|
|
1157
|
-
return /* @__PURE__ */ (0,
|
|
1158
|
-
"span",
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1192
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "frankie-thinking inline-flex items-center gap-1 py-2", role: "status", "aria-label": "Thinking", children: [
|
|
1193
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "frankie-thinking-label", children: "Thinking" }),
|
|
1194
|
+
[0, 1, 2].map((index) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1195
|
+
"span",
|
|
1196
|
+
{
|
|
1197
|
+
className: cn("size-1.5 animate-pulse rounded-full bg-muted-foreground"),
|
|
1198
|
+
style: { animationDelay: `${index * 150}ms` }
|
|
1199
|
+
},
|
|
1200
|
+
index
|
|
1201
|
+
))
|
|
1202
|
+
] });
|
|
1165
1203
|
}
|
|
1166
1204
|
|
|
1167
1205
|
// src/components/chat/SuggestedPrompts.tsx
|
|
1168
|
-
var
|
|
1206
|
+
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
1169
1207
|
function SuggestedPrompts({
|
|
1170
1208
|
prompts,
|
|
1171
1209
|
onPick,
|
|
1172
1210
|
disabled
|
|
1173
1211
|
}) {
|
|
1174
1212
|
if (prompts.length === 0) return null;
|
|
1175
|
-
return /* @__PURE__ */ (0,
|
|
1213
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "flex flex-wrap gap-2", children: prompts.map((prompt) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1176
1214
|
Button,
|
|
1177
1215
|
{
|
|
1178
1216
|
type: "button",
|
|
@@ -1187,7 +1225,7 @@ function SuggestedPrompts({
|
|
|
1187
1225
|
}
|
|
1188
1226
|
|
|
1189
1227
|
// src/components/chat/ChatPanel.tsx
|
|
1190
|
-
var
|
|
1228
|
+
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
1191
1229
|
function ChatPanel({
|
|
1192
1230
|
className,
|
|
1193
1231
|
header,
|
|
@@ -1202,7 +1240,7 @@ function ChatPanel({
|
|
|
1202
1240
|
}) {
|
|
1203
1241
|
const { config, configError, loading } = useFrankie();
|
|
1204
1242
|
const theme = useFrankieTheme();
|
|
1205
|
-
const { messages, busy, restoring,
|
|
1243
|
+
const { messages, busy, restoring, error, send, stop, reset } = useFrankieChat();
|
|
1206
1244
|
const scrollRef = (0, import_react8.useRef)(null);
|
|
1207
1245
|
const features = config?.features;
|
|
1208
1246
|
const pickerModels = features?.modelPicker.enabled ? features.modelPicker.models : [];
|
|
@@ -1215,11 +1253,12 @@ function ChatPanel({
|
|
|
1215
1253
|
}, [messages]);
|
|
1216
1254
|
const name = config?.branding.name ?? "Assistant";
|
|
1217
1255
|
const mode = resolveMode(theme?.mode);
|
|
1218
|
-
return /* @__PURE__ */ (0,
|
|
1256
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
|
|
1219
1257
|
Card,
|
|
1220
1258
|
{
|
|
1221
1259
|
className: cn(
|
|
1222
|
-
"frankie-root",
|
|
1260
|
+
"frankie-root frankie-chat-panel",
|
|
1261
|
+
fill && "frankie-chat-panel-fill",
|
|
1223
1262
|
fill ? "h-full w-full rounded-none border-0 shadow-none" : "h-[32rem] w-[22rem] max-w-[calc(100vw-2rem)]",
|
|
1224
1263
|
mode === "dark" && "dark",
|
|
1225
1264
|
className
|
|
@@ -1227,24 +1266,25 @@ function ChatPanel({
|
|
|
1227
1266
|
style: themeToCssVars(theme),
|
|
1228
1267
|
"data-frankie-mode": mode,
|
|
1229
1268
|
children: [
|
|
1230
|
-
header === false ? null : header !== void 0 ? header : /* @__PURE__ */ (0,
|
|
1231
|
-
/* @__PURE__ */ (0,
|
|
1232
|
-
/* @__PURE__ */ (0,
|
|
1233
|
-
/* @__PURE__ */ (0,
|
|
1234
|
-
config?.branding.tagline ? /* @__PURE__ */ (0,
|
|
1269
|
+
header === false ? null : header !== void 0 ? header : /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(CardHeader, { children: [
|
|
1270
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Avatar, { ...config?.branding.avatarUrl ? { src: config.branding.avatarUrl } : {}, name }),
|
|
1271
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "min-w-0 flex-1", children: [
|
|
1272
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(CardTitle, { children: name }),
|
|
1273
|
+
config?.branding.tagline ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(CardDescription, { children: config.branding.tagline }) : null
|
|
1235
1274
|
] }),
|
|
1236
|
-
/* @__PURE__ */ (0,
|
|
1237
|
-
onClose ? /* @__PURE__ */ (0,
|
|
1275
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Button, { size: "icon", variant: "ghost", onClick: reset, "aria-label": "Start a new conversation", title: "New conversation", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_lucide_react5.RotateCcw, {}) }),
|
|
1276
|
+
onClose ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Button, { size: "icon", variant: "ghost", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_lucide_react5.X, {}) }) : null
|
|
1238
1277
|
] }),
|
|
1239
|
-
/* @__PURE__ */ (0,
|
|
1240
|
-
|
|
1241
|
-
/* @__PURE__ */ (0,
|
|
1242
|
-
/* @__PURE__ */ (0,
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
/* @__PURE__ */ (0,
|
|
1246
|
-
|
|
1247
|
-
|
|
1278
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(AgentContext, {}),
|
|
1279
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(CardContent, { className: "frankie-conversation-content", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ScrollArea, { ref: scrollRef, className: "frankie-transcript flex h-full flex-col gap-4 p-4", "aria-live": "polite", children: configError ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "frankie-config-error", role: "alert", children: [
|
|
1280
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("strong", { children: "Frankie couldn\u2019t start" }),
|
|
1281
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { children: configError }),
|
|
1282
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { children: "Refresh this page after checking the workspace\u2019s allowed origin and sign-in settings." })
|
|
1283
|
+
] }) : loading ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "text-sm text-muted-foreground", children: "Starting Frankie\u2026" }) : messages.length === 0 ? emptyState ?? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex flex-col gap-4", children: [
|
|
1284
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "text-sm text-muted-foreground", children: config?.greeting }),
|
|
1285
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(SuggestedPrompts, { prompts: config?.suggestedPrompts ?? [], onPick: send, disabled: busy })
|
|
1286
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "frankie-message-list flex flex-col gap-4", children: messages.map(
|
|
1287
|
+
(message) => renderMessage ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { children: renderMessage(message) }, message.id) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
1248
1288
|
MessageBubble,
|
|
1249
1289
|
{
|
|
1250
1290
|
message,
|
|
@@ -1256,19 +1296,9 @@ function ChatPanel({
|
|
|
1256
1296
|
message.id
|
|
1257
1297
|
)
|
|
1258
1298
|
) }) }) }),
|
|
1259
|
-
/* @__PURE__ */ (0,
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
loadedSkills.length ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("ul", { children: loadedSkills.map((skill) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("li", { children: [
|
|
1263
|
-
skill.name,
|
|
1264
|
-
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("small", { children: [
|
|
1265
|
-
"v",
|
|
1266
|
-
skill.revision
|
|
1267
|
-
] })
|
|
1268
|
-
] }, skill.id)) }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { children: "Your assistant can still answer using its available tools." })
|
|
1269
|
-
] }) : null,
|
|
1270
|
-
error ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { className: "w-full text-xs text-red-600 dark:text-red-400", children: error }) : null,
|
|
1271
|
-
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "w-full", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1299
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(CardFooter, { className: "frankie-composer-footer flex flex-col gap-2", children: [
|
|
1300
|
+
error ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "w-full text-xs text-red-600 dark:text-red-400", children: error }) : null,
|
|
1301
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "w-full", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
1272
1302
|
Composer,
|
|
1273
1303
|
{
|
|
1274
1304
|
onSend: send,
|
|
@@ -1289,7 +1319,7 @@ function ChatPanel({
|
|
|
1289
1319
|
}
|
|
1290
1320
|
|
|
1291
1321
|
// src/components/chat/FrankieWidget.tsx
|
|
1292
|
-
var
|
|
1322
|
+
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
1293
1323
|
function FrankieWidget({
|
|
1294
1324
|
position = "bottom-right",
|
|
1295
1325
|
offset = 24,
|
|
@@ -1319,15 +1349,15 @@ function FrankieWidget({
|
|
|
1319
1349
|
const label = config?.branding.launcherLabel ?? "Need a hand?";
|
|
1320
1350
|
const mode = resolveMode(theme?.mode);
|
|
1321
1351
|
const side = position === "bottom-right" ? { right: offset } : { left: offset };
|
|
1322
|
-
return /* @__PURE__ */ (0,
|
|
1352
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
1323
1353
|
"div",
|
|
1324
1354
|
{
|
|
1325
1355
|
className: cn("frankie-root frankie-widget", mode === "dark" && "dark"),
|
|
1326
1356
|
style: { ...themeToCssVars(theme), position: "fixed", bottom: offset, ...side, zIndex, pointerEvents: "none" },
|
|
1327
1357
|
"data-frankie-mode": mode,
|
|
1328
|
-
children: /* @__PURE__ */ (0,
|
|
1329
|
-
open ? /* @__PURE__ */ (0,
|
|
1330
|
-
/* @__PURE__ */ (0,
|
|
1358
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: cn("flex flex-col gap-3", position === "bottom-left" ? "items-start" : "items-end"), children: [
|
|
1359
|
+
open ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { style: { pointerEvents: "auto", maxHeight: "calc(100dvh - 100px)", overflow: "hidden" }, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ChatPanel, { ...panelProps, className: cn("frankie-widget-panel", panelProps.className), onClose: () => setOpen(false), autoFocus: true }) }) : null,
|
|
1360
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { style: { pointerEvents: "auto" }, children: renderLauncher ? renderLauncher({ open, toggle: () => setOpen(!open), label }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
1331
1361
|
Button,
|
|
1332
1362
|
{
|
|
1333
1363
|
size: "icon",
|
|
@@ -1336,7 +1366,7 @@ function FrankieWidget({
|
|
|
1336
1366
|
"aria-expanded": open,
|
|
1337
1367
|
"aria-label": open ? "Close the assistant" : label,
|
|
1338
1368
|
title: label,
|
|
1339
|
-
children: open ? /* @__PURE__ */ (0,
|
|
1369
|
+
children: open ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react6.X, { className: "size-5" }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react6.MessageCircle, { className: "size-5" })
|
|
1340
1370
|
}
|
|
1341
1371
|
) })
|
|
1342
1372
|
] })
|
|
@@ -1345,45 +1375,45 @@ function FrankieWidget({
|
|
|
1345
1375
|
}
|
|
1346
1376
|
|
|
1347
1377
|
// src/components/chat/FrankieInline.tsx
|
|
1348
|
-
var
|
|
1378
|
+
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
1349
1379
|
function FrankieInline({ className, style, ...panelProps }) {
|
|
1350
1380
|
const theme = useFrankieTheme();
|
|
1351
1381
|
const mode = resolveMode(theme?.mode);
|
|
1352
|
-
return /* @__PURE__ */ (0,
|
|
1382
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1353
1383
|
"div",
|
|
1354
1384
|
{
|
|
1355
1385
|
className: cn("frankie-root frankie-inline", mode === "dark" && "dark", className),
|
|
1356
1386
|
style: { ...themeToCssVars(theme), display: "flex", height: "100%", width: "100%", ...style },
|
|
1357
1387
|
"data-frankie-mode": mode,
|
|
1358
|
-
children: /* @__PURE__ */ (0,
|
|
1388
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ChatPanel, { ...panelProps, fill: true })
|
|
1359
1389
|
}
|
|
1360
1390
|
);
|
|
1361
1391
|
}
|
|
1362
1392
|
|
|
1363
1393
|
// src/components/chat/FrankieChatPage.tsx
|
|
1364
1394
|
var import_react10 = require("react");
|
|
1365
|
-
var
|
|
1366
|
-
var
|
|
1395
|
+
var import_lucide_react7 = require("lucide-react");
|
|
1396
|
+
var import_jsx_runtime17 = require("react/jsx-runtime");
|
|
1367
1397
|
function PageEmptyState() {
|
|
1368
1398
|
const { config } = useFrankie();
|
|
1369
1399
|
const { send, busy, restoring } = useFrankieChat();
|
|
1370
1400
|
const name = config?.branding.name ?? "Frankie";
|
|
1371
|
-
return /* @__PURE__ */ (0,
|
|
1372
|
-
/* @__PURE__ */ (0,
|
|
1373
|
-
/* @__PURE__ */ (0,
|
|
1374
|
-
/* @__PURE__ */ (0,
|
|
1375
|
-
/* @__PURE__ */ (0,
|
|
1376
|
-
/* @__PURE__ */ (0,
|
|
1401
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("section", { className: "frankie-page-empty", "aria-label": "Start a conversation", children: [
|
|
1402
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "frankie-page-avatar", "aria-hidden": true, children: name.trim().slice(0, 1).toUpperCase() }),
|
|
1403
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { children: [
|
|
1404
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { className: "frankie-page-kicker", children: "Your product assistant" }),
|
|
1405
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("h1", { children: "How can I help?" }),
|
|
1406
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { children: config?.greeting ?? "Ask a question or choose a task to get started." })
|
|
1377
1407
|
] }),
|
|
1378
|
-
/* @__PURE__ */ (0,
|
|
1379
|
-
/* @__PURE__ */ (0,
|
|
1380
|
-
/* @__PURE__ */ (0,
|
|
1408
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "frankie-page-prompts", children: config?.suggestedPrompts?.slice(0, 3).map((prompt) => /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("button", { type: "button", disabled: busy || restoring, onClick: () => void send(prompt), children: [
|
|
1409
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("strong", { children: prompt }),
|
|
1410
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("b", { "aria-hidden": true, children: "\u2197" })
|
|
1381
1411
|
] }, prompt)) })
|
|
1382
1412
|
] });
|
|
1383
1413
|
}
|
|
1384
1414
|
function PageChrome({ showHistory, navigation, currentId, onSelect, onNew, ...panel }) {
|
|
1385
1415
|
const { client, config } = useFrankie();
|
|
1386
|
-
const { busy } = useFrankieChat();
|
|
1416
|
+
const { busy, messages } = useFrankieChat();
|
|
1387
1417
|
const theme = useFrankieTheme();
|
|
1388
1418
|
const [conversations, setConversations] = (0, import_react10.useState)([]);
|
|
1389
1419
|
const [historyError, setHistoryError] = (0, import_react10.useState)("");
|
|
@@ -1393,7 +1423,6 @@ function PageChrome({ showHistory, navigation, currentId, onSelect, onNew, ...pa
|
|
|
1393
1423
|
const dialog = (0, import_react10.useRef)(null);
|
|
1394
1424
|
const menu = (0, import_react10.useRef)(null);
|
|
1395
1425
|
const requestVersion = (0, import_react10.useRef)(0);
|
|
1396
|
-
const name = config?.branding.name ?? "Frankie";
|
|
1397
1426
|
const refresh = (0, import_react10.useCallback)(async () => {
|
|
1398
1427
|
if (!showHistory || !config || config.allowAnonymous) {
|
|
1399
1428
|
setHistoryError("");
|
|
@@ -1435,77 +1464,76 @@ function PageChrome({ showHistory, navigation, currentId, onSelect, onNew, ...pa
|
|
|
1435
1464
|
close();
|
|
1436
1465
|
}
|
|
1437
1466
|
};
|
|
1438
|
-
const history = /* @__PURE__ */ (0,
|
|
1439
|
-
/* @__PURE__ */ (0,
|
|
1440
|
-
/* @__PURE__ */ (0,
|
|
1441
|
-
/* @__PURE__ */ (0,
|
|
1467
|
+
const history = /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
|
|
1468
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "frankie-history-heading", children: [
|
|
1469
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("strong", { children: "Conversations" }),
|
|
1470
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", className: "frankie-mobile-only", "aria-label": "Close conversations", onClick: close, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react7.X, { size: 18 }) })
|
|
1442
1471
|
] }),
|
|
1443
|
-
/* @__PURE__ */ (0,
|
|
1472
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("button", { type: "button", className: "frankie-new-chat", disabled: busy, onClick: () => {
|
|
1444
1473
|
onNew();
|
|
1445
1474
|
close();
|
|
1446
1475
|
}, children: [
|
|
1447
|
-
/* @__PURE__ */ (0,
|
|
1476
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react7.Plus, { size: 17 }),
|
|
1448
1477
|
"New chat"
|
|
1449
1478
|
] }),
|
|
1450
|
-
/* @__PURE__ */ (0,
|
|
1451
|
-
/* @__PURE__ */ (0,
|
|
1452
|
-
/* @__PURE__ */ (0,
|
|
1479
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("label", { className: "frankie-history-search", children: [
|
|
1480
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { children: "Search conversations" }),
|
|
1481
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("input", { type: "search", placeholder: "Search chats\u2026", value: query, onChange: (event) => setQuery(event.target.value) })
|
|
1453
1482
|
] }),
|
|
1454
|
-
/* @__PURE__ */ (0,
|
|
1455
|
-
/* @__PURE__ */ (0,
|
|
1456
|
-
/* @__PURE__ */ (0,
|
|
1483
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("nav", { className: "frankie-history-list", "aria-label": "Conversations", children: conversations.filter((item) => item.title.toLowerCase().includes(query.toLowerCase())).map((item) => /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("button", { type: "button", "aria-current": item.id === currentId ? "page" : void 0, disabled: busy, onClick: () => select(item.id), children: [
|
|
1484
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { children: item.title }),
|
|
1485
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("small", { children: new Date(item.updatedAt).toLocaleDateString() })
|
|
1457
1486
|
] }, item.id)) }),
|
|
1458
|
-
historyLoading ? /* @__PURE__ */ (0,
|
|
1487
|
+
historyLoading ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { role: "status", children: "Loading history\u2026" }) : historyError ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("p", { role: "status", children: [
|
|
1459
1488
|
historyError,
|
|
1460
|
-
/* @__PURE__ */ (0,
|
|
1461
|
-
] }) : !conversations.length ? /* @__PURE__ */ (0,
|
|
1462
|
-
navigation ? /* @__PURE__ */ (0,
|
|
1489
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", onClick: () => void refresh(), children: "Try again" })
|
|
1490
|
+
] }) : !conversations.length ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { children: config?.allowAnonymous ? "Sign in to sync conversations across devices." : "Your conversations will appear here." }) : null,
|
|
1491
|
+
navigation ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "frankie-host-navigation", children: navigation }) : null
|
|
1463
1492
|
] });
|
|
1464
|
-
return /* @__PURE__ */ (0,
|
|
1465
|
-
showHistory ? /* @__PURE__ */ (0,
|
|
1466
|
-
/* @__PURE__ */ (0,
|
|
1467
|
-
/* @__PURE__ */ (0,
|
|
1468
|
-
/* @__PURE__ */ (0,
|
|
1469
|
-
showHistory ? /* @__PURE__ */ (0,
|
|
1470
|
-
/* @__PURE__ */ (0,
|
|
1471
|
-
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "frankie-header-caption", children: "Product assistant" })
|
|
1493
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "frankie-page-shell" + (resolveMode(theme?.mode) === "dark" ? " dark" : ""), style: themeToCssVars(theme), children: [
|
|
1494
|
+
showHistory ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("aside", { className: "frankie-history-desktop", children: history }) : null,
|
|
1495
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "frankie-page-main", children: [
|
|
1496
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("header", { className: "frankie-page-header", children: [
|
|
1497
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { children: [
|
|
1498
|
+
showHistory ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", className: "frankie-mobile-only", ref: menu, "aria-label": "Open conversations", "aria-haspopup": "dialog", "aria-expanded": drawerOpen, onClick: () => setDrawerOpen(true), children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react7.Menu, { size: 20 }) }) : null,
|
|
1499
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("strong", { className: "frankie-thread-title", children: conversations.find((item) => item.id === currentId)?.title ?? messages.find((message) => message.role === "user")?.text ?? "New chat" })
|
|
1472
1500
|
] }),
|
|
1473
|
-
/* @__PURE__ */ (0,
|
|
1474
|
-
/* @__PURE__ */ (0,
|
|
1475
|
-
/* @__PURE__ */ (0,
|
|
1476
|
-
/* @__PURE__ */ (0,
|
|
1501
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { children: [
|
|
1502
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("button", { type: "button", disabled: busy, onClick: onNew, "aria-label": "Start a new conversation", children: [
|
|
1503
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react7.Plus, { size: 16 }),
|
|
1504
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { children: "New chat" })
|
|
1477
1505
|
] }),
|
|
1478
|
-
/* @__PURE__ */ (0,
|
|
1479
|
-
/* @__PURE__ */ (0,
|
|
1480
|
-
/* @__PURE__ */ (0,
|
|
1481
|
-
/* @__PURE__ */ (0,
|
|
1482
|
-
/* @__PURE__ */ (0,
|
|
1483
|
-
/* @__PURE__ */ (0,
|
|
1484
|
-
/* @__PURE__ */ (0,
|
|
1506
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("details", { className: "frankie-chat-settings", children: [
|
|
1507
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("summary", { "aria-label": "Chat settings", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react7.Settings, { size: 19 }) }),
|
|
1508
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { children: [
|
|
1509
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("strong", { children: "Chat settings" }),
|
|
1510
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { children: "History syncs automatically for signed-in users." }),
|
|
1511
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("button", { type: "button", onClick: () => void refresh(), disabled: historyLoading || !showHistory, children: [
|
|
1512
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react7.History, { size: 16 }),
|
|
1485
1513
|
"Refresh history"
|
|
1486
1514
|
] }),
|
|
1487
|
-
/* @__PURE__ */ (0,
|
|
1515
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("small", { children: "Skills are checked at the start of each message." })
|
|
1488
1516
|
] })
|
|
1489
1517
|
] })
|
|
1490
1518
|
] })
|
|
1491
1519
|
] }),
|
|
1492
|
-
/* @__PURE__ */ (0,
|
|
1520
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "frankie-page-canvas", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(FrankieInline, { ...panel, header: panel.header ?? false, emptyState: panel.emptyState ?? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(PageEmptyState, {}) }) })
|
|
1493
1521
|
] }),
|
|
1494
|
-
showHistory ? /* @__PURE__ */ (0,
|
|
1522
|
+
showHistory ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("dialog", { ref: dialog, className: "frankie-history-dialog frankie-root", "aria-label": "Chat conversations", style: themeToCssVars(theme), onCancel: close, onClick: (event) => {
|
|
1495
1523
|
if (event.target === event.currentTarget) close();
|
|
1496
|
-
}, children: /* @__PURE__ */ (0,
|
|
1524
|
+
}, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("aside", { children: history }) }) : null
|
|
1497
1525
|
] });
|
|
1498
1526
|
}
|
|
1499
1527
|
function FrankieChatPage({ className, style, header, footer, emptyState, renderMessage, renderTool, placeholder, autoFocus, showHistory = true, navigation, ...provider }) {
|
|
1500
1528
|
const [id, setId] = (0, import_react10.useState)(() => provider.conversationId ?? crypto.randomUUID());
|
|
1501
1529
|
const onNew = () => setId(crypto.randomUUID());
|
|
1502
|
-
return /* @__PURE__ */ (0,
|
|
1530
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: ["frankie-root", "frankie-chat-page", className].filter(Boolean).join(" "), style: { height: "100%", minHeight: "min(36rem, 100dvh)", width: "100%", ...style }, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(FrankieProvider, { ...provider, conversationId: id, storageNamespace: (provider.storageNamespace ?? "frankie." + provider.publishableKey) + "." + id, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(PageChrome, { showHistory, navigation, currentId: id, onSelect: setId, onNew, header, footer, emptyState, renderMessage, renderTool, placeholder, autoFocus }) }) });
|
|
1503
1531
|
}
|
|
1504
1532
|
|
|
1505
1533
|
// src/components/ui/separator.tsx
|
|
1506
|
-
var
|
|
1534
|
+
var import_jsx_runtime18 = require("react/jsx-runtime");
|
|
1507
1535
|
function Separator({ className }) {
|
|
1508
|
-
return /* @__PURE__ */ (0,
|
|
1536
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { role: "separator", className: cn("h-px w-full bg-border", className) });
|
|
1509
1537
|
}
|
|
1510
1538
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1511
1539
|
0 && (module.exports = {
|