@frankieai/react 0.1.1 → 0.1.4
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 +198 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +33 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.cts +31 -18
- package/dist/index.d.ts +31 -18
- package/dist/index.js +196 -37
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/chat/ChatPanel.tsx +6 -2
- package/src/components/chat/FrankieChatPage.tsx +53 -0
- package/src/components/chat/MessageBubble.tsx +22 -5
- package/src/components/chat/ToolActivityList.tsx +10 -6
- package/src/components/chat/message-actions.css +13 -0
- package/src/hooks/FrankieProvider.tsx +1 -9
- package/src/hooks/useFrankieChat.ts +60 -15
- package/src/index.ts +2 -0
- package/src/lib/client.ts +82 -17
- package/src/styles/frankie.css +12 -4
package/dist/frankie.css
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
|
|
2
|
-
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid}}}@layer theme{:root,:host{--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-emerald-400:oklch(76.5% .177 163.223);--color-emerald-500:oklch(69.6% .17 162.48);--color-emerald-600:oklch(59.6% .145 163.225);--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--font-weight-medium:500;--font-weight-semibold:600;--leading-tight:1.25;--animate-spin:spin 1s linear infinite;--animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1)}}@layer components{.frankie-root{--frankie-background:#fff;--frankie-foreground:#0a0a0a;--frankie-primary:#171717;--frankie-primary-foreground:#fafafa;--frankie-secondary:#f5f5f5;--frankie-secondary-foreground:#171717;--frankie-muted:#f5f5f5;--frankie-muted-foreground:#737373;--frankie-border:#e5e5e5;--frankie-radius:.625rem;--frankie-font:ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;font-family:var(--frankie-font);color:var(--frankie-foreground);-webkit-font-smoothing:antialiased;line-height:1.5}.frankie-root.dark{--frankie-background:#0a0a0a;--frankie-foreground:#fafafa;--frankie-primary:#fafafa;--frankie-primary-foreground:#171717;--frankie-secondary:#262626;--frankie-secondary-foreground:#fafafa;--frankie-muted:#262626;--frankie-muted-foreground:#a3a3a3;--frankie-border:#262626}.frankie-root,.frankie-root *,.frankie-root :before,.frankie-root :after{box-sizing:border-box}.frankie-root button,.frankie-root textarea,.frankie-root input{font:inherit;color:inherit;margin:0}.frankie-root textarea{background-color:var(--frankie-background)}.frankie-focus:focus-visible{outline:2px solid var(--frankie-primary);outline-offset:2px}.frankie-scroll{scrollbar-width:thin;scrollbar-color:var(--frankie-border) transparent}.frankie-prose>*+*{margin-top:.5rem}.frankie-prose p{white-space:pre-wrap;overflow-wrap:anywhere;margin:0}.frankie-prose ul,.frankie-prose ol{flex-direction:column;gap:.25rem;margin:0;padding-left:1.15rem;display:flex}.frankie-prose ul{list-style:outside}.frankie-prose ol{list-style:decimal}.frankie-prose a{color:var(--frankie-primary);text-underline-offset:2px;text-decoration:underline}.frankie-prose code{background-color:var(--frankie-muted);border-radius:.25rem;padding:.1em .3em;font-family:ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,monospace;font-size:.85em}.frankie-prose pre{border-radius:var(--frankie-radius);background-color:var(--frankie-muted);margin:0;padding:.625rem .75rem;overflow-x:auto}.frankie-prose pre code{background:0 0;padding:0}}@layer utilities{.fixed{position:fixed}.isolate{isolation:isolate}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-2{margin-top:calc(var(--spacing) * 2)}.flex{display:flex}.hidden{display:none}.inline{display:inline}.inline-flex{display:inline-flex}.size-1\.5{width:calc(var(--spacing) * 1.5);height:calc(var(--spacing) * 1.5)}.size-3{width:calc(var(--spacing) * 3);height:calc(var(--spacing) * 3)}.size-5{width:calc(var(--spacing) * 5);height:calc(var(--spacing) * 5)}.size-7{width:calc(var(--spacing) * 7);height:calc(var(--spacing) * 7)}.size-9{width:calc(var(--spacing) * 9);height:calc(var(--spacing) * 9)}.size-12{width:calc(var(--spacing) * 12);height:calc(var(--spacing) * 12)}.size-full{width:100%;height:100%}.h-8{height:calc(var(--spacing) * 8)}.h-9{height:calc(var(--spacing) * 9)}.h-10{height:calc(var(--spacing) * 10)}.h-\[32rem\]{height:32rem}.h-full{height:100%}.h-px{height:1px}.min-h-\[2\.25rem\]{min-height:2.25rem}.w-\[22rem\]{width:22rem}.w-full{width:100%}.max-w-\[8rem\]{max-width:8rem}.max-w-\[12rem\]{max-width:12rem}.max-w-\[14rem\]{max-width:14rem}.max-w-\[85\%\]{max-width:85%}.max-w-\[calc\(100vw-2rem\)\]{max-width:calc(100vw - 2rem)}.min-w-0{min-width:0}.flex-1{flex:1}.shrink-0{flex-shrink:0}.animate-pulse{animation:var(--animate-pulse)}.animate-spin{animation:var(--animate-spin)}.resize-none{resize:none}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-1{gap:var(--spacing)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.overscroll-contain{overscroll-behavior:contain}.rounded-\[calc\(var\(--frankie-radius\)\+4px\)\]{border-radius:calc(var(--frankie-radius) + 4px)}.rounded-\[calc\(var\(--frankie-radius\)-2px\)\]{border-radius:calc(var(--frankie-radius) - 2px)}.rounded-\[var\(--frankie-radius\)\]{border-radius:var(--frankie-radius)}.rounded-full{border-radius:3.40282e38px}.rounded-none{border-radius:0}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-border{border-color:var(--frankie-border)}.border-transparent{border-color:#0000}.bg-background{background-color:var(--frankie-background)}.bg-border{background-color:var(--frankie-border)}.bg-emerald-500\/10{background-color:#00bb7f1a}@supports (color:color-mix(in lab, red, red)){.bg-emerald-500\/10{background-color:color-mix(in oklab, var(--color-emerald-500) 10%, transparent)}}.bg-muted{background-color:var(--frankie-muted)}.bg-muted-foreground{background-color:var(--frankie-muted-foreground)}.bg-primary{background-color:var(--frankie-primary)}.bg-red-500\/10{background-color:#fb2c361a}@supports (color:color-mix(in lab, red, red)){.bg-red-500\/10{background-color:color-mix(in oklab, var(--color-red-500) 10%, transparent)}}.bg-secondary{background-color:var(--frankie-secondary)}.object-cover{object-fit:cover}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-6{padding-inline:calc(var(--spacing) * 6)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:var(--spacing)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-3{padding-block:calc(var(--spacing) * 3)}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[0\.625rem\]{font-size:.625rem}.text-\[0\.6875rem\]{font-size:.6875rem}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.whitespace-nowrap{white-space:nowrap}.text-emerald-600{color:var(--color-emerald-600)}.text-foreground{color:var(--frankie-foreground)}.text-muted-foreground{color:var(--frankie-muted-foreground)}.text-primary{color:var(--frankie-primary)}.text-primary-foreground{color:var(--frankie-primary-foreground)}.text-red-600{color:var(--color-red-600)}.text-secondary-foreground{color:var(--frankie-secondary-foreground)}.underline-offset-4{text-underline-offset:4px}.opacity-60{opacity:.6}.opacity-70{opacity:.7}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-none{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.placeholder\:text-muted-foreground::placeholder{color:var(--frankie-muted-foreground)}@media (hover:hover){.hover\:bg-muted:hover{background-color:var(--frankie-muted)}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-90:hover{opacity:.9}.hover\:opacity-100:hover{opacity:1}}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}.dark\:text-emerald-400:where(.dark,.dark *){color:var(--color-emerald-400)}.dark\:text-red-400:where(.dark,.dark *){color:var(--color-red-400)}.\[\&_svg\]\:pointer-events-none svg{pointer-events:none}.\[\&_svg\]\:size-4 svg{width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}.\[\&_svg\]\:shrink-0 svg{flex-shrink:0}}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse{50%{opacity:.5}}
|
|
2
|
+
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid}}}@layer theme{:root,:host{--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-emerald-400:oklch(76.5% .177 163.223);--color-emerald-500:oklch(69.6% .17 162.48);--color-emerald-600:oklch(59.6% .145 163.225);--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--font-weight-medium:500;--font-weight-semibold:600;--leading-tight:1.25;--radius-md:.375rem;--animate-spin:spin 1s linear infinite;--animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1)}}@layer components{.frankie-root{--frankie-background:#fff;--frankie-foreground:#0a0a0a;--frankie-primary:#171717;--frankie-primary-foreground:#fafafa;--frankie-secondary:#f5f5f5;--frankie-secondary-foreground:#171717;--frankie-muted:#f5f5f5;--frankie-muted-foreground:#737373;--frankie-border:#e5e5e5;--frankie-radius:.625rem;--frankie-font:ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;font-family:var(--frankie-font);color:var(--frankie-foreground);-webkit-font-smoothing:antialiased;line-height:1.5}.frankie-root.dark{--frankie-background:#0a0a0a;--frankie-foreground:#fafafa;--frankie-primary:#fafafa;--frankie-primary-foreground:#171717;--frankie-secondary:#262626;--frankie-secondary-foreground:#fafafa;--frankie-muted:#262626;--frankie-muted-foreground:#a3a3a3;--frankie-border:#262626}.frankie-root,.frankie-root *,.frankie-root :before,.frankie-root :after{box-sizing:border-box}.frankie-root button,.frankie-root textarea,.frankie-root input{font:inherit;color:inherit;margin:0}.frankie-root textarea{background-color:var(--frankie-background)}.frankie-focus:focus-visible{outline:2px solid var(--frankie-primary);outline-offset:2px}.frankie-scroll{scrollbar-width:thin;scrollbar-color:var(--frankie-border) transparent}.frankie-prose>*+*{margin-top:.5rem}.frankie-prose p{white-space:pre-wrap;overflow-wrap:anywhere;margin:0}.frankie-prose ul,.frankie-prose ol{flex-direction:column;gap:.25rem;margin:0;padding-left:1.15rem;display:flex}.frankie-prose ul{list-style:outside}.frankie-prose ol{list-style:decimal}.frankie-prose a{color:var(--frankie-primary);text-underline-offset:2px;text-decoration:underline}.frankie-prose code{background-color:var(--frankie-muted);border-radius:.25rem;padding:.1em .3em;font-family:ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,monospace;font-size:.85em}.frankie-prose pre{border-radius:var(--frankie-radius);background-color:var(--frankie-muted);margin:0;padding:.625rem .75rem;overflow-x:auto}.frankie-prose pre code{background:0 0;padding:0}}@layer utilities;@scope(.frankie-root){.visible{visibility:visible}.fixed{position:fixed}.isolate{isolation:isolate}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:var(--spacing)}.mt-2{margin-top:calc(var(--spacing) * 2)}.flex{display:flex}.hidden{display:none}.inline{display:inline}.inline-flex{display:inline-flex}.size-1\.5{width:calc(var(--spacing) * 1.5);height:calc(var(--spacing) * 1.5)}.size-3{width:calc(var(--spacing) * 3);height:calc(var(--spacing) * 3)}.size-3\.5{width:calc(var(--spacing) * 3.5);height:calc(var(--spacing) * 3.5)}.size-5{width:calc(var(--spacing) * 5);height:calc(var(--spacing) * 5)}.size-7{width:calc(var(--spacing) * 7);height:calc(var(--spacing) * 7)}.size-8{width:calc(var(--spacing) * 8);height:calc(var(--spacing) * 8)}.size-9{width:calc(var(--spacing) * 9);height:calc(var(--spacing) * 9)}.size-12{width:calc(var(--spacing) * 12);height:calc(var(--spacing) * 12)}.size-full{width:100%;height:100%}.h-8{height:calc(var(--spacing) * 8)}.h-9{height:calc(var(--spacing) * 9)}.h-10{height:calc(var(--spacing) * 10)}.h-\[32rem\]{height:32rem}.h-full{height:100%}.h-px{height:1px}.min-h-7{min-height:calc(var(--spacing) * 7)}.min-h-\[2\.25rem\]{min-height:2.25rem}.w-\[22rem\]{width:22rem}.w-full{width:100%}.max-w-\[8rem\]{max-width:8rem}.max-w-\[12rem\]{max-width:12rem}.max-w-\[14rem\]{max-width:14rem}.max-w-\[85\%\]{max-width:85%}.max-w-\[calc\(100vw-2rem\)\]{max-width:calc(100vw - 2rem)}.min-w-0{min-width:0}.flex-1{flex:1}.shrink-0{flex-shrink:0}.animate-pulse{animation:var(--animate-pulse)}.animate-spin{animation:var(--animate-spin)}.cursor-pointer{cursor:pointer}.resize-none{resize:none}.list-none{list-style-type:none}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-1{gap:var(--spacing)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.overscroll-contain{overscroll-behavior:contain}.rounded-\[calc\(var\(--frankie-radius\)\+4px\)\]{border-radius:calc(var(--frankie-radius) + 4px)}.rounded-\[calc\(var\(--frankie-radius\)-2px\)\]{border-radius:calc(var(--frankie-radius) - 2px)}.rounded-\[var\(--frankie-radius\)\]{border-radius:var(--frankie-radius)}.rounded-full{border-radius:3.40282e38px}.rounded-md{border-radius:var(--radius-md)}.rounded-none{border-radius:0}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-border{border-color:var(--frankie-border)}.border-transparent{border-color:#0000}.bg-background{background-color:var(--frankie-background)}.bg-border{background-color:var(--frankie-border)}.bg-emerald-500\/10{background-color:#00bb7f1a}@supports (color:color-mix(in lab, red, red)){.bg-emerald-500\/10{background-color:color-mix(in oklab, var(--color-emerald-500) 10%, transparent)}}.bg-muted{background-color:var(--frankie-muted)}.bg-muted-foreground{background-color:var(--frankie-muted-foreground)}.bg-primary{background-color:var(--frankie-primary)}.bg-red-500\/10{background-color:#fb2c361a}@supports (color:color-mix(in lab, red, red)){.bg-red-500\/10{background-color:color-mix(in oklab, var(--color-red-500) 10%, transparent)}}.bg-secondary{background-color:var(--frankie-secondary)}.object-cover{object-fit:cover}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.px-1{padding-inline:var(--spacing)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-6{padding-inline:calc(var(--spacing) * 6)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:var(--spacing)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-3{padding-block:calc(var(--spacing) * 3)}.pl-2{padding-left:calc(var(--spacing) * 2)}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[0\.625rem\]{font-size:.625rem}.text-\[0\.6875rem\]{font-size:.6875rem}.text-\[11px\]{font-size:11px}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.whitespace-nowrap{white-space:nowrap}.text-emerald-600{color:var(--color-emerald-600)}.text-foreground{color:var(--frankie-foreground)}.text-muted-foreground{color:var(--frankie-muted-foreground)}.text-primary{color:var(--frankie-primary)}.text-primary-foreground{color:var(--frankie-primary-foreground)}.text-red-600{color:var(--color-red-600)}.text-secondary-foreground{color:var(--frankie-secondary-foreground)}.underline-offset-4{text-underline-offset:4px}.opacity-60{opacity:.6}.opacity-70{opacity:.7}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-none{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.placeholder\:text-muted-foreground::placeholder{color:var(--frankie-muted-foreground)}@media (hover:hover){.hover\:border-border:hover{border-color:var(--frankie-border)}.hover\:bg-muted:hover{background-color:var(--frankie-muted)}.hover\:text-foreground:hover{color:var(--frankie-foreground)}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-90:hover{opacity:.9}.hover\:opacity-100:hover{opacity:1}}.focus-visible\:ring-2:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus-visible\:ring-primary:focus-visible{--tw-ring-color:var(--frankie-primary)}.focus-visible\:outline-none:focus-visible{--tw-outline-style:none;outline-style:none}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}.dark\:text-emerald-400:where(.dark,.dark *){color:var(--color-emerald-400)}.dark\:text-red-400:where(.dark,.dark *){color:var(--color-red-400)}.\[\&_svg\]\:pointer-events-none svg{pointer-events:none}.\[\&_svg\]\:size-4 svg{width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}.\[\&_svg\]\:shrink-0 svg{flex-shrink:0}.\[\&\:\:-webkit-details-marker\]\:hidden::-webkit-details-marker{display:none}}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse{50%{opacity:.5}}
|
package/dist/index.cjs
CHANGED
|
@@ -34,6 +34,7 @@ __export(index_exports, {
|
|
|
34
34
|
Composer: () => Composer,
|
|
35
35
|
FRANKIE_CLOUD_URL: () => FRANKIE_CLOUD_URL,
|
|
36
36
|
FrankieApiError: () => FrankieApiError,
|
|
37
|
+
FrankieChatPage: () => FrankieChatPage,
|
|
37
38
|
FrankieClient: () => FrankieClient,
|
|
38
39
|
FrankieInline: () => FrankieInline,
|
|
39
40
|
FrankieProvider: () => FrankieProvider,
|
|
@@ -89,13 +90,16 @@ var FrankieClient = class {
|
|
|
89
90
|
get sessionId() {
|
|
90
91
|
return this.session?.sessionId;
|
|
91
92
|
}
|
|
93
|
+
get conversationId() {
|
|
94
|
+
return this.options.conversationId;
|
|
95
|
+
}
|
|
92
96
|
/** Whether this client persists across reloads (browser only, opt-out via `persist:false`). */
|
|
93
97
|
get persist() {
|
|
94
98
|
return this.options.persist !== false && typeof window !== "undefined";
|
|
95
99
|
}
|
|
96
|
-
/** localStorage namespace for this tenant, shared with the chat hook. */
|
|
100
|
+
/** localStorage namespace for this tenant/conversation, shared with the chat hook. */
|
|
97
101
|
get namespace() {
|
|
98
|
-
return `frankie.${this.options.publishableKey}`;
|
|
102
|
+
return this.options.storageNamespace ?? `frankie.${this.options.publishableKey}`;
|
|
99
103
|
}
|
|
100
104
|
readStored() {
|
|
101
105
|
if (!this.persist) return void 0;
|
|
@@ -123,30 +127,80 @@ var FrankieClient = class {
|
|
|
123
127
|
});
|
|
124
128
|
return await this.unwrap(response);
|
|
125
129
|
}
|
|
126
|
-
/** Opens (or reuses) a chat session. */
|
|
127
130
|
async ensureSession(context) {
|
|
128
|
-
if (this.session && new Date(this.session.expiresAt).getTime() > Date.now() + 3e4)
|
|
129
|
-
return this.session;
|
|
130
|
-
}
|
|
131
|
+
if (this.session && new Date(this.session.expiresAt).getTime() > Date.now() + 3e4) return this.session;
|
|
131
132
|
const userToken = await this.options.getUserToken?.();
|
|
133
|
+
if (this.options.getUserToken && !userToken) {
|
|
134
|
+
throw new FrankieApiError(
|
|
135
|
+
"Your signed-in Frankie session could not be verified. Refresh the page and sign in again.",
|
|
136
|
+
401,
|
|
137
|
+
"user_token_unavailable"
|
|
138
|
+
);
|
|
139
|
+
}
|
|
132
140
|
const response = await this.doFetch(`${this.baseUrl}/v1/session`, {
|
|
133
141
|
method: "POST",
|
|
134
142
|
headers: { "content-type": "application/json", "x-frankie-key": this.options.publishableKey },
|
|
135
|
-
body: JSON.stringify({
|
|
143
|
+
body: JSON.stringify({
|
|
144
|
+
...userToken ? { userToken } : {},
|
|
145
|
+
...context ? { context } : {},
|
|
146
|
+
...this.options.conversationId ? { conversationId: this.options.conversationId } : {}
|
|
147
|
+
})
|
|
136
148
|
});
|
|
137
149
|
this.session = await this.unwrap(response);
|
|
138
150
|
this.writeStored();
|
|
139
151
|
return this.session;
|
|
140
152
|
}
|
|
141
|
-
|
|
153
|
+
async listConversations() {
|
|
154
|
+
const userToken = await this.requireUserToken();
|
|
155
|
+
const response = await this.doFetch(`${this.baseUrl}/v1/conversations`, {
|
|
156
|
+
headers: { "x-frankie-key": this.options.publishableKey, authorization: `Bearer ${userToken}` }
|
|
157
|
+
});
|
|
158
|
+
const body = await this.unwrap(response);
|
|
159
|
+
return Array.isArray(body.conversations) ? body.conversations : [];
|
|
160
|
+
}
|
|
161
|
+
async resumeConversation(id = this.options.conversationId) {
|
|
162
|
+
if (!id) return void 0;
|
|
163
|
+
const userToken = await this.requireUserToken();
|
|
164
|
+
const response = await this.doFetch(`${this.baseUrl}/v1/conversations/${encodeURIComponent(id)}/resume`, {
|
|
165
|
+
method: "POST",
|
|
166
|
+
headers: { "x-frankie-key": this.options.publishableKey, authorization: `Bearer ${userToken}` }
|
|
167
|
+
});
|
|
168
|
+
if (response.status === 404) return void 0;
|
|
169
|
+
const body = await this.unwrap(response);
|
|
170
|
+
this.session = body.session;
|
|
171
|
+
this.writeStored();
|
|
172
|
+
return { messages: Array.isArray(body.messages) ? body.messages : [], title: body.title ?? "New chat" };
|
|
173
|
+
}
|
|
174
|
+
async syncConversation(messages, title) {
|
|
175
|
+
if (!this.session || !this.options.conversationId) return;
|
|
176
|
+
const response = await this.doFetch(`${this.baseUrl}/v1/conversations/${encodeURIComponent(this.options.conversationId)}`, {
|
|
177
|
+
method: "PUT",
|
|
178
|
+
headers: {
|
|
179
|
+
"content-type": "application/json",
|
|
180
|
+
"x-frankie-key": this.options.publishableKey,
|
|
181
|
+
authorization: `Bearer ${this.session.sessionToken}`
|
|
182
|
+
},
|
|
183
|
+
body: JSON.stringify({ messages, ...title ? { title } : {} })
|
|
184
|
+
});
|
|
185
|
+
await this.unwrap(response);
|
|
186
|
+
}
|
|
187
|
+
async deleteConversation(id) {
|
|
188
|
+
const userToken = await this.requireUserToken();
|
|
189
|
+
const response = await this.doFetch(`${this.baseUrl}/v1/conversations/${encodeURIComponent(id)}`, {
|
|
190
|
+
method: "DELETE",
|
|
191
|
+
headers: { "x-frankie-key": this.options.publishableKey, authorization: `Bearer ${userToken}` }
|
|
192
|
+
});
|
|
193
|
+
if (response.status !== 404) await this.unwrap(response);
|
|
194
|
+
}
|
|
195
|
+
async requireUserToken() {
|
|
196
|
+
const token = await this.options.getUserToken?.();
|
|
197
|
+
if (!token) throw new FrankieApiError("Sign in to access your chat history.", 401, "unauthorized");
|
|
198
|
+
return token;
|
|
199
|
+
}
|
|
142
200
|
resetSession() {
|
|
143
201
|
this.session = void 0;
|
|
144
202
|
this.writeStored();
|
|
145
203
|
}
|
|
146
|
-
/**
|
|
147
|
-
* Sends a message and invokes `onEvent` for each server event as it arrives.
|
|
148
|
-
* Resolves when the turn ends.
|
|
149
|
-
*/
|
|
150
204
|
async send(input, onEvent) {
|
|
151
205
|
const session = await this.ensureSession(input.context);
|
|
152
206
|
const body = {
|
|
@@ -232,9 +286,8 @@ function FrankieProvider({
|
|
|
232
286
|
}) {
|
|
233
287
|
const client = (0, import_react.useMemo)(
|
|
234
288
|
() => new FrankieClient(clientOptions),
|
|
235
|
-
// The client holds session state; rebuild it only when the endpoint changes.
|
|
236
289
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
237
|
-
[clientOptions.baseUrl, clientOptions.publishableKey]
|
|
290
|
+
[clientOptions.baseUrl, clientOptions.publishableKey, clientOptions.storageNamespace, clientOptions.conversationId]
|
|
238
291
|
);
|
|
239
292
|
const [config, setConfig] = (0, import_react.useState)(providedConfig);
|
|
240
293
|
const [configError, setConfigError] = (0, import_react.useState)(void 0);
|
|
@@ -360,6 +413,7 @@ function useFrankieChat() {
|
|
|
360
413
|
const [messages, setMessages] = (0, import_react2.useState)([]);
|
|
361
414
|
const [busy, setBusy] = (0, import_react2.useState)(false);
|
|
362
415
|
const [error, setError] = (0, import_react2.useState)(void 0);
|
|
416
|
+
const [restored, setRestored] = (0, import_react2.useState)(false);
|
|
363
417
|
const abortRef = (0, import_react2.useRef)(null);
|
|
364
418
|
const mounted = (0, import_react2.useRef)(true);
|
|
365
419
|
(0, import_react2.useEffect)(() => {
|
|
@@ -371,22 +425,38 @@ function useFrankieChat() {
|
|
|
371
425
|
}, []);
|
|
372
426
|
const storageKey = `${client.namespace}.messages`;
|
|
373
427
|
(0, import_react2.useEffect)(() => {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
428
|
+
let active = true;
|
|
429
|
+
const restore = async () => {
|
|
430
|
+
if (client.persist) {
|
|
431
|
+
try {
|
|
432
|
+
const raw = window.localStorage.getItem(storageKey);
|
|
433
|
+
const stored = raw ? JSON.parse(raw) : [];
|
|
434
|
+
if (Array.isArray(stored) && stored.length && active) setMessages(stored);
|
|
435
|
+
} catch {
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
try {
|
|
439
|
+
const remote = await client.resumeConversation();
|
|
440
|
+
if (active && remote?.messages.length) setMessages(remote.messages);
|
|
441
|
+
} catch {
|
|
442
|
+
} finally {
|
|
443
|
+
if (active) setRestored(true);
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
void restore();
|
|
447
|
+
return () => {
|
|
448
|
+
active = false;
|
|
449
|
+
};
|
|
450
|
+
}, [client, storageKey]);
|
|
382
451
|
(0, import_react2.useEffect)(() => {
|
|
383
|
-
if (!client.persist || busy) return;
|
|
452
|
+
if (!client.persist || !restored || busy) return;
|
|
384
453
|
try {
|
|
385
454
|
if (messages.length) window.localStorage.setItem(storageKey, JSON.stringify(messages));
|
|
386
455
|
else window.localStorage.removeItem(storageKey);
|
|
387
456
|
} catch {
|
|
388
457
|
}
|
|
389
|
-
|
|
458
|
+
if (messages.length) void client.syncConversation(messages, conversationTitle(messages)).catch(() => void 0);
|
|
459
|
+
}, [messages, busy, client, storageKey, restored]);
|
|
390
460
|
const patchAssistant = (0, import_react2.useCallback)((id, update) => {
|
|
391
461
|
setMessages(
|
|
392
462
|
(current) => current.map(
|
|
@@ -415,6 +485,8 @@ function useFrankieChat() {
|
|
|
415
485
|
const abort = new AbortController();
|
|
416
486
|
abortRef.current = abort;
|
|
417
487
|
try {
|
|
488
|
+
let receivedText = false;
|
|
489
|
+
let receivedToolActivity = false;
|
|
418
490
|
await client.send(
|
|
419
491
|
{
|
|
420
492
|
message: outgoing,
|
|
@@ -425,9 +497,18 @@ function useFrankieChat() {
|
|
|
425
497
|
},
|
|
426
498
|
(event) => {
|
|
427
499
|
if (event.type === "ping" || event.type === "session") return;
|
|
500
|
+
if (event.type === "text_delta") receivedText = true;
|
|
501
|
+
if (event.type === "tool_start" || event.type === "tool_end") receivedToolActivity = true;
|
|
428
502
|
patchAssistant(assistantId, (message) => applyEvent(message, event));
|
|
429
503
|
}
|
|
430
504
|
);
|
|
505
|
+
if (!receivedText) {
|
|
506
|
+
patchAssistant(assistantId, (message) => ({
|
|
507
|
+
...message,
|
|
508
|
+
status: "done",
|
|
509
|
+
text: receivedToolActivity ? "Completed. Open Agent activity for the details." : "Frankie didn\u2019t return an answer. Please try again."
|
|
510
|
+
}));
|
|
511
|
+
}
|
|
431
512
|
} catch (caught) {
|
|
432
513
|
if (abort.signal.aborted) {
|
|
433
514
|
patchAssistant(assistantId, (message) => ({
|
|
@@ -436,7 +517,7 @@ function useFrankieChat() {
|
|
|
436
517
|
text: message.text || "Stopped."
|
|
437
518
|
}));
|
|
438
519
|
} else {
|
|
439
|
-
const message = caught instanceof FrankieApiError ? caught.message : "Could not reach the assistant. Check your connection and try again.";
|
|
520
|
+
const message = caught instanceof FrankieApiError ? caught.message : caught instanceof Error && caught.message.startsWith("Frankie") ? caught.message : "Could not reach the assistant. Check your connection and try again.";
|
|
440
521
|
if (mounted.current) setError(message);
|
|
441
522
|
patchAssistant(assistantId, (current) => ({ ...current, status: "error", error: message }));
|
|
442
523
|
}
|
|
@@ -460,6 +541,7 @@ function useFrankieChat() {
|
|
|
460
541
|
setMessages([]);
|
|
461
542
|
setError(void 0);
|
|
462
543
|
setBusy(false);
|
|
544
|
+
setRestored(true);
|
|
463
545
|
try {
|
|
464
546
|
if (client.persist) window.localStorage.removeItem(`${client.namespace}.messages`);
|
|
465
547
|
} catch {
|
|
@@ -467,10 +549,16 @@ function useFrankieChat() {
|
|
|
467
549
|
}, [client]);
|
|
468
550
|
return { messages, busy, ...error ? { error } : {}, send, stop, reset };
|
|
469
551
|
}
|
|
552
|
+
function conversationTitle(messages) {
|
|
553
|
+
const first = messages.find((message) => message.role === "user" && message.text.trim());
|
|
554
|
+
if (!first) return void 0;
|
|
555
|
+
const title = first.text.trim().replace(/\s+/g, " ");
|
|
556
|
+
return title.length > 42 ? `${title.slice(0, 42)}\u2026` : title;
|
|
557
|
+
}
|
|
470
558
|
|
|
471
559
|
// src/components/chat/FrankieWidget.tsx
|
|
472
560
|
var import_lucide_react5 = require("lucide-react");
|
|
473
|
-
var
|
|
561
|
+
var import_react9 = require("react");
|
|
474
562
|
|
|
475
563
|
// src/lib/theme.ts
|
|
476
564
|
function themeToCssVars(theme) {
|
|
@@ -531,7 +619,7 @@ function Button({ className, variant, size, asChild = false, ...props }) {
|
|
|
531
619
|
|
|
532
620
|
// src/components/chat/ChatPanel.tsx
|
|
533
621
|
var import_lucide_react4 = require("lucide-react");
|
|
534
|
-
var
|
|
622
|
+
var import_react8 = require("react");
|
|
535
623
|
|
|
536
624
|
// src/components/ui/avatar.tsx
|
|
537
625
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
@@ -793,6 +881,7 @@ function readAsBase64(file) {
|
|
|
793
881
|
|
|
794
882
|
// src/components/chat/MessageBubble.tsx
|
|
795
883
|
var import_lucide_react3 = require("lucide-react");
|
|
884
|
+
var import_react7 = require("react");
|
|
796
885
|
|
|
797
886
|
// src/components/chat/Markdown.tsx
|
|
798
887
|
var import_react6 = require("react");
|
|
@@ -928,7 +1017,20 @@ function Badge({
|
|
|
928
1017
|
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
929
1018
|
function ToolActivityList({ tools, renderTool }) {
|
|
930
1019
|
if (tools.length === 0) return null;
|
|
931
|
-
|
|
1020
|
+
const running = tools.some((tool) => tool.status === "running");
|
|
1021
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("details", { className: "frankie-tool-activity mt-2", open: running, children: [
|
|
1022
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.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: [
|
|
1023
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react2.ChevronDown, { className: "frankie-tool-chevron size-3.5 transition-transform", "aria-hidden": "true" }),
|
|
1024
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: running ? "Working" : "Activity" }),
|
|
1025
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "text-[11px]", children: [
|
|
1026
|
+
"\xB7 ",
|
|
1027
|
+
tools.length,
|
|
1028
|
+
" tool call",
|
|
1029
|
+
tools.length === 1 ? "" : "s"
|
|
1030
|
+
] })
|
|
1031
|
+
] }),
|
|
1032
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("ul", { className: "mt-1 flex flex-col gap-1 border-l border-border pl-2", children: tools.map((tool) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("li", { children: renderTool ? renderTool(tool) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ToolActivityRow, { tool }) }, tool.id)) })
|
|
1033
|
+
] });
|
|
932
1034
|
}
|
|
933
1035
|
function ToolActivityRow({ tool }) {
|
|
934
1036
|
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Badge, { variant: badgeVariant(tool.status), title: tool.detail ?? tool.name, children: [
|
|
@@ -977,18 +1079,36 @@ function MessageBubble({
|
|
|
977
1079
|
},
|
|
978
1080
|
`${file.name}-${index}`
|
|
979
1081
|
)) }) : null,
|
|
980
|
-
message.text ? /* @__PURE__ */ (0, import_jsx_runtime11.
|
|
1082
|
+
message.text ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex max-w-[85%] items-end gap-1", children: [
|
|
1083
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "rounded-[var(--frankie-radius)] bg-primary px-3 py-2 text-sm text-primary-foreground", children: message.text }),
|
|
1084
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(MessageCopyButton, { text: message.text })
|
|
1085
|
+
] }) : null
|
|
981
1086
|
] });
|
|
982
1087
|
}
|
|
983
1088
|
const empty = message.text.length === 0 && message.tools.length === 0;
|
|
984
1089
|
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex gap-2", children: [
|
|
985
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.
|
|
1090
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "frankie-assistant-actions mt-0.5 flex shrink-0 flex-col items-center gap-1", children: [
|
|
1091
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Avatar, { ...avatarUrl ? { src: avatarUrl } : {}, name: assistantName }),
|
|
1092
|
+
message.text ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(MessageCopyButton, { text: message.text }) : null
|
|
1093
|
+
] }),
|
|
986
1094
|
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "min-w-0 max-w-[85%]", children: [
|
|
987
1095
|
message.status === "error" ? /* @__PURE__ */ (0, import_jsx_runtime11.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_runtime11.jsx)(TypingDots, {}) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Markdown, { text: message.text, className: "text-sm" }),
|
|
988
1096
|
showToolActivity ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ToolActivityList, { tools: message.tools, ...renderTool ? { renderTool } : {} }) : null
|
|
989
1097
|
] })
|
|
990
1098
|
] });
|
|
991
1099
|
}
|
|
1100
|
+
function MessageCopyButton({ text }) {
|
|
1101
|
+
const [copied, setCopied] = (0, import_react7.useState)(false);
|
|
1102
|
+
const copy = async () => {
|
|
1103
|
+
try {
|
|
1104
|
+
await navigator.clipboard.writeText(text);
|
|
1105
|
+
setCopied(true);
|
|
1106
|
+
window.setTimeout(() => setCopied(false), 1500);
|
|
1107
|
+
} catch {
|
|
1108
|
+
}
|
|
1109
|
+
};
|
|
1110
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.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_runtime11.jsx)(import_lucide_react3.Check, { className: "size-3.5", "aria-hidden": "true" }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react3.Copy, { className: "size-3.5", "aria-hidden": "true" }) });
|
|
1111
|
+
}
|
|
992
1112
|
function TypingDots() {
|
|
993
1113
|
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "inline-flex items-center gap-1 py-2", role: "status", "aria-label": "Thinking", children: [0, 1, 2].map((index) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
994
1114
|
"span",
|
|
@@ -1039,11 +1159,11 @@ function ChatPanel({
|
|
|
1039
1159
|
const { config, configError, loading } = useFrankie();
|
|
1040
1160
|
const theme = useFrankieTheme();
|
|
1041
1161
|
const { messages, busy, error, send, stop, reset } = useFrankieChat();
|
|
1042
|
-
const scrollRef = (0,
|
|
1162
|
+
const scrollRef = (0, import_react8.useRef)(null);
|
|
1043
1163
|
const features = config?.features;
|
|
1044
1164
|
const pickerModels = features?.modelPicker.enabled ? features.modelPicker.models : [];
|
|
1045
|
-
const [model, setModel] = (0,
|
|
1046
|
-
(0,
|
|
1165
|
+
const [model, setModel] = (0, import_react8.useState)(void 0);
|
|
1166
|
+
(0, import_react8.useEffect)(() => {
|
|
1047
1167
|
const node = scrollRef.current;
|
|
1048
1168
|
if (!node) return;
|
|
1049
1169
|
const distanceFromBottom = node.scrollHeight - node.scrollTop - node.clientHeight;
|
|
@@ -1072,7 +1192,11 @@ function ChatPanel({
|
|
|
1072
1192
|
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Button, { size: "icon", variant: "ghost", onClick: reset, "aria-label": "Start a new conversation", title: "New conversation", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react4.RotateCcw, {}) }),
|
|
1073
1193
|
onClose ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Button, { size: "icon", variant: "ghost", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react4.X, {}) }) : null
|
|
1074
1194
|
] }),
|
|
1075
|
-
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(CardContent, { children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ScrollArea, { ref: scrollRef, className: "flex h-full flex-col gap-4 p-4", "aria-live": "polite", children: configError ? /* @__PURE__ */ (0, import_jsx_runtime13.
|
|
1195
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(CardContent, { children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ScrollArea, { ref: scrollRef, className: "flex h-full flex-col gap-4 p-4", "aria-live": "polite", children: configError ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "frankie-config-error", role: "alert", children: [
|
|
1196
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("strong", { children: "Frankie couldn\u2019t start" }),
|
|
1197
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { children: configError }),
|
|
1198
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { children: "Refresh this page after checking the workspace\u2019s allowed origin and sign-in settings." })
|
|
1199
|
+
] }) : loading ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { className: "text-sm text-muted-foreground", children: "Starting Frankie\u2026" }) : messages.length === 0 ? emptyState ?? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex flex-col gap-4", children: [
|
|
1076
1200
|
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { className: "text-sm text-muted-foreground", children: config?.greeting }),
|
|
1077
1201
|
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(SuggestedPrompts, { prompts: config?.suggestedPrompts ?? [], onPick: send, disabled: busy })
|
|
1078
1202
|
] }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "flex flex-col gap-4", children: messages.map(
|
|
@@ -1123,13 +1247,13 @@ function FrankieWidget({
|
|
|
1123
1247
|
}) {
|
|
1124
1248
|
const { config } = useFrankie();
|
|
1125
1249
|
const theme = useFrankieTheme();
|
|
1126
|
-
const [uncontrolledOpen, setUncontrolledOpen] = (0,
|
|
1250
|
+
const [uncontrolledOpen, setUncontrolledOpen] = (0, import_react9.useState)(defaultOpen);
|
|
1127
1251
|
const open = controlledOpen ?? uncontrolledOpen;
|
|
1128
1252
|
const setOpen = (next) => {
|
|
1129
1253
|
if (controlledOpen === void 0) setUncontrolledOpen(next);
|
|
1130
1254
|
onOpenChange?.(next);
|
|
1131
1255
|
};
|
|
1132
|
-
(0,
|
|
1256
|
+
(0, import_react9.useEffect)(() => {
|
|
1133
1257
|
if (!open) return;
|
|
1134
1258
|
const onKeyDown = (event) => {
|
|
1135
1259
|
if (event.key === "Escape") setOpen(false);
|
|
@@ -1181,10 +1305,45 @@ function FrankieInline({ className, style, ...panelProps }) {
|
|
|
1181
1305
|
);
|
|
1182
1306
|
}
|
|
1183
1307
|
|
|
1184
|
-
// src/components/
|
|
1308
|
+
// src/components/chat/FrankieChatPage.tsx
|
|
1185
1309
|
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
1310
|
+
function FrankieChatPage({
|
|
1311
|
+
className,
|
|
1312
|
+
style,
|
|
1313
|
+
header,
|
|
1314
|
+
footer,
|
|
1315
|
+
emptyState,
|
|
1316
|
+
renderMessage,
|
|
1317
|
+
renderTool,
|
|
1318
|
+
placeholder,
|
|
1319
|
+
autoFocus,
|
|
1320
|
+
...providerProps
|
|
1321
|
+
}) {
|
|
1322
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1323
|
+
"main",
|
|
1324
|
+
{
|
|
1325
|
+
className,
|
|
1326
|
+
style: { height: "100dvh", minHeight: "100dvh", width: "100%", ...style },
|
|
1327
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(FrankieProvider, { ...providerProps, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1328
|
+
FrankieInline,
|
|
1329
|
+
{
|
|
1330
|
+
header,
|
|
1331
|
+
footer,
|
|
1332
|
+
emptyState,
|
|
1333
|
+
renderMessage,
|
|
1334
|
+
renderTool,
|
|
1335
|
+
placeholder,
|
|
1336
|
+
autoFocus
|
|
1337
|
+
}
|
|
1338
|
+
) })
|
|
1339
|
+
}
|
|
1340
|
+
);
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
// src/components/ui/separator.tsx
|
|
1344
|
+
var import_jsx_runtime17 = require("react/jsx-runtime");
|
|
1186
1345
|
function Separator({ className }) {
|
|
1187
|
-
return /* @__PURE__ */ (0,
|
|
1346
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { role: "separator", className: cn("h-px w-full bg-border", className) });
|
|
1188
1347
|
}
|
|
1189
1348
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1190
1349
|
0 && (module.exports = {
|
|
@@ -1201,6 +1360,7 @@ function Separator({ className }) {
|
|
|
1201
1360
|
Composer,
|
|
1202
1361
|
FRANKIE_CLOUD_URL,
|
|
1203
1362
|
FrankieApiError,
|
|
1363
|
+
FrankieChatPage,
|
|
1204
1364
|
FrankieClient,
|
|
1205
1365
|
FrankieInline,
|
|
1206
1366
|
FrankieProvider,
|