@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 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({ ...userToken ? { userToken } : {}, ...context ? { context } : {} })
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
- /** Forget the current session so the next send starts a fresh conversation. */
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
- if (!client.persist) return;
375
- try {
376
- const raw = window.localStorage.getItem(storageKey);
377
- const stored = raw ? JSON.parse(raw) : [];
378
- if (Array.isArray(stored) && stored.length) setMessages(stored);
379
- } catch {
380
- }
381
- }, [storageKey]);
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
- }, [messages, busy, client, storageKey]);
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 import_react8 = require("react");
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 import_react7 = require("react");
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
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("ul", { className: "mt-2 flex flex-col gap-1", 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)) });
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.jsx)("div", { className: "max-w-[85%] rounded-[var(--frankie-radius)] bg-primary px-3 py-2 text-sm text-primary-foreground", children: message.text }) : null
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.jsx)(Avatar, { ...avatarUrl ? { src: avatarUrl } : {}, name: assistantName, className: "mt-0.5" }),
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, import_react7.useRef)(null);
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, import_react7.useState)(void 0);
1046
- (0, import_react7.useEffect)(() => {
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.jsx)("p", { className: "text-sm text-muted-foreground", children: configError }) : loading ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { className: "text-sm text-muted-foreground", children: "Loading\u2026" }) : messages.length === 0 ? emptyState ?? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex flex-col gap-4", children: [
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, import_react8.useState)(defaultOpen);
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, import_react8.useEffect)(() => {
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/ui/separator.tsx
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, import_jsx_runtime16.jsx)("div", { role: "separator", className: cn("h-px w-full bg-border", className) });
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,