@frankieai/react 0.1.1 → 0.1.5
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 +280 -51
- 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 +32 -18
- package/dist/index.d.ts +32 -18
- package/dist/index.js +278 -50
- 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 +121 -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 +91 -28
- package/src/index.ts +2 -0
- package/src/lib/client.ts +82 -17
- package/src/styles/frankie.css +173 -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}.frankie-chat-page{background:radial-gradient(60rem 24rem at 50% -12rem, var(--frankie-primary), transparent 65%), var(--frankie-background);flex-direction:column;min-width:0;display:flex;overflow:hidden}@supports (color:color-mix(in lab, red, red)){.frankie-chat-page{background:radial-gradient(60rem 24rem at 50% -12rem, color-mix(in srgb, var(--frankie-primary) 8%, transparent), transparent 65%), var(--frankie-background)}}.frankie-chat-page{color:var(--frankie-foreground)}.frankie-page-header{border-bottom:1px solid var(--frankie-border);background:var(--frankie-background);flex:0 0 56px;justify-content:space-between;align-items:center;gap:16px;min-height:56px;padding:0 24px;display:flex}@supports (color:color-mix(in lab, red, red)){.frankie-page-header{background:color-mix(in srgb, var(--frankie-background) 92%, transparent)}}.frankie-page-header{-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px)}.frankie-page-header>div{align-items:center;gap:10px;display:flex}.frankie-page-header strong{font-size:.925rem;font-weight:650}.frankie-page-status{background:var(--frankie-primary);width:10px;height:10px;box-shadow:0 0 0 4px var(--frankie-primary);border-radius:999px}@supports (color:color-mix(in lab, red, red)){.frankie-page-status{box-shadow:0 0 0 4px color-mix(in srgb, var(--frankie-primary) 16%, transparent)}}.frankie-page-header button{border:1px solid var(--frankie-border);border-radius:calc(var(--frankie-radius) * .9);background:var(--frankie-secondary);cursor:pointer;min-height:36px;padding:0 13px}.frankie-page-header button:hover{border-color:var(--frankie-primary)}.frankie-page-canvas{flex:1;min-width:0;min-height:0;display:flex}.frankie-page-canvas>.frankie-inline{width:100%;max-width:56rem;margin:0 auto}.frankie-page-canvas .frankie-scroll{padding:2rem clamp(1rem,4vw,4rem) 1.25rem}.frankie-page-canvas .frankie-root>.border-t{background:linear-gradient(180deg, transparent, var(--frankie-background) 30%);border-top:0;padding:.75rem clamp(1rem,4vw,4rem) 1.5rem}.frankie-page-empty{text-align:center;flex-direction:column;justify-content:center;align-items:center;gap:1rem;width:min(100%,38rem);min-height:100%;margin:auto;padding:clamp(2rem,7vh,4.5rem) 1rem 2rem;display:flex}.frankie-page-avatar{border-radius:calc(var(--frankie-radius) * 1.4);background:var(--frankie-primary);width:48px;height:48px;color:var(--frankie-primary-foreground);place-items:center;font-weight:750;display:grid}.frankie-page-kicker{color:var(--frankie-primary);letter-spacing:.1em;text-transform:uppercase;margin:0 0 .4rem;font-size:.7rem;font-weight:750}.frankie-page-empty h1{letter-spacing:-.045em;margin:0;font-size:clamp(1.65rem,4vw,2.15rem);line-height:1.08}.frankie-page-empty h1+p{max-width:30rem;color:var(--frankie-muted-foreground);margin:.6rem auto 0}.frankie-page-prompts{gap:.55rem;width:100%;margin-top:.4rem;display:grid}.frankie-page-prompts button{border:1px solid var(--frankie-border);border-radius:calc(var(--frankie-radius) * 1.25);background:var(--frankie-secondary);text-align:left;cursor:pointer;grid-template-columns:2rem minmax(0,1fr) auto;align-items:center;gap:.65rem;min-height:60px;padding:.75rem .9rem;display:grid}.frankie-page-prompts button:hover{border-color:var(--frankie-primary)}@supports (color:color-mix(in lab, red, red)){.frankie-page-prompts button:hover{border-color:color-mix(in srgb, var(--frankie-primary) 58%, var(--frankie-border))}}.frankie-page-prompts button:hover{transform:translateY(-1px)}.frankie-page-prompts button>span{color:var(--frankie-muted-foreground);font-size:.68rem;font-weight:700}.frankie-page-prompts button>strong{font-size:.875rem}.frankie-page-prompts button>b{color:var(--frankie-primary);font-size:1rem}@media (max-width:640px){.frankie-page-header{flex-basis:52px;min-height:52px;padding:0 12px}.frankie-page-canvas .frankie-scroll{padding:1.25rem .75rem .5rem}.frankie-page-canvas .frankie-root>.border-t{padding:.65rem .75rem calc(.65rem + env(safe-area-inset-bottom))}}}@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,8 +413,10 @@ 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);
|
|
419
|
+
const messagesRef = (0, import_react2.useRef)([]);
|
|
365
420
|
(0, import_react2.useEffect)(() => {
|
|
366
421
|
mounted.current = true;
|
|
367
422
|
return () => {
|
|
@@ -371,28 +426,51 @@ function useFrankieChat() {
|
|
|
371
426
|
}, []);
|
|
372
427
|
const storageKey = `${client.namespace}.messages`;
|
|
373
428
|
(0, import_react2.useEffect)(() => {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
429
|
+
let active = true;
|
|
430
|
+
const restore = async () => {
|
|
431
|
+
if (client.persist) {
|
|
432
|
+
try {
|
|
433
|
+
const raw = window.localStorage.getItem(storageKey);
|
|
434
|
+
const stored = raw ? JSON.parse(raw) : [];
|
|
435
|
+
if (Array.isArray(stored) && stored.length && active) {
|
|
436
|
+
messagesRef.current = stored;
|
|
437
|
+
setMessages(stored);
|
|
438
|
+
}
|
|
439
|
+
} catch {
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
try {
|
|
443
|
+
const remote = await client.resumeConversation();
|
|
444
|
+
if (active && remote?.messages.length) {
|
|
445
|
+
const restoredMessages = remote.messages;
|
|
446
|
+
messagesRef.current = restoredMessages;
|
|
447
|
+
setMessages(restoredMessages);
|
|
448
|
+
}
|
|
449
|
+
} catch {
|
|
450
|
+
} finally {
|
|
451
|
+
if (active) setRestored(true);
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
void restore();
|
|
455
|
+
return () => {
|
|
456
|
+
active = false;
|
|
457
|
+
};
|
|
458
|
+
}, [client, storageKey]);
|
|
382
459
|
(0, import_react2.useEffect)(() => {
|
|
383
|
-
if (!client.persist || busy) return;
|
|
460
|
+
if (!client.persist || !restored || busy) return;
|
|
384
461
|
try {
|
|
385
462
|
if (messages.length) window.localStorage.setItem(storageKey, JSON.stringify(messages));
|
|
386
463
|
else window.localStorage.removeItem(storageKey);
|
|
387
464
|
} catch {
|
|
388
465
|
}
|
|
389
|
-
|
|
466
|
+
if (messages.length) void client.syncConversation(messages, conversationTitle(messages)).catch(() => void 0);
|
|
467
|
+
}, [messages, busy, client, storageKey, restored]);
|
|
390
468
|
const patchAssistant = (0, import_react2.useCallback)((id, update) => {
|
|
391
|
-
|
|
392
|
-
(
|
|
393
|
-
(message) => message.id === id && message.role === "assistant" ? update(message) : message
|
|
394
|
-
)
|
|
469
|
+
const next = messagesRef.current.map(
|
|
470
|
+
(message) => message.id === id && message.role === "assistant" ? update(message) : message
|
|
395
471
|
);
|
|
472
|
+
messagesRef.current = next;
|
|
473
|
+
setMessages(next);
|
|
396
474
|
}, []);
|
|
397
475
|
const send = (0, import_react2.useCallback)(
|
|
398
476
|
async (text, opts) => {
|
|
@@ -403,18 +481,22 @@ function useFrankieChat() {
|
|
|
403
481
|
const assistantId = nextId("a");
|
|
404
482
|
setError(void 0);
|
|
405
483
|
setBusy(true);
|
|
406
|
-
|
|
407
|
-
...current,
|
|
484
|
+
const next = [
|
|
485
|
+
...messagesRef.current,
|
|
408
486
|
newUserMessage(
|
|
409
487
|
nextId("u"),
|
|
410
488
|
trimmed,
|
|
411
489
|
attachments.map((file) => ({ name: file.name, mimeType: file.mimeType }))
|
|
412
490
|
),
|
|
413
491
|
newAssistantMessage(assistantId)
|
|
414
|
-
]
|
|
492
|
+
];
|
|
493
|
+
messagesRef.current = next;
|
|
494
|
+
setMessages(next);
|
|
415
495
|
const abort = new AbortController();
|
|
416
496
|
abortRef.current = abort;
|
|
417
497
|
try {
|
|
498
|
+
let receivedText = false;
|
|
499
|
+
let receivedToolActivity = false;
|
|
418
500
|
await client.send(
|
|
419
501
|
{
|
|
420
502
|
message: outgoing,
|
|
@@ -425,9 +507,18 @@ function useFrankieChat() {
|
|
|
425
507
|
},
|
|
426
508
|
(event) => {
|
|
427
509
|
if (event.type === "ping" || event.type === "session") return;
|
|
510
|
+
if (event.type === "text_delta") receivedText = true;
|
|
511
|
+
if (event.type === "tool_start" || event.type === "tool_end") receivedToolActivity = true;
|
|
428
512
|
patchAssistant(assistantId, (message) => applyEvent(message, event));
|
|
429
513
|
}
|
|
430
514
|
);
|
|
515
|
+
if (!receivedText) {
|
|
516
|
+
patchAssistant(assistantId, (message) => ({
|
|
517
|
+
...message,
|
|
518
|
+
status: "done",
|
|
519
|
+
text: receivedToolActivity ? "Completed. Open Agent activity for the details." : "Frankie didn\u2019t return an answer. Please try again."
|
|
520
|
+
}));
|
|
521
|
+
}
|
|
431
522
|
} catch (caught) {
|
|
432
523
|
if (abort.signal.aborted) {
|
|
433
524
|
patchAssistant(assistantId, (message) => ({
|
|
@@ -436,18 +527,20 @@ function useFrankieChat() {
|
|
|
436
527
|
text: message.text || "Stopped."
|
|
437
528
|
}));
|
|
438
529
|
} else {
|
|
439
|
-
const message = caught instanceof FrankieApiError ? caught.message : "Could not reach the assistant. Check your connection and try again.";
|
|
530
|
+
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
531
|
if (mounted.current) setError(message);
|
|
441
532
|
patchAssistant(assistantId, (current) => ({ ...current, status: "error", error: message }));
|
|
442
533
|
}
|
|
443
534
|
} finally {
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
535
|
+
patchAssistant(
|
|
536
|
+
assistantId,
|
|
537
|
+
(message) => message.status === "streaming" ? { ...message, status: "done" } : message
|
|
538
|
+
);
|
|
539
|
+
const transcript = messagesRef.current;
|
|
540
|
+
if (transcript.length) {
|
|
541
|
+
await client.syncConversation(transcript, conversationTitle(transcript)).catch(() => void 0);
|
|
450
542
|
}
|
|
543
|
+
if (mounted.current) setBusy(false);
|
|
451
544
|
abortRef.current = null;
|
|
452
545
|
}
|
|
453
546
|
},
|
|
@@ -457,9 +550,11 @@ function useFrankieChat() {
|
|
|
457
550
|
const reset = (0, import_react2.useCallback)(() => {
|
|
458
551
|
abortRef.current?.abort();
|
|
459
552
|
client.resetSession();
|
|
553
|
+
messagesRef.current = [];
|
|
460
554
|
setMessages([]);
|
|
461
555
|
setError(void 0);
|
|
462
556
|
setBusy(false);
|
|
557
|
+
setRestored(true);
|
|
463
558
|
try {
|
|
464
559
|
if (client.persist) window.localStorage.removeItem(`${client.namespace}.messages`);
|
|
465
560
|
} catch {
|
|
@@ -467,10 +562,16 @@ function useFrankieChat() {
|
|
|
467
562
|
}, [client]);
|
|
468
563
|
return { messages, busy, ...error ? { error } : {}, send, stop, reset };
|
|
469
564
|
}
|
|
565
|
+
function conversationTitle(messages) {
|
|
566
|
+
const first = messages.find((message) => message.role === "user" && message.text.trim());
|
|
567
|
+
if (!first) return void 0;
|
|
568
|
+
const title = first.text.trim().replace(/\s+/g, " ");
|
|
569
|
+
return title.length > 42 ? `${title.slice(0, 42)}\u2026` : title;
|
|
570
|
+
}
|
|
470
571
|
|
|
471
572
|
// src/components/chat/FrankieWidget.tsx
|
|
472
573
|
var import_lucide_react5 = require("lucide-react");
|
|
473
|
-
var
|
|
574
|
+
var import_react9 = require("react");
|
|
474
575
|
|
|
475
576
|
// src/lib/theme.ts
|
|
476
577
|
function themeToCssVars(theme) {
|
|
@@ -531,7 +632,7 @@ function Button({ className, variant, size, asChild = false, ...props }) {
|
|
|
531
632
|
|
|
532
633
|
// src/components/chat/ChatPanel.tsx
|
|
533
634
|
var import_lucide_react4 = require("lucide-react");
|
|
534
|
-
var
|
|
635
|
+
var import_react8 = require("react");
|
|
535
636
|
|
|
536
637
|
// src/components/ui/avatar.tsx
|
|
537
638
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
@@ -793,6 +894,7 @@ function readAsBase64(file) {
|
|
|
793
894
|
|
|
794
895
|
// src/components/chat/MessageBubble.tsx
|
|
795
896
|
var import_lucide_react3 = require("lucide-react");
|
|
897
|
+
var import_react7 = require("react");
|
|
796
898
|
|
|
797
899
|
// src/components/chat/Markdown.tsx
|
|
798
900
|
var import_react6 = require("react");
|
|
@@ -928,7 +1030,20 @@ function Badge({
|
|
|
928
1030
|
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
929
1031
|
function ToolActivityList({ tools, renderTool }) {
|
|
930
1032
|
if (tools.length === 0) return null;
|
|
931
|
-
|
|
1033
|
+
const running = tools.some((tool) => tool.status === "running");
|
|
1034
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("details", { className: "frankie-tool-activity mt-2", open: running, children: [
|
|
1035
|
+
/* @__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: [
|
|
1036
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react2.ChevronDown, { className: "frankie-tool-chevron size-3.5 transition-transform", "aria-hidden": "true" }),
|
|
1037
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: running ? "Working" : "Activity" }),
|
|
1038
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "text-[11px]", children: [
|
|
1039
|
+
"\xB7 ",
|
|
1040
|
+
tools.length,
|
|
1041
|
+
" tool call",
|
|
1042
|
+
tools.length === 1 ? "" : "s"
|
|
1043
|
+
] })
|
|
1044
|
+
] }),
|
|
1045
|
+
/* @__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)) })
|
|
1046
|
+
] });
|
|
932
1047
|
}
|
|
933
1048
|
function ToolActivityRow({ tool }) {
|
|
934
1049
|
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Badge, { variant: badgeVariant(tool.status), title: tool.detail ?? tool.name, children: [
|
|
@@ -977,18 +1092,36 @@ function MessageBubble({
|
|
|
977
1092
|
},
|
|
978
1093
|
`${file.name}-${index}`
|
|
979
1094
|
)) }) : null,
|
|
980
|
-
message.text ? /* @__PURE__ */ (0, import_jsx_runtime11.
|
|
1095
|
+
message.text ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex max-w-[85%] items-end gap-1", children: [
|
|
1096
|
+
/* @__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 }),
|
|
1097
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(MessageCopyButton, { text: message.text })
|
|
1098
|
+
] }) : null
|
|
981
1099
|
] });
|
|
982
1100
|
}
|
|
983
1101
|
const empty = message.text.length === 0 && message.tools.length === 0;
|
|
984
1102
|
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex gap-2", children: [
|
|
985
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.
|
|
1103
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "frankie-assistant-actions mt-0.5 flex shrink-0 flex-col items-center gap-1", children: [
|
|
1104
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Avatar, { ...avatarUrl ? { src: avatarUrl } : {}, name: assistantName }),
|
|
1105
|
+
message.text ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(MessageCopyButton, { text: message.text }) : null
|
|
1106
|
+
] }),
|
|
986
1107
|
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "min-w-0 max-w-[85%]", children: [
|
|
987
1108
|
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
1109
|
showToolActivity ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ToolActivityList, { tools: message.tools, ...renderTool ? { renderTool } : {} }) : null
|
|
989
1110
|
] })
|
|
990
1111
|
] });
|
|
991
1112
|
}
|
|
1113
|
+
function MessageCopyButton({ text }) {
|
|
1114
|
+
const [copied, setCopied] = (0, import_react7.useState)(false);
|
|
1115
|
+
const copy = async () => {
|
|
1116
|
+
try {
|
|
1117
|
+
await navigator.clipboard.writeText(text);
|
|
1118
|
+
setCopied(true);
|
|
1119
|
+
window.setTimeout(() => setCopied(false), 1500);
|
|
1120
|
+
} catch {
|
|
1121
|
+
}
|
|
1122
|
+
};
|
|
1123
|
+
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" }) });
|
|
1124
|
+
}
|
|
992
1125
|
function TypingDots() {
|
|
993
1126
|
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
1127
|
"span",
|
|
@@ -1039,11 +1172,11 @@ function ChatPanel({
|
|
|
1039
1172
|
const { config, configError, loading } = useFrankie();
|
|
1040
1173
|
const theme = useFrankieTheme();
|
|
1041
1174
|
const { messages, busy, error, send, stop, reset } = useFrankieChat();
|
|
1042
|
-
const scrollRef = (0,
|
|
1175
|
+
const scrollRef = (0, import_react8.useRef)(null);
|
|
1043
1176
|
const features = config?.features;
|
|
1044
1177
|
const pickerModels = features?.modelPicker.enabled ? features.modelPicker.models : [];
|
|
1045
|
-
const [model, setModel] = (0,
|
|
1046
|
-
(0,
|
|
1178
|
+
const [model, setModel] = (0, import_react8.useState)(void 0);
|
|
1179
|
+
(0, import_react8.useEffect)(() => {
|
|
1047
1180
|
const node = scrollRef.current;
|
|
1048
1181
|
if (!node) return;
|
|
1049
1182
|
const distanceFromBottom = node.scrollHeight - node.scrollTop - node.clientHeight;
|
|
@@ -1072,7 +1205,11 @@ function ChatPanel({
|
|
|
1072
1205
|
/* @__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
1206
|
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
1207
|
] }),
|
|
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.
|
|
1208
|
+
/* @__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: [
|
|
1209
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("strong", { children: "Frankie couldn\u2019t start" }),
|
|
1210
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { children: configError }),
|
|
1211
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { children: "Refresh this page after checking the workspace\u2019s allowed origin and sign-in settings." })
|
|
1212
|
+
] }) : 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
1213
|
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { className: "text-sm text-muted-foreground", children: config?.greeting }),
|
|
1077
1214
|
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(SuggestedPrompts, { prompts: config?.suggestedPrompts ?? [], onPick: send, disabled: busy })
|
|
1078
1215
|
] }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "flex flex-col gap-4", children: messages.map(
|
|
@@ -1123,13 +1260,13 @@ function FrankieWidget({
|
|
|
1123
1260
|
}) {
|
|
1124
1261
|
const { config } = useFrankie();
|
|
1125
1262
|
const theme = useFrankieTheme();
|
|
1126
|
-
const [uncontrolledOpen, setUncontrolledOpen] = (0,
|
|
1263
|
+
const [uncontrolledOpen, setUncontrolledOpen] = (0, import_react9.useState)(defaultOpen);
|
|
1127
1264
|
const open = controlledOpen ?? uncontrolledOpen;
|
|
1128
1265
|
const setOpen = (next) => {
|
|
1129
1266
|
if (controlledOpen === void 0) setUncontrolledOpen(next);
|
|
1130
1267
|
onOpenChange?.(next);
|
|
1131
1268
|
};
|
|
1132
|
-
(0,
|
|
1269
|
+
(0, import_react9.useEffect)(() => {
|
|
1133
1270
|
if (!open) return;
|
|
1134
1271
|
const onKeyDown = (event) => {
|
|
1135
1272
|
if (event.key === "Escape") setOpen(false);
|
|
@@ -1181,10 +1318,101 @@ function FrankieInline({ className, style, ...panelProps }) {
|
|
|
1181
1318
|
);
|
|
1182
1319
|
}
|
|
1183
1320
|
|
|
1184
|
-
// src/components/
|
|
1321
|
+
// src/components/chat/FrankieChatPage.tsx
|
|
1185
1322
|
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
1323
|
+
function PageEmptyState() {
|
|
1324
|
+
const { config } = useFrankie();
|
|
1325
|
+
const { send, busy } = useFrankieChat();
|
|
1326
|
+
const name = config?.branding.name ?? "Frankie";
|
|
1327
|
+
const prompts = config?.suggestedPrompts?.slice(0, 3) ?? [];
|
|
1328
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("section", { className: "frankie-page-empty", "aria-label": "Start a conversation", children: [
|
|
1329
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "frankie-page-avatar", "aria-hidden": true, children: name.trim().slice(0, 1).toUpperCase() }),
|
|
1330
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { children: [
|
|
1331
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("p", { className: "frankie-page-kicker", children: [
|
|
1332
|
+
name,
|
|
1333
|
+
" assistant"
|
|
1334
|
+
] }),
|
|
1335
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("h1", { children: "What would you like to do?" }),
|
|
1336
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { children: config?.greeting ?? "Ask a question or choose a task to get started." })
|
|
1337
|
+
] }),
|
|
1338
|
+
prompts.length ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "frankie-page-prompts", children: prompts.map((prompt, index) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("button", { type: "button", disabled: busy, onClick: () => send(prompt), children: [
|
|
1339
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { children: String(index + 1).padStart(2, "0") }),
|
|
1340
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("strong", { children: prompt }),
|
|
1341
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("b", { "aria-hidden": true, children: "\u2197" })
|
|
1342
|
+
] }, prompt)) }) : null
|
|
1343
|
+
] });
|
|
1344
|
+
}
|
|
1345
|
+
function PageChrome({
|
|
1346
|
+
header,
|
|
1347
|
+
footer,
|
|
1348
|
+
emptyState,
|
|
1349
|
+
renderMessage,
|
|
1350
|
+
renderTool,
|
|
1351
|
+
placeholder,
|
|
1352
|
+
autoFocus
|
|
1353
|
+
}) {
|
|
1354
|
+
const { config } = useFrankie();
|
|
1355
|
+
const { reset } = useFrankieChat();
|
|
1356
|
+
const name = config?.branding.name ?? "Frankie";
|
|
1357
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
|
|
1358
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("header", { className: "frankie-page-header", children: [
|
|
1359
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { children: [
|
|
1360
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "frankie-page-status", "aria-hidden": true }),
|
|
1361
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("strong", { children: name })
|
|
1362
|
+
] }),
|
|
1363
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", onClick: reset, "aria-label": "Start a new conversation", children: "New chat" })
|
|
1364
|
+
] }),
|
|
1365
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "frankie-page-canvas", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1366
|
+
FrankieInline,
|
|
1367
|
+
{
|
|
1368
|
+
header: header ?? false,
|
|
1369
|
+
footer,
|
|
1370
|
+
emptyState: emptyState ?? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(PageEmptyState, {}),
|
|
1371
|
+
renderMessage,
|
|
1372
|
+
renderTool,
|
|
1373
|
+
placeholder,
|
|
1374
|
+
autoFocus
|
|
1375
|
+
}
|
|
1376
|
+
) })
|
|
1377
|
+
] });
|
|
1378
|
+
}
|
|
1379
|
+
function FrankieChatPage({
|
|
1380
|
+
className,
|
|
1381
|
+
style,
|
|
1382
|
+
header,
|
|
1383
|
+
footer,
|
|
1384
|
+
emptyState,
|
|
1385
|
+
renderMessage,
|
|
1386
|
+
renderTool,
|
|
1387
|
+
placeholder,
|
|
1388
|
+
autoFocus,
|
|
1389
|
+
...providerProps
|
|
1390
|
+
}) {
|
|
1391
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1392
|
+
"main",
|
|
1393
|
+
{
|
|
1394
|
+
className: ["frankie-root", "frankie-chat-page", className].filter(Boolean).join(" "),
|
|
1395
|
+
style: { height: "100dvh", minHeight: "100dvh", width: "100%", ...style },
|
|
1396
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(FrankieProvider, { ...providerProps, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1397
|
+
PageChrome,
|
|
1398
|
+
{
|
|
1399
|
+
header,
|
|
1400
|
+
footer,
|
|
1401
|
+
emptyState,
|
|
1402
|
+
renderMessage,
|
|
1403
|
+
renderTool,
|
|
1404
|
+
placeholder,
|
|
1405
|
+
autoFocus
|
|
1406
|
+
}
|
|
1407
|
+
) })
|
|
1408
|
+
}
|
|
1409
|
+
);
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
// src/components/ui/separator.tsx
|
|
1413
|
+
var import_jsx_runtime17 = require("react/jsx-runtime");
|
|
1186
1414
|
function Separator({ className }) {
|
|
1187
|
-
return /* @__PURE__ */ (0,
|
|
1415
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { role: "separator", className: cn("h-px w-full bg-border", className) });
|
|
1188
1416
|
}
|
|
1189
1417
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1190
1418
|
0 && (module.exports = {
|
|
@@ -1201,6 +1429,7 @@ function Separator({ className }) {
|
|
|
1201
1429
|
Composer,
|
|
1202
1430
|
FRANKIE_CLOUD_URL,
|
|
1203
1431
|
FrankieApiError,
|
|
1432
|
+
FrankieChatPage,
|
|
1204
1433
|
FrankieClient,
|
|
1205
1434
|
FrankieInline,
|
|
1206
1435
|
FrankieProvider,
|