@frankieai/react 0.2.9 → 0.2.11
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 +268 -59
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +55 -3
- package/dist/index.d.ts +55 -3
- package/dist/index.js +268 -60
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/components/chat/Composer.tsx +49 -42
- package/src/components/chat/FrankieChatPage.tsx +1 -1
- package/src/components/chat/FrankieInline.tsx +3 -2
- package/src/components/chat/FrankieWidget.tsx +2 -2
- package/src/components/chat/MessageBubble.tsx +24 -5
- package/src/components/chat/ToolActivityList.tsx +2 -1
- package/src/generated/css-content.ts +2 -0
- package/src/hooks/FrankieProvider.tsx +30 -3
- package/src/index.ts +1 -1
- package/src/lib/client.ts +136 -5
- package/src/lib/ensure-frankie-styles.ts +32 -0
- package/src/lib/identity.ts +39 -0
- package/src/lib/styles-ready.ts +19 -7
- package/src/lib/theme-inherit.test.ts +68 -0
- package/src/lib/theme-inherit.ts +89 -0
- package/src/styles/frankie.css +34 -0
- package/src/styles/host-safety.test.ts +57 -0
- package/src/styles-ready.test.tsx +30 -3
- package/src/widget.test.tsx +218 -0
package/dist/index.js
CHANGED
|
@@ -3,6 +3,31 @@
|
|
|
3
3
|
// src/hooks/FrankieProvider.tsx
|
|
4
4
|
import { createContext as createContext2, useContext as useContext2, useEffect as useEffect2, useMemo, useRef as useRef2, useState as useState2 } from "react";
|
|
5
5
|
|
|
6
|
+
// src/lib/identity.ts
|
|
7
|
+
function sessionIdentity(token) {
|
|
8
|
+
if (!token) return void 0;
|
|
9
|
+
return jwtSubject(token) ?? token;
|
|
10
|
+
}
|
|
11
|
+
function jwtSubject(token) {
|
|
12
|
+
const parts = token.split(".");
|
|
13
|
+
if (parts.length !== 3 || !parts[1]) return void 0;
|
|
14
|
+
try {
|
|
15
|
+
const payload = JSON.parse(base64UrlDecode(parts[1]));
|
|
16
|
+
return typeof payload.sub === "string" && payload.sub ? payload.sub : void 0;
|
|
17
|
+
} catch {
|
|
18
|
+
return void 0;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function base64UrlDecode(segment) {
|
|
22
|
+
const base64 = segment.replace(/-/g, "+").replace(/_/g, "/");
|
|
23
|
+
const padded = base64 + "=".repeat((4 - base64.length % 4) % 4);
|
|
24
|
+
if (typeof atob === "function") {
|
|
25
|
+
const bytes = atob(padded);
|
|
26
|
+
return decodeURIComponent(Array.from(bytes, (c) => "%" + c.charCodeAt(0).toString(16).padStart(2, "0")).join(""));
|
|
27
|
+
}
|
|
28
|
+
return Buffer.from(padded, "base64").toString("utf8");
|
|
29
|
+
}
|
|
30
|
+
|
|
6
31
|
// src/lib/client.ts
|
|
7
32
|
var FRANKIE_CLOUD_URL = "https://frankie-control-665333762719.us-central1.run.app";
|
|
8
33
|
var FrankieApiError = class extends Error {
|
|
@@ -31,7 +56,8 @@ var FrankieClient = class {
|
|
|
31
56
|
baseUrl;
|
|
32
57
|
doFetch;
|
|
33
58
|
session;
|
|
34
|
-
|
|
59
|
+
/** Identity (JWT `sub`, else the raw token) the cached session was issued for. */
|
|
60
|
+
sessionUserKey;
|
|
35
61
|
get sessionId() {
|
|
36
62
|
return this.session?.sessionId;
|
|
37
63
|
}
|
|
@@ -73,7 +99,18 @@ var FrankieClient = class {
|
|
|
73
99
|
return await this.unwrap(response);
|
|
74
100
|
}
|
|
75
101
|
async ensureSession(context) {
|
|
76
|
-
|
|
102
|
+
let userToken;
|
|
103
|
+
try {
|
|
104
|
+
userToken = await this.options.getUserToken?.();
|
|
105
|
+
} catch (error) {
|
|
106
|
+
this.resetSession();
|
|
107
|
+
const reason = error instanceof Error && error.message.trim() ? error.message.trim() : void 0;
|
|
108
|
+
throw new FrankieApiError(
|
|
109
|
+
reason ?? "Your signed-in Frankie session could not be verified. Refresh the page and sign in again.",
|
|
110
|
+
401,
|
|
111
|
+
"user_token_unavailable"
|
|
112
|
+
);
|
|
113
|
+
}
|
|
77
114
|
if (this.options.getUserToken && !userToken) {
|
|
78
115
|
this.resetSession();
|
|
79
116
|
throw new FrankieApiError(
|
|
@@ -82,7 +119,8 @@ var FrankieClient = class {
|
|
|
82
119
|
"user_token_unavailable"
|
|
83
120
|
);
|
|
84
121
|
}
|
|
85
|
-
|
|
122
|
+
const userKey = sessionIdentity(userToken);
|
|
123
|
+
if (userKey !== this.sessionUserKey) this.resetSession();
|
|
86
124
|
if (this.session && new Date(this.session.expiresAt).getTime() > Date.now() + 3e4) return this.session;
|
|
87
125
|
const response = await this.doFetch(`${this.baseUrl}/v1/session`, {
|
|
88
126
|
method: "POST",
|
|
@@ -94,7 +132,7 @@ var FrankieClient = class {
|
|
|
94
132
|
})
|
|
95
133
|
});
|
|
96
134
|
this.session = await this.unwrap(response);
|
|
97
|
-
this.
|
|
135
|
+
this.sessionUserKey = userKey;
|
|
98
136
|
this.writeStored();
|
|
99
137
|
return this.session;
|
|
100
138
|
}
|
|
@@ -116,7 +154,7 @@ var FrankieClient = class {
|
|
|
116
154
|
if (response.status === 404) return void 0;
|
|
117
155
|
const body = await this.unwrap(response);
|
|
118
156
|
this.session = body.session;
|
|
119
|
-
this.
|
|
157
|
+
this.sessionUserKey = sessionIdentity(userToken);
|
|
120
158
|
this.writeStored();
|
|
121
159
|
return { messages: Array.isArray(body.messages) ? body.messages : [], title: body.title ?? "New chat" };
|
|
122
160
|
}
|
|
@@ -175,9 +213,59 @@ var FrankieClient = class {
|
|
|
175
213
|
return;
|
|
176
214
|
}
|
|
177
215
|
for await (const event of readEventStream(response.body)) {
|
|
216
|
+
if (event.type === "tool_call_request") {
|
|
217
|
+
void this.runClientTool(session, event.callId, event.request);
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
178
220
|
onEvent(event);
|
|
179
221
|
}
|
|
180
222
|
}
|
|
223
|
+
/**
|
|
224
|
+
* Execute one host API call in the browser and report the outcome back to the
|
|
225
|
+
* server. Never throws: a transport failure or a missing `api` client becomes
|
|
226
|
+
* a result the model can see and explain, exactly like a non-2xx status.
|
|
227
|
+
*/
|
|
228
|
+
async runClientTool(session, callId, request) {
|
|
229
|
+
const result = await this.callHostApi(request);
|
|
230
|
+
await this.postToolResult(session, callId, result).catch(() => void 0);
|
|
231
|
+
}
|
|
232
|
+
async callHostApi(request) {
|
|
233
|
+
const api = this.options.api;
|
|
234
|
+
if (!api) {
|
|
235
|
+
return {
|
|
236
|
+
status: 0,
|
|
237
|
+
error: "This assistant tried to call your API from the browser, but no host API client is configured in the Frankie SDK (the `api` option)."
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
try {
|
|
241
|
+
const extra = api.headers ? await api.headers() : {};
|
|
242
|
+
const response = await this.doFetch(joinHostUrl(api.baseUrl, request.path), {
|
|
243
|
+
method: request.method,
|
|
244
|
+
credentials: api.credentials ?? "same-origin",
|
|
245
|
+
headers: {
|
|
246
|
+
accept: "application/json, text/plain;q=0.9, */*;q=0.8",
|
|
247
|
+
...request.body !== void 0 ? { "content-type": "application/json" } : {},
|
|
248
|
+
...extra
|
|
249
|
+
},
|
|
250
|
+
...request.body !== void 0 ? { body: JSON.stringify(request.body) } : {}
|
|
251
|
+
});
|
|
252
|
+
const body = await response.text().catch(() => "");
|
|
253
|
+
return { status: response.status, body };
|
|
254
|
+
} catch (error) {
|
|
255
|
+
return { status: 0, error: error instanceof Error ? error.message : String(error) };
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
async postToolResult(session, callId, result) {
|
|
259
|
+
await this.doFetch(`${this.baseUrl}/v1/chat/tool-result`, {
|
|
260
|
+
method: "POST",
|
|
261
|
+
headers: {
|
|
262
|
+
"content-type": "application/json",
|
|
263
|
+
"x-frankie-key": this.options.publishableKey,
|
|
264
|
+
authorization: `Bearer ${session.sessionToken}`
|
|
265
|
+
},
|
|
266
|
+
body: JSON.stringify({ callId, result })
|
|
267
|
+
});
|
|
268
|
+
}
|
|
181
269
|
async unwrap(response) {
|
|
182
270
|
if (response.ok) return response.json();
|
|
183
271
|
let code = "internal";
|
|
@@ -191,6 +279,12 @@ var FrankieClient = class {
|
|
|
191
279
|
throw new FrankieApiError(message, response.status, code);
|
|
192
280
|
}
|
|
193
281
|
};
|
|
282
|
+
function joinHostUrl(baseUrl, path) {
|
|
283
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(path) || path.startsWith("//")) {
|
|
284
|
+
throw new Error("A client tool path must be relative to the configured host API base URL.");
|
|
285
|
+
}
|
|
286
|
+
return `${baseUrl.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
|
|
287
|
+
}
|
|
194
288
|
async function* readEventStream(body) {
|
|
195
289
|
const reader = body.getReader();
|
|
196
290
|
const decoder = new TextDecoder();
|
|
@@ -223,6 +317,68 @@ function parseFrame(frame) {
|
|
|
223
317
|
}
|
|
224
318
|
}
|
|
225
319
|
|
|
320
|
+
// src/lib/theme-inherit.ts
|
|
321
|
+
function parseRgb(value) {
|
|
322
|
+
const match = value.match(/rgba?\(\s*([\d.]+)[,\s]+([\d.]+)[,\s]+([\d.]+)(?:\s*[,/]\s*([\d.]+%?))?\s*\)/i);
|
|
323
|
+
if (!match) return null;
|
|
324
|
+
const alpha = match[4] === void 0 ? 1 : match[4].endsWith("%") ? Number(match[4].slice(0, -1)) / 100 : Number(match[4]);
|
|
325
|
+
return [Number(match[1]), Number(match[2]), Number(match[3]), alpha];
|
|
326
|
+
}
|
|
327
|
+
function toCss([r, g, b, a]) {
|
|
328
|
+
return a >= 1 ? `rgb(${r}, ${g}, ${b})` : `rgba(${r}, ${g}, ${b}, ${a})`;
|
|
329
|
+
}
|
|
330
|
+
function sameColor(a, b) {
|
|
331
|
+
return Math.abs(a[0] - b[0]) < 12 && Math.abs(a[1] - b[1]) < 12 && Math.abs(a[2] - b[2]) < 12;
|
|
332
|
+
}
|
|
333
|
+
function luminance([r, g, b]) {
|
|
334
|
+
const channel = (c) => {
|
|
335
|
+
const v = c / 255;
|
|
336
|
+
return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;
|
|
337
|
+
};
|
|
338
|
+
return 0.2126 * channel(r) + 0.7152 * channel(g) + 0.0722 * channel(b);
|
|
339
|
+
}
|
|
340
|
+
function contrastRatio(a, b) {
|
|
341
|
+
const la = luminance(a), lb = luminance(b);
|
|
342
|
+
return (Math.max(la, lb) + 0.05) / (Math.min(la, lb) + 0.05);
|
|
343
|
+
}
|
|
344
|
+
var ACCENT_SELECTORS = ["[data-frankie-accent]", "button[type=submit]", "a[href]", "button", "[class*=primary]", "[class*=btn]"];
|
|
345
|
+
function inferHostTheme() {
|
|
346
|
+
if (typeof document === "undefined" || typeof getComputedStyle !== "function") return null;
|
|
347
|
+
try {
|
|
348
|
+
const bodyStyle = getComputedStyle(document.body);
|
|
349
|
+
const background = parseRgb(bodyStyle.backgroundColor);
|
|
350
|
+
const foreground = parseRgb(bodyStyle.color);
|
|
351
|
+
if (!background || !foreground) return null;
|
|
352
|
+
if (background[3] < 0.5) return null;
|
|
353
|
+
if (contrastRatio(background, foreground) < 2) return null;
|
|
354
|
+
let accent = null;
|
|
355
|
+
for (const selector of ACCENT_SELECTORS) {
|
|
356
|
+
const element = document.querySelector(selector);
|
|
357
|
+
if (!element) continue;
|
|
358
|
+
const style = getComputedStyle(element);
|
|
359
|
+
const surface = parseRgb(style.backgroundColor);
|
|
360
|
+
if (surface && surface[3] > 0.5 && !sameColor(surface, background)) {
|
|
361
|
+
accent = surface;
|
|
362
|
+
break;
|
|
363
|
+
}
|
|
364
|
+
const text = parseRgb(style.color);
|
|
365
|
+
if (text && text[3] > 0.5 && !sameColor(text, foreground)) {
|
|
366
|
+
accent = text;
|
|
367
|
+
break;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
if (!accent) return null;
|
|
371
|
+
return {
|
|
372
|
+
background: toCss(background),
|
|
373
|
+
foreground: toCss(foreground),
|
|
374
|
+
primary: toCss(accent),
|
|
375
|
+
primaryForeground: luminance(accent) > 0.5 ? "#0a0a0a" : "#fafafa"
|
|
376
|
+
};
|
|
377
|
+
} catch {
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
226
382
|
// src/hooks/useFrankieChat.ts
|
|
227
383
|
import { createContext, useContext, useCallback, useEffect, useRef, useState } from "react";
|
|
228
384
|
|
|
@@ -543,6 +699,7 @@ function FrankieProvider({
|
|
|
543
699
|
context,
|
|
544
700
|
theme,
|
|
545
701
|
config: providedConfig,
|
|
702
|
+
injectStyles = true,
|
|
546
703
|
...clientOptions
|
|
547
704
|
}) {
|
|
548
705
|
const tokenCallback = useRef2(clientOptions.getUserToken);
|
|
@@ -550,7 +707,7 @@ function FrankieProvider({
|
|
|
550
707
|
const client = useMemo(
|
|
551
708
|
() => new FrankieClient({ ...clientOptions, ...clientOptions.getUserToken ? { getUserToken: () => tokenCallback.current?.() } : {} }),
|
|
552
709
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
553
|
-
[clientOptions.baseUrl, clientOptions.publishableKey, clientOptions.storageNamespace, clientOptions.conversationId, clientOptions.persist, clientOptions.fetchImpl, Boolean(clientOptions.getUserToken)]
|
|
710
|
+
[clientOptions.baseUrl, clientOptions.publishableKey, clientOptions.storageNamespace, clientOptions.conversationId, clientOptions.persist, clientOptions.fetchImpl, clientOptions.api?.baseUrl, clientOptions.api?.credentials, Boolean(clientOptions.getUserToken)]
|
|
554
711
|
);
|
|
555
712
|
const [config, setConfig] = useState2(providedConfig);
|
|
556
713
|
const [configError, setConfigError] = useState2(void 0);
|
|
@@ -577,9 +734,10 @@ function FrankieProvider({
|
|
|
577
734
|
...configError ? { configError } : {},
|
|
578
735
|
loading,
|
|
579
736
|
...context ? { context } : {},
|
|
580
|
-
...theme ? { theme } : {}
|
|
737
|
+
...theme ? { theme } : {},
|
|
738
|
+
injectStyles
|
|
581
739
|
}),
|
|
582
|
-
[client, config, configError, loading, context, theme]
|
|
740
|
+
[client, config, configError, loading, context, theme, injectStyles]
|
|
583
741
|
);
|
|
584
742
|
return /* @__PURE__ */ jsx(FrankieContext.Provider, { value, children: /* @__PURE__ */ jsx(ConversationState, { children }, client.namespace + ":" + (client.conversationId ?? "")) });
|
|
585
743
|
}
|
|
@@ -594,7 +752,16 @@ function useFrankie() {
|
|
|
594
752
|
}
|
|
595
753
|
function useFrankieTheme() {
|
|
596
754
|
const { config, theme } = useFrankie();
|
|
597
|
-
|
|
755
|
+
const merged = useMemo(() => ({ ...config?.theme, ...theme }), [config?.theme, theme]);
|
|
756
|
+
const inheriting = merged?.source === "inherit";
|
|
757
|
+
const [inferred, setInferred] = useState2(null);
|
|
758
|
+
useEffect2(() => {
|
|
759
|
+
setInferred(inheriting ? inferHostTheme() : null);
|
|
760
|
+
}, [inheriting]);
|
|
761
|
+
return useMemo(() => {
|
|
762
|
+
if (!inheriting || !inferred) return merged;
|
|
763
|
+
return { ...merged, ...inferred };
|
|
764
|
+
}, [merged, inheriting, inferred]);
|
|
598
765
|
}
|
|
599
766
|
|
|
600
767
|
// src/components/chat/FrankieWidget.tsx
|
|
@@ -603,9 +770,28 @@ import { useEffect as useEffect6, useRef as useRef5, useState as useState7 } fro
|
|
|
603
770
|
|
|
604
771
|
// src/lib/styles-ready.ts
|
|
605
772
|
import { useEffect as useEffect3, useLayoutEffect, useState as useState3 } from "react";
|
|
773
|
+
|
|
774
|
+
// src/generated/css-content.ts
|
|
775
|
+
var FRANKIE_CSS_CONTENT = `/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
|
|
776
|
+
@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;--tw-ease:initial}}}@layer theme{.frankie-root{--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;--ease-in-out:cubic-bezier(.4, 0, .2, 1);--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-page-shell{background:var(--frankie-background);height:100%;min-height:0;color:var(--frankie-foreground);flex:1;display:flex}.frankie-page-main{flex-direction:column;flex:1;min-width:0;min-height:0;display:flex}.frankie-history-desktop{border-right:1px solid var(--frankie-border);background:var(--frankie-secondary);flex-direction:column;flex-shrink:0;gap:16px;width:260px;padding:20px 14px;transition:width .16s,padding .16s;display:flex;overflow:auto}.frankie-history-toggle{border:1px solid var(--frankie-border);background:var(--frankie-background);width:32px;height:32px;color:var(--frankie-muted-foreground);cursor:pointer;border-radius:8px;flex-shrink:0;justify-content:center;align-self:flex-end;align-items:center;display:flex}.frankie-history-toggle:hover{color:var(--frankie-foreground)}.frankie-history-collapsed{width:56px;padding:20px 8px;overflow:visible}.frankie-history-collapsed .frankie-history-toggle{align-self:center}.frankie-history-heading{justify-content:space-between;align-items:center;font-size:14px;display:flex}.frankie-new-chat{border:1px solid var(--frankie-border);background:var(--frankie-background);cursor:pointer;border-radius:10px;justify-content:center;align-items:center;gap:8px;min-height:44px;font-weight:600;display:flex}.frankie-history-search>span{color:var(--frankie-muted-foreground);margin-bottom:6px;font-size:12px;display:block}.frankie-history-search input{border:1px solid var(--frankie-border);background:var(--frankie-background);border-radius:8px;width:100%;min-height:40px;padding:8px 10px}.frankie-history-list{flex-direction:column;flex:1;gap:4px;display:flex}.frankie-history-list button{text-align:left;cursor:pointer;background:0 0;border:1px solid #0000;border-radius:8px;flex-direction:column;align-items:flex-start;gap:5px;min-height:54px;padding:10px;display:flex}.frankie-history-list button span{text-overflow:ellipsis;white-space:nowrap;width:100%;font-size:13px;overflow:hidden}.frankie-history-list small{color:var(--frankie-muted-foreground);font-size:11px}.frankie-history-list button:hover,.frankie-history-list button[aria-current]{background:var(--frankie-background);border-color:var(--frankie-border)}.frankie-history-desktop>p,.frankie-history-dialog p{color:var(--frankie-muted-foreground);font-size:13px}.frankie-header-caption{color:var(--frankie-muted-foreground);font-size:12px}.frankie-page-header button{cursor:pointer;justify-content:center;align-items:center;gap:6px;min-width:40px;min-height:40px;display:inline-flex}.frankie-chat-settings{position:relative}.frankie-chat-settings summary{cursor:pointer;border-radius:8px;place-items:center;width:40px;height:40px;list-style:none;display:grid}.frankie-chat-settings summary::-webkit-details-marker{display:none}.frankie-chat-settings>div{z-index:5;border:1px solid var(--frankie-border);background:var(--frankie-background);border-radius:12px;gap:12px;width:min(280px,80vw);padding:16px;font-size:13px;display:grid;position:absolute;top:46px;right:0;box-shadow:0 10px 30px #0002}.frankie-chat-settings p{color:var(--frankie-muted-foreground);margin:0}.frankie-loaded-skills{width:100%;max-width:760px;color:var(--frankie-muted-foreground);font-size:12px}.frankie-loaded-skills summary{cursor:pointer;padding:6px 0}.frankie-loaded-skills ul{margin:0;padding:4px 0;list-style:none}.frankie-loaded-skills li{gap:8px;padding:4px 0;display:flex}.frankie-loaded-skills small{opacity:.7}.frankie-history-dialog{background:var(--frankie-secondary);width:min(320px,86vw);max-width:none;height:100dvh;max-height:none;color:var(--frankie-foreground);border:0;margin:0;padding:0}.frankie-history-dialog::backdrop{background:#0008}.frankie-history-dialog>aside{height:100%;padding:calc(20px + env(safe-area-inset-top)) 16px calc(20px + env(safe-area-inset-bottom));flex-direction:column;gap:16px;display:flex;overflow:auto}.frankie-mobile-only{display:none!important}.frankie-widget-panel{max-height:calc(100dvh - 100px)}.frankie-root button:focus-visible,.frankie-root summary:focus-visible,.frankie-root input:focus-visible{outline:2px solid var(--frankie-primary);outline-offset:3px}.frankie-root button:disabled{opacity:.5;cursor:not-allowed}@media (max-width:760px){.frankie-history-desktop{display:none}.frankie-mobile-only{display:inline-flex!important}.frankie-header-caption{display:none}.frankie-page-header{padding:6px 10px}.frankie-history-heading button{background:0 0;border:0;justify-content:center;align-items:center;min-width:44px;min-height:44px}.frankie-page-prompts{gap:8px}.frankie-widget-panel{width:calc(100vw - 32px)}}.frankie-root{--frankie-styles-ready:1;--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:.6rem}.frankie-prose p{white-space:pre-wrap;overflow-wrap:anywhere;margin:0;line-height:1.6}.frankie-prose>.frankie-h{letter-spacing:-.01em;margin-top:1.15rem;line-height:1.3}.frankie-prose>.frankie-h:first-child{margin-top:0}.frankie-prose h3.frankie-h{font-size:1.06em;font-weight:700}.frankie-prose h4.frankie-h{font-size:1em;font-weight:600}.frankie-prose h5.frankie-h{color:var(--frankie-muted-foreground);font-size:.92em;font-weight:600}.frankie-prose strong{font-weight:600}.frankie-prose ul,.frankie-prose ol{flex-direction:column;gap:.35rem;margin:0;padding-left:1.4rem;display:flex}.frankie-prose li{padding-left:.15rem;line-height:1.55}.frankie-prose li::marker{color:var(--frankie-muted-foreground)}.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: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){.invisible{visibility:hidden}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.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{margin-top:0}.mt-0\\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:var(--spacing)}.mt-2{margin-top:calc(var(--spacing) * 2)}.block{display:block}.contents{display:contents}.flex{display:flex}.hidden{display:none}.inline{display:inline}.inline-flex{display:inline-flex}.table{display:table}.size-1{width:var(--spacing);height:var(--spacing)}.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-4{width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}.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-\\[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}.grow{flex-grow:1}.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{border-radius:.25rem}.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{background-color:var(--color-emerald-500)}.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{background-color:var(--color-red-500)}.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{padding-block:0}.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))}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.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}}.frankie-root.frankie-chat-panel{min-height:0;position:relative}.frankie-root.frankie-widget-panel{box-shadow:0 32px 72px -18px var(--frankie-primary), 0 12px 28px -12px #0f172a47, 0 0 0 1px var(--frankie-foreground)}@supports (color:color-mix(in lab, red, red)){.frankie-root.frankie-widget-panel{box-shadow:0 32px 72px -18px color-mix(in srgb, var(--frankie-primary) 22%, #0f172a6b), 0 12px 28px -12px #0f172a47, 0 0 0 1px color-mix(in srgb, var(--frankie-foreground) 8%, transparent)}}.frankie-root.frankie-widget-panel.dark{box-shadow:0 34px 80px -16px #000000b8, 0 14px 32px -12px #0009, 0 0 0 1px var(--frankie-foreground)}@supports (color:color-mix(in lab, red, red)){.frankie-root.frankie-widget-panel.dark{box-shadow:0 34px 80px -16px #000000b8, 0 14px 32px -12px #0009, 0 0 0 1px color-mix(in srgb, var(--frankie-foreground) 12%, transparent)}}.frankie-root .frankie-agent-context-wrap{flex:none;justify-content:center;padding:12px 10px;display:flex}.frankie-root .frankie-agent-context{max-width:100%;color:var(--frankie-foreground);font-size:13px;position:relative}.frankie-root .frankie-agent-context>summary{border:1px solid var(--frankie-border);background:var(--frankie-background);cursor:pointer;white-space:nowrap;border-radius:999px;justify-content:center;align-items:center;gap:10px;min-height:44px;padding:7px 13px;list-style:none;display:flex;box-shadow:0 1px 3px #0000000d}.frankie-root .frankie-agent-context>summary::-webkit-details-marker{display:none}.frankie-root .frankie-agent-context>summary>svg{color:var(--frankie-muted-foreground);flex:none}.frankie-root .frankie-context-count{background:var(--frankie-muted);color:var(--frankie-muted-foreground);border-radius:999px;padding:2px 10px;font-size:12px}.frankie-root .frankie-context-status{color:var(--frankie-muted-foreground);align-items:center;gap:6px;font-size:12px;display:flex}.frankie-root .frankie-context-status i{background:currentColor;border-radius:50%;width:7px;height:7px}.frankie-root .frankie-context-status[data-active=true]{color:#00976b}.frankie-root .frankie-context-detail{z-index:10;border:1px solid var(--frankie-border);background:var(--frankie-background);border-radius:16px;width:340px;max-width:calc(100vw - 32px);padding:18px;position:absolute;top:calc(100% + 10px);left:50%;transform:translate(-50%);box-shadow:0 12px 32px #00000016}.frankie-root .frankie-context-detail p{color:var(--frankie-muted-foreground);margin:10px 0 0;font-size:12px;line-height:1.6}.frankie-root .frankie-context-detail ul{max-height:220px;margin:12px 0;padding:0;list-style:none;overflow:auto}.frankie-root .frankie-context-detail li{border-bottom:1px solid var(--frankie-border);overflow-wrap:anywhere;justify-content:space-between;gap:12px;padding:8px 0;display:flex}.frankie-root .frankie-context-detail small{color:var(--frankie-muted-foreground);flex:none}.frankie-root .frankie-sr-only{clip:rect(0,0,0,0);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.frankie-root.frankie-chat-panel-fill{--frankie-canvas:var(--frankie-background)}@supports (color:color-mix(in lab, red, red)){.frankie-root.frankie-chat-panel-fill{--frankie-canvas:color-mix(in srgb,var(--frankie-background) 60%,var(--frankie-muted))}}.frankie-root.frankie-chat-panel-fill{background:var(--frankie-canvas)}.frankie-root.frankie-chat-panel-fill .frankie-agent-context-wrap{padding:44px 16px 26px}.frankie-root.frankie-chat-panel-fill .frankie-transcript{padding:14px max(24px,50% - 480px) 24px}.frankie-root.frankie-chat-panel-fill .frankie-message-list{gap:38px}.frankie-root.frankie-chat-panel-fill .frankie-user-bubble{background:var(--frankie-muted);color:var(--frankie-foreground);white-space:pre-wrap;overflow-wrap:anywhere;border-radius:24px;padding:12px 20px;font-size:15px}.frankie-root.frankie-chat-panel-fill .frankie-assistant-content{flex:1;max-width:100%;font-size:15px;line-height:1.7}.frankie-root.frankie-chat-panel-fill .frankie-assistant-message{gap:14px}.frankie-root .frankie-thinking{color:var(--frankie-muted-foreground);gap:5px}.frankie-root .frankie-thinking-label{margin-right:6px;font-size:14px}.frankie-root.frankie-chat-panel-fill .frankie-composer-footer{background:0 0;border:0;flex:none;padding:16px max(24px,50% - 500px) 24px}.frankie-root.frankie-chat-panel-fill .frankie-composer{border:1px solid var(--frankie-border);background:var(--frankie-background);border-radius:28px;padding:14px;box-shadow:0 2px 4px #0000000a}.frankie-root.frankie-chat-panel-fill .frankie-composer:focus-within{border-color:var(--frankie-foreground)}@supports (color:color-mix(in lab, red, red)){.frankie-root.frankie-chat-panel-fill .frankie-composer:focus-within{border-color:color-mix(in srgb,var(--frankie-foreground) 32%,var(--frankie-border))}}.frankie-root.frankie-chat-panel-fill .frankie-composer:focus-within{box-shadow:0 0 0 2px var(--frankie-primary)}@supports (color:color-mix(in lab, red, red)){.frankie-root.frankie-chat-panel-fill .frankie-composer:focus-within{box-shadow:0 0 0 2px color-mix(in srgb,var(--frankie-primary) 8%,transparent)}}.frankie-root.frankie-chat-panel-fill .frankie-composer-controls{flex-wrap:wrap;align-items:center;gap:8px;display:flex}.frankie-root.frankie-chat-panel-fill .frankie-composer textarea{resize:none;width:100%;min-height:32px;max-height:140px;color:var(--frankie-foreground);box-shadow:none;background:0 0;border:0;border-radius:0;outline:none;flex:0 0 100%;order:-1;margin-bottom:4px;padding:2px 6px;font-size:16px}.frankie-root.frankie-chat-panel-fill .frankie-composer button[aria-label=Send],.frankie-root.frankie-chat-panel-fill .frankie-composer button[aria-label=Stop]{background:var(--frankie-primary);width:42px;height:42px;color:var(--frankie-primary-foreground);border:0;border-radius:50%;margin-left:auto}.frankie-root.frankie-chat-panel-fill .frankie-composer button[aria-label=Attach\\ files]{border-radius:50%;width:42px;height:42px}.frankie-root .frankie-composer-actions{align-items:center;gap:6px;min-width:0;display:flex}.frankie-root .frankie-composer-meta{align-items:center;gap:8px;margin-top:6px;padding:0 2px;display:flex}.frankie-root .frankie-model-picker{align-items:center;min-width:0;display:inline-flex}.frankie-root .frankie-model-select{appearance:none;max-width:12rem;color:var(--frankie-muted-foreground);font:inherit;cursor:pointer;text-overflow:ellipsis;background-color:#0000;background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%238b8b8b' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>");background-position:right 6px center;background-repeat:no-repeat;background-size:11px;border:0;border-radius:8px;padding:5px 22px 5px 8px;font-size:12px;font-weight:500;line-height:1;transition:background-color .12s,color .12s}.frankie-root .frankie-model-select:hover{background-color:var(--frankie-muted);color:var(--frankie-foreground)}.frankie-root .frankie-model-select:disabled{opacity:.5;cursor:not-allowed}.frankie-root .frankie-model-select option{color:var(--frankie-foreground);background:var(--frankie-background);font-weight:400}.frankie-root .frankie-history-desktop{background:var(--frankie-background);width:290px;padding:20px 12px}.frankie-root .frankie-history-collapsed{width:56px;padding:20px 8px}.frankie-root .frankie-new-chat{background:0 0;border:0;justify-content:flex-start;padding:8px 12px;font-size:15px}.frankie-root .frankie-history-heading{color:var(--frankie-muted-foreground);padding:0 12px;font-size:12px;font-weight:400}.frankie-root .frankie-history-list button{min-height:44px;padding:10px 12px}.frankie-root .frankie-history-list button span{font-size:14px}.frankie-root .frankie-history-list button[aria-current],.frankie-root .frankie-history-list button:hover{background:var(--frankie-muted);border-color:#0000}.frankie-root .frankie-history-list small{display:none}.frankie-root .frankie-page-header{background:var(--frankie-background);height:56px;min-height:56px}.frankie-root .frankie-page-header>div{min-width:0}.frankie-root .frankie-thread-title{text-overflow:ellipsis;white-space:nowrap;max-width:55vw;font-weight:500;overflow:hidden}.frankie-root .frankie-page-header button{background:0 0;border-color:#0000}.frankie-root .frankie-page-canvas>.frankie-inline{max-width:none}@media (max-width:760px){.frankie-root.frankie-chat-panel-fill .frankie-agent-context-wrap{padding:20px 8px 14px}.frankie-root.frankie-chat-panel-fill .frankie-transcript{padding:12px 16px 16px}.frankie-root.frankie-chat-panel-fill .frankie-composer-footer{padding:10px 12px calc(12px + env(safe-area-inset-bottom))}.frankie-root .frankie-agent-context>summary{gap:6px;padding:7px 9px;font-size:12px}.frankie-root .frankie-context-count{padding:2px 7px;font-size:11px}.frankie-root .frankie-context-status{gap:4px;font-size:11px}.frankie-root .frankie-agent-context>summary>svg{width:14px}.frankie-root .frankie-thread-title{max-width:42vw}}.frankie-root .frankie-agent-context>summary>svg:first-child{box-sizing:content-box;background:var(--frankie-muted);border-radius:50%;padding:4px}.frankie-root.frankie-chat-panel-fill .frankie-agent-context>summary{font-size:14px}.frankie-root .frankie-agent-context>summary>span:first-of-type{border-right:1px solid var(--frankie-border);padding-right:10px}@media (max-width:760px){.frankie-root .frankie-page-header button>span{display:none}}.frankie-root .frankie-choices{flex-direction:column;gap:8px;margin-top:12px;display:flex}.frankie-root .frankie-choice{text-align:left;cursor:pointer;border:1px solid var(--frankie-border);border-radius:var(--frankie-radius);background:var(--frankie-background);width:100%;color:var(--frankie-foreground);font:inherit;align-items:center;gap:12px;padding:11px 14px;font-size:14px;line-height:1.4;transition:border-color .15s,background-color .15s,transform .12s;display:flex}.frankie-root .frankie-choice:hover:not(:disabled){border-color:var(--frankie-primary)}@supports (color:color-mix(in lab, red, red)){.frankie-root .frankie-choice:hover:not(:disabled){border-color:color-mix(in srgb, var(--frankie-primary) 55%, var(--frankie-border))}}.frankie-root .frankie-choice:hover:not(:disabled){background:var(--frankie-primary)}@supports (color:color-mix(in lab, red, red)){.frankie-root .frankie-choice:hover:not(:disabled){background:color-mix(in srgb, var(--frankie-primary) 6%, var(--frankie-background))}}.frankie-root .frankie-choice:active:not(:disabled){transform:scale(.99)}.frankie-root .frankie-choice:focus-visible{box-shadow:0 0 0 2px var(--frankie-primary);outline:none}@supports (color:color-mix(in lab, red, red)){.frankie-root .frankie-choice:focus-visible{box-shadow:0 0 0 2px color-mix(in srgb, var(--frankie-primary) 40%, transparent)}}.frankie-root .frankie-choice:disabled{opacity:.6;cursor:default}.frankie-root .frankie-choice-index{border:1px solid var(--frankie-border);background:var(--frankie-muted);width:24px;height:24px;color:var(--frankie-muted-foreground);border-radius:50%;flex:none;place-items:center;font-size:12px;font-weight:600;transition:background-color .15s,color .15s,border-color .15s;display:grid}.frankie-root .frankie-choice:hover:not(:disabled) .frankie-choice-index{background:var(--frankie-primary);border-color:var(--frankie-primary);color:var(--frankie-primary-foreground)}.frankie-root .frankie-choice-label{flex:1;min-width:0}.frankie-root .frankie-choice-go{color:var(--frankie-muted-foreground);opacity:0;flex:none;transition:opacity .15s,transform .15s;transform:translate(-4px)}.frankie-root .frankie-choice:hover:not(:disabled) .frankie-choice-go{opacity:1;color:var(--frankie-primary);transform:none}@media (prefers-reduced-motion:reduce){.frankie-root .frankie-choice,.frankie-root .frankie-choice-go,.frankie-root .frankie-choice-index{transition:none}}.frankie-root .frankie-choice-other{color:var(--frankie-muted-foreground);border-style:dashed}.frankie-root .frankie-choice-other .frankie-choice-index{background:0 0}.frankie-root .frankie-choice-other:hover:not(:disabled){color:var(--frankie-foreground)}.frankie-root .frankie-choice-other:hover:not(:disabled) .frankie-choice-index{background:var(--frankie-muted);border-color:var(--frankie-border);color:var(--frankie-foreground)}.frankie-root .frankie-choice-hint{color:var(--frankie-muted-foreground);flex:none;font-size:12px}.frankie-root .frankie-skeleton{flex-direction:column;gap:8px;max-width:min(420px,100%);padding:4px 0 6px;display:flex}.frankie-root .frankie-skeleton-line{background:linear-gradient(90deg, var(--frankie-muted-foreground) 25%, var(--frankie-muted-foreground) 37%, var(--frankie-muted-foreground) 63%);border-radius:6px;height:12px}@supports (color:color-mix(in lab, red, red)){.frankie-root .frankie-skeleton-line{background:linear-gradient(90deg, color-mix(in srgb, var(--frankie-muted-foreground) 11%, transparent) 25%, color-mix(in srgb, var(--frankie-muted-foreground) 26%, transparent) 37%, color-mix(in srgb, var(--frankie-muted-foreground) 11%, transparent) 63%)}}.frankie-root .frankie-skeleton-line{background-size:400% 100%;animation:1.4s infinite frankie-shimmer}@keyframes frankie-shimmer{0%{background-position:100%}to{background-position:0}}@keyframes frankie-skeleton-pulse{0%,to{opacity:.45}50%{opacity:.85}}@media (prefers-reduced-motion:reduce){.frankie-root .frankie-skeleton-line{background:var(--frankie-muted-foreground)}@supports (color:color-mix(in lab, red, red)){.frankie-root .frankie-skeleton-line{background:color-mix(in srgb, var(--frankie-muted-foreground) 16%, transparent)}}.frankie-root .frankie-skeleton-line{animation:1.6s ease-in-out infinite frankie-skeleton-pulse}}@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}@property --tw-ease{syntax:"*";inherits:false}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse{50%{opacity:.5}}`;
|
|
777
|
+
|
|
778
|
+
// src/lib/ensure-frankie-styles.ts
|
|
779
|
+
var STYLE_ELEMENT_ID = "frankie-fallback-styles";
|
|
780
|
+
var attempted = false;
|
|
781
|
+
function ensureFrankieStyles() {
|
|
782
|
+
if (attempted || typeof document === "undefined") return;
|
|
783
|
+
attempted = true;
|
|
784
|
+
if (document.getElementById(STYLE_ELEMENT_ID)) return;
|
|
785
|
+
const style = document.createElement("style");
|
|
786
|
+
style.id = STYLE_ELEMENT_ID;
|
|
787
|
+
style.textContent = FRANKIE_CSS_CONTENT;
|
|
788
|
+
document.head.appendChild(style);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
// src/lib/styles-ready.ts
|
|
606
792
|
var useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect3 : useLayoutEffect;
|
|
607
793
|
var SENTINEL = "--frankie-styles-ready";
|
|
608
|
-
function useStylesReady(ref, timeoutMs = 600) {
|
|
794
|
+
function useStylesReady(ref, timeoutMs = 600, injectFallback = true) {
|
|
609
795
|
const [ready, setReady] = useState3(false);
|
|
610
796
|
useIsomorphicLayoutEffect(() => {
|
|
611
797
|
if (ready) return;
|
|
@@ -622,7 +808,12 @@ function useStylesReady(ref, timeoutMs = 600) {
|
|
|
622
808
|
const deadline = Date.now() + timeoutMs;
|
|
623
809
|
let frame = 0;
|
|
624
810
|
const poll = () => {
|
|
625
|
-
if (applied()
|
|
811
|
+
if (applied()) {
|
|
812
|
+
setReady(true);
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
if (Date.now() >= deadline) {
|
|
816
|
+
if (injectFallback) ensureFrankieStyles();
|
|
626
817
|
setReady(true);
|
|
627
818
|
return;
|
|
628
819
|
}
|
|
@@ -634,7 +825,7 @@ function useStylesReady(ref, timeoutMs = 600) {
|
|
|
634
825
|
}
|
|
635
826
|
frame = window.requestAnimationFrame(poll);
|
|
636
827
|
return () => window.cancelAnimationFrame(frame);
|
|
637
|
-
}, [ref, ready, timeoutMs]);
|
|
828
|
+
}, [ref, ready, timeoutMs, injectFallback]);
|
|
638
829
|
return ready;
|
|
639
830
|
}
|
|
640
831
|
|
|
@@ -932,35 +1123,6 @@ function Composer({ onSend, onStop, busy, placeholder, disabled, autoFocus, atta
|
|
|
932
1123
|
)) }) : null,
|
|
933
1124
|
fileError ? /* @__PURE__ */ jsx8("p", { className: "text-xs text-red-600 dark:text-red-400", children: fileError }) : null,
|
|
934
1125
|
/* @__PURE__ */ jsxs2("div", { className: "frankie-composer-controls flex items-end gap-2", children: [
|
|
935
|
-
canAttach ? /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
936
|
-
/* @__PURE__ */ jsx8(
|
|
937
|
-
"input",
|
|
938
|
-
{
|
|
939
|
-
ref: fileInputRef,
|
|
940
|
-
type: "file",
|
|
941
|
-
multiple: true,
|
|
942
|
-
hidden: true,
|
|
943
|
-
accept: attachments?.accept.join(","),
|
|
944
|
-
onChange: (event) => {
|
|
945
|
-
void addFiles(event.target.files);
|
|
946
|
-
event.target.value = "";
|
|
947
|
-
}
|
|
948
|
-
}
|
|
949
|
-
),
|
|
950
|
-
/* @__PURE__ */ jsx8(
|
|
951
|
-
Button,
|
|
952
|
-
{
|
|
953
|
-
type: "button",
|
|
954
|
-
size: "icon",
|
|
955
|
-
variant: "outline",
|
|
956
|
-
disabled: disabled || files.length >= (attachments?.maxCount ?? 0),
|
|
957
|
-
onClick: () => fileInputRef.current?.click(),
|
|
958
|
-
"aria-label": "Attach files",
|
|
959
|
-
title: "Attach files",
|
|
960
|
-
children: /* @__PURE__ */ jsx8(Plus, {})
|
|
961
|
-
}
|
|
962
|
-
)
|
|
963
|
-
] }) : null,
|
|
964
1126
|
/* @__PURE__ */ jsx8(
|
|
965
1127
|
Textarea,
|
|
966
1128
|
{
|
|
@@ -975,17 +1137,51 @@ function Composer({ onSend, onStop, busy, placeholder, disabled, autoFocus, atta
|
|
|
975
1137
|
"aria-label": "Message"
|
|
976
1138
|
}
|
|
977
1139
|
),
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1140
|
+
/* @__PURE__ */ jsxs2("div", { className: "frankie-composer-actions", children: [
|
|
1141
|
+
canAttach ? /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
1142
|
+
/* @__PURE__ */ jsx8(
|
|
1143
|
+
"input",
|
|
1144
|
+
{
|
|
1145
|
+
ref: fileInputRef,
|
|
1146
|
+
type: "file",
|
|
1147
|
+
multiple: true,
|
|
1148
|
+
hidden: true,
|
|
1149
|
+
accept: attachments?.accept.join(","),
|
|
1150
|
+
onChange: (event) => {
|
|
1151
|
+
void addFiles(event.target.files);
|
|
1152
|
+
event.target.value = "";
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
),
|
|
1156
|
+
/* @__PURE__ */ jsx8(
|
|
1157
|
+
Button,
|
|
1158
|
+
{
|
|
1159
|
+
type: "button",
|
|
1160
|
+
size: "icon",
|
|
1161
|
+
variant: "outline",
|
|
1162
|
+
disabled: disabled || files.length >= (attachments?.maxCount ?? 0),
|
|
1163
|
+
onClick: () => fileInputRef.current?.click(),
|
|
1164
|
+
"aria-label": "Attach files",
|
|
1165
|
+
title: "Attach files",
|
|
1166
|
+
children: /* @__PURE__ */ jsx8(Plus, {})
|
|
1167
|
+
}
|
|
1168
|
+
)
|
|
1169
|
+
] }) : null,
|
|
1170
|
+
models.length > 0 ? /* @__PURE__ */ jsxs2("label", { className: "frankie-model-picker", title: "Model", children: [
|
|
1171
|
+
/* @__PURE__ */ jsx8("span", { className: "frankie-sr-only", children: "Model" }),
|
|
1172
|
+
/* @__PURE__ */ jsx8(
|
|
1173
|
+
"select",
|
|
1174
|
+
{
|
|
1175
|
+
value: modelPicker?.value ?? models[0]?.id,
|
|
1176
|
+
onChange: (event) => modelPicker?.onChange(event.target.value),
|
|
1177
|
+
disabled,
|
|
1178
|
+
"aria-label": "Model",
|
|
1179
|
+
className: "frankie-model-select",
|
|
1180
|
+
children: models.map((model) => /* @__PURE__ */ jsx8("option", { value: model.id, children: model.label }, model.id))
|
|
1181
|
+
}
|
|
1182
|
+
)
|
|
1183
|
+
] }) : null
|
|
1184
|
+
] }),
|
|
989
1185
|
busy ? /* @__PURE__ */ jsx8(Button, { type: "button", size: "icon", variant: "outline", onClick: onStop, "aria-label": "Stop", children: /* @__PURE__ */ jsx8(Square, {}) }) : /* @__PURE__ */ jsx8(Button, { type: "submit", size: "icon", disabled: disabled || nothingToSend, "aria-label": "Send", children: /* @__PURE__ */ jsx8(ArrowUp, {}) })
|
|
990
1186
|
] })
|
|
991
1187
|
]
|
|
@@ -1181,7 +1377,7 @@ function renderToken(token) {
|
|
|
1181
1377
|
}
|
|
1182
1378
|
|
|
1183
1379
|
// src/components/chat/ToolActivityList.tsx
|
|
1184
|
-
import { Check, ChevronDown, Loader2, ShieldAlert, X as X2 } from "lucide-react";
|
|
1380
|
+
import { Check, ChevronDown, Loader2, ShieldAlert, Wrench, X as X2 } from "lucide-react";
|
|
1185
1381
|
|
|
1186
1382
|
// src/components/ui/badge.tsx
|
|
1187
1383
|
import { cva as cva2 } from "class-variance-authority";
|
|
@@ -1216,6 +1412,7 @@ function ToolActivityList({ tools, renderTool }) {
|
|
|
1216
1412
|
return /* @__PURE__ */ jsxs4("details", { className: "frankie-tool-activity mt-2", open: running, children: [
|
|
1217
1413
|
/* @__PURE__ */ jsxs4("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: [
|
|
1218
1414
|
/* @__PURE__ */ jsx12(ChevronDown, { className: "frankie-tool-chevron size-3.5 transition-transform", "aria-hidden": "true" }),
|
|
1415
|
+
running ? /* @__PURE__ */ jsx12(Loader2, { className: "size-3.5 animate-spin", "aria-hidden": "true" }) : /* @__PURE__ */ jsx12(Wrench, { className: "size-3.5", "aria-hidden": "true" }),
|
|
1219
1416
|
/* @__PURE__ */ jsx12("span", { children: running ? "Working" : "Activity" }),
|
|
1220
1417
|
/* @__PURE__ */ jsxs4("span", { className: "text-[11px]", children: [
|
|
1221
1418
|
"\xB7 ",
|
|
@@ -1284,18 +1481,20 @@ var MessageBubble = memo2(function MessageBubble2({
|
|
|
1284
1481
|
] }) : null
|
|
1285
1482
|
] });
|
|
1286
1483
|
}
|
|
1287
|
-
const empty = message.text.length === 0 && message.tools.length === 0;
|
|
1288
1484
|
const { body, options } = extractChoices(message.text);
|
|
1289
1485
|
const showChoices = Boolean(active && onChoose && options && message.status === "done");
|
|
1486
|
+
const toolsRunning = message.tools.some((tool) => tool.status === "running");
|
|
1487
|
+
const waiting = message.status === "streaming" && body.length === 0 && !toolsRunning;
|
|
1290
1488
|
return /* @__PURE__ */ jsxs5("div", { className: "frankie-assistant-message flex gap-2", children: [
|
|
1291
1489
|
/* @__PURE__ */ jsxs5("div", { className: "frankie-assistant-actions mt-0.5 flex shrink-0 flex-col items-center gap-1", children: [
|
|
1292
1490
|
/* @__PURE__ */ jsx13(Avatar, { ...avatarUrl ? { src: avatarUrl } : {}, name: assistantName }),
|
|
1293
1491
|
body ? /* @__PURE__ */ jsx13(MessageCopyButton, { text: body }) : null
|
|
1294
1492
|
] }),
|
|
1295
1493
|
/* @__PURE__ */ jsxs5("div", { className: "frankie-assistant-content min-w-0 max-w-[85%]", children: [
|
|
1296
|
-
message.status === "error" ? /* @__PURE__ */ jsx13("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 }) :
|
|
1494
|
+
message.status === "error" ? /* @__PURE__ */ jsx13("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 }) : body ? /* @__PURE__ */ jsx13(Markdown, { text: body, className: "text-sm" }) : null,
|
|
1297
1495
|
showChoices ? /* @__PURE__ */ jsx13(Choices, { options: options ?? [], onPick: onChoose, ...onRequestInput ? { onSomethingElse: onRequestInput } : {}, disabled: busy }) : null,
|
|
1298
|
-
showToolActivity ? /* @__PURE__ */ jsx13(ToolActivityList, { tools: message.tools, ...renderTool ? { renderTool } : {} }) : null
|
|
1496
|
+
showToolActivity ? /* @__PURE__ */ jsx13(ToolActivityList, { tools: message.tools, ...renderTool ? { renderTool } : {} }) : null,
|
|
1497
|
+
waiting ? /* @__PURE__ */ jsx13(Thinking, {}) : null
|
|
1299
1498
|
] })
|
|
1300
1499
|
] });
|
|
1301
1500
|
});
|
|
@@ -1324,6 +1523,13 @@ function TypingDots() {
|
|
|
1324
1523
|
))
|
|
1325
1524
|
] });
|
|
1326
1525
|
}
|
|
1526
|
+
function Thinking() {
|
|
1527
|
+
return /* @__PURE__ */ jsxs5("div", { className: "frankie-skeleton", role: "status", "aria-label": "Thinking", children: [
|
|
1528
|
+
/* @__PURE__ */ jsx13("span", { className: "frankie-skeleton-line", style: { width: "62%" } }),
|
|
1529
|
+
/* @__PURE__ */ jsx13("span", { className: "frankie-skeleton-line", style: { width: "92%" } }),
|
|
1530
|
+
/* @__PURE__ */ jsx13("span", { className: "frankie-skeleton-line", style: { width: "48%" } })
|
|
1531
|
+
] });
|
|
1532
|
+
}
|
|
1327
1533
|
|
|
1328
1534
|
// src/components/chat/SuggestedPrompts.tsx
|
|
1329
1535
|
import { jsx as jsx14 } from "react/jsx-runtime";
|
|
@@ -1469,10 +1675,10 @@ function FrankieWidget({
|
|
|
1469
1675
|
renderLauncher,
|
|
1470
1676
|
...panelProps
|
|
1471
1677
|
}) {
|
|
1472
|
-
const { config } = useFrankie();
|
|
1678
|
+
const { config, injectStyles } = useFrankie();
|
|
1473
1679
|
const theme = useFrankieTheme();
|
|
1474
1680
|
const rootRef = useRef5(null);
|
|
1475
|
-
const stylesReady = useStylesReady(rootRef);
|
|
1681
|
+
const stylesReady = useStylesReady(rootRef, void 0, injectStyles);
|
|
1476
1682
|
const [uncontrolledOpen, setUncontrolledOpen] = useState7(defaultOpen);
|
|
1477
1683
|
const open = controlledOpen ?? uncontrolledOpen;
|
|
1478
1684
|
const setOpen = (next) => {
|
|
@@ -1521,10 +1727,11 @@ function FrankieWidget({
|
|
|
1521
1727
|
import { useRef as useRef6 } from "react";
|
|
1522
1728
|
import { jsx as jsx17 } from "react/jsx-runtime";
|
|
1523
1729
|
function FrankieInline({ className, style, ...panelProps }) {
|
|
1730
|
+
const { injectStyles } = useFrankie();
|
|
1524
1731
|
const theme = useFrankieTheme();
|
|
1525
1732
|
const mode = resolveMode(theme?.mode);
|
|
1526
1733
|
const rootRef = useRef6(null);
|
|
1527
|
-
const stylesReady = useStylesReady(rootRef);
|
|
1734
|
+
const stylesReady = useStylesReady(rootRef, void 0, injectStyles);
|
|
1528
1735
|
return /* @__PURE__ */ jsx17(
|
|
1529
1736
|
"div",
|
|
1530
1737
|
{
|
|
@@ -1696,7 +1903,7 @@ function FrankieChatPage({ className, style, header, footer, emptyState, renderM
|
|
|
1696
1903
|
const [id, setId] = useState8(() => provider.conversationId ?? crypto.randomUUID());
|
|
1697
1904
|
const onNew = () => setId(crypto.randomUUID());
|
|
1698
1905
|
const pageRef = useRef7(null);
|
|
1699
|
-
const stylesReady = useStylesReady(pageRef);
|
|
1906
|
+
const stylesReady = useStylesReady(pageRef, void 0, provider.injectStyles);
|
|
1700
1907
|
return /* @__PURE__ */ jsx18("div", { ref: pageRef, className: ["frankie-root", "frankie-chat-page", className].filter(Boolean).join(" "), style: { height: "100%", minHeight: "min(36rem, 100dvh)", width: "100%", ...style, ...stylesReady ? null : { opacity: 0 } }, "data-frankie-styles": stylesReady ? "ready" : "pending", children: /* @__PURE__ */ jsx18(FrankieProvider, { ...provider, conversationId: id, storageNamespace: (provider.storageNamespace ?? "frankie." + provider.publishableKey) + "." + id, children: /* @__PURE__ */ jsx18(PageChrome, { showHistory, navigation, currentId: id, onSelect: setId, onNew, header, footer, emptyState, renderMessage, renderTool, placeholder, autoFocus }) }) });
|
|
1701
1908
|
}
|
|
1702
1909
|
|
|
@@ -1731,6 +1938,7 @@ export {
|
|
|
1731
1938
|
Separator,
|
|
1732
1939
|
SuggestedPrompts,
|
|
1733
1940
|
Textarea,
|
|
1941
|
+
Thinking,
|
|
1734
1942
|
ToolActivityList,
|
|
1735
1943
|
ToolActivityRow,
|
|
1736
1944
|
TypingDots,
|