@frankieai/react 0.2.8 → 0.2.10
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 +596 -203
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +114 -10
- package/dist/index.d.ts +114 -10
- package/dist/index.js +579 -189
- package/dist/index.js.map +1 -1
- package/package.json +6 -3
- package/src/components/chat/ChatPanel.tsx +9 -1
- package/src/components/chat/Choices.tsx +52 -0
- package/src/components/chat/Composer.tsx +33 -18
- package/src/components/chat/FrankieChatPage.tsx +4 -1
- package/src/components/chat/FrankieInline.tsx +9 -3
- package/src/components/chat/FrankieWidget.tsx +12 -3
- package/src/components/chat/Markdown.tsx +15 -8
- package/src/components/chat/MessageBubble.tsx +53 -10
- 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/hooks/useFrankieChat.ts +57 -1
- package/src/index.ts +4 -1
- package/src/lib/choices.ts +49 -0
- 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 +83 -0
- package/src/lib/theme-inherit.test.ts +68 -0
- package/src/lib/theme-inherit.ts +89 -0
- package/src/styles/frankie.css +136 -3
- package/src/styles-ready.test.tsx +98 -0
- package/src/widget.test.tsx +218 -0
package/dist/index.cjs
CHANGED
|
@@ -31,6 +31,7 @@ __export(index_exports, {
|
|
|
31
31
|
CardHeader: () => CardHeader,
|
|
32
32
|
CardTitle: () => CardTitle,
|
|
33
33
|
ChatPanel: () => ChatPanel,
|
|
34
|
+
Choices: () => Choices,
|
|
34
35
|
Composer: () => Composer,
|
|
35
36
|
FRANKIE_CLOUD_URL: () => FRANKIE_CLOUD_URL,
|
|
36
37
|
FrankieApiError: () => FrankieApiError,
|
|
@@ -45,12 +46,14 @@ __export(index_exports, {
|
|
|
45
46
|
Separator: () => Separator,
|
|
46
47
|
SuggestedPrompts: () => SuggestedPrompts,
|
|
47
48
|
Textarea: () => Textarea,
|
|
49
|
+
Thinking: () => Thinking,
|
|
48
50
|
ToolActivityList: () => ToolActivityList,
|
|
49
51
|
ToolActivityRow: () => ToolActivityRow,
|
|
50
52
|
TypingDots: () => TypingDots,
|
|
51
53
|
applyEvent: () => applyEvent,
|
|
52
54
|
buttonVariants: () => buttonVariants,
|
|
53
55
|
cn: () => cn,
|
|
56
|
+
extractChoices: () => extractChoices,
|
|
54
57
|
newAssistantMessage: () => newAssistantMessage,
|
|
55
58
|
newUserMessage: () => newUserMessage,
|
|
56
59
|
readEventStream: () => readEventStream,
|
|
@@ -65,6 +68,31 @@ module.exports = __toCommonJS(index_exports);
|
|
|
65
68
|
// src/hooks/FrankieProvider.tsx
|
|
66
69
|
var import_react2 = require("react");
|
|
67
70
|
|
|
71
|
+
// src/lib/identity.ts
|
|
72
|
+
function sessionIdentity(token) {
|
|
73
|
+
if (!token) return void 0;
|
|
74
|
+
return jwtSubject(token) ?? token;
|
|
75
|
+
}
|
|
76
|
+
function jwtSubject(token) {
|
|
77
|
+
const parts = token.split(".");
|
|
78
|
+
if (parts.length !== 3 || !parts[1]) return void 0;
|
|
79
|
+
try {
|
|
80
|
+
const payload = JSON.parse(base64UrlDecode(parts[1]));
|
|
81
|
+
return typeof payload.sub === "string" && payload.sub ? payload.sub : void 0;
|
|
82
|
+
} catch {
|
|
83
|
+
return void 0;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function base64UrlDecode(segment) {
|
|
87
|
+
const base64 = segment.replace(/-/g, "+").replace(/_/g, "/");
|
|
88
|
+
const padded = base64 + "=".repeat((4 - base64.length % 4) % 4);
|
|
89
|
+
if (typeof atob === "function") {
|
|
90
|
+
const bytes = atob(padded);
|
|
91
|
+
return decodeURIComponent(Array.from(bytes, (c) => "%" + c.charCodeAt(0).toString(16).padStart(2, "0")).join(""));
|
|
92
|
+
}
|
|
93
|
+
return Buffer.from(padded, "base64").toString("utf8");
|
|
94
|
+
}
|
|
95
|
+
|
|
68
96
|
// src/lib/client.ts
|
|
69
97
|
var FRANKIE_CLOUD_URL = "https://frankie-control-665333762719.us-central1.run.app";
|
|
70
98
|
var FrankieApiError = class extends Error {
|
|
@@ -93,7 +121,8 @@ var FrankieClient = class {
|
|
|
93
121
|
baseUrl;
|
|
94
122
|
doFetch;
|
|
95
123
|
session;
|
|
96
|
-
|
|
124
|
+
/** Identity (JWT `sub`, else the raw token) the cached session was issued for. */
|
|
125
|
+
sessionUserKey;
|
|
97
126
|
get sessionId() {
|
|
98
127
|
return this.session?.sessionId;
|
|
99
128
|
}
|
|
@@ -135,7 +164,18 @@ var FrankieClient = class {
|
|
|
135
164
|
return await this.unwrap(response);
|
|
136
165
|
}
|
|
137
166
|
async ensureSession(context) {
|
|
138
|
-
|
|
167
|
+
let userToken;
|
|
168
|
+
try {
|
|
169
|
+
userToken = await this.options.getUserToken?.();
|
|
170
|
+
} catch (error) {
|
|
171
|
+
this.resetSession();
|
|
172
|
+
const reason = error instanceof Error && error.message.trim() ? error.message.trim() : void 0;
|
|
173
|
+
throw new FrankieApiError(
|
|
174
|
+
reason ?? "Your signed-in Frankie session could not be verified. Refresh the page and sign in again.",
|
|
175
|
+
401,
|
|
176
|
+
"user_token_unavailable"
|
|
177
|
+
);
|
|
178
|
+
}
|
|
139
179
|
if (this.options.getUserToken && !userToken) {
|
|
140
180
|
this.resetSession();
|
|
141
181
|
throw new FrankieApiError(
|
|
@@ -144,7 +184,8 @@ var FrankieClient = class {
|
|
|
144
184
|
"user_token_unavailable"
|
|
145
185
|
);
|
|
146
186
|
}
|
|
147
|
-
|
|
187
|
+
const userKey = sessionIdentity(userToken);
|
|
188
|
+
if (userKey !== this.sessionUserKey) this.resetSession();
|
|
148
189
|
if (this.session && new Date(this.session.expiresAt).getTime() > Date.now() + 3e4) return this.session;
|
|
149
190
|
const response = await this.doFetch(`${this.baseUrl}/v1/session`, {
|
|
150
191
|
method: "POST",
|
|
@@ -156,7 +197,7 @@ var FrankieClient = class {
|
|
|
156
197
|
})
|
|
157
198
|
});
|
|
158
199
|
this.session = await this.unwrap(response);
|
|
159
|
-
this.
|
|
200
|
+
this.sessionUserKey = userKey;
|
|
160
201
|
this.writeStored();
|
|
161
202
|
return this.session;
|
|
162
203
|
}
|
|
@@ -178,7 +219,7 @@ var FrankieClient = class {
|
|
|
178
219
|
if (response.status === 404) return void 0;
|
|
179
220
|
const body = await this.unwrap(response);
|
|
180
221
|
this.session = body.session;
|
|
181
|
-
this.
|
|
222
|
+
this.sessionUserKey = sessionIdentity(userToken);
|
|
182
223
|
this.writeStored();
|
|
183
224
|
return { messages: Array.isArray(body.messages) ? body.messages : [], title: body.title ?? "New chat" };
|
|
184
225
|
}
|
|
@@ -237,9 +278,59 @@ var FrankieClient = class {
|
|
|
237
278
|
return;
|
|
238
279
|
}
|
|
239
280
|
for await (const event of readEventStream(response.body)) {
|
|
281
|
+
if (event.type === "tool_call_request") {
|
|
282
|
+
void this.runClientTool(session, event.callId, event.request);
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
240
285
|
onEvent(event);
|
|
241
286
|
}
|
|
242
287
|
}
|
|
288
|
+
/**
|
|
289
|
+
* Execute one host API call in the browser and report the outcome back to the
|
|
290
|
+
* server. Never throws: a transport failure or a missing `api` client becomes
|
|
291
|
+
* a result the model can see and explain, exactly like a non-2xx status.
|
|
292
|
+
*/
|
|
293
|
+
async runClientTool(session, callId, request) {
|
|
294
|
+
const result = await this.callHostApi(request);
|
|
295
|
+
await this.postToolResult(session, callId, result).catch(() => void 0);
|
|
296
|
+
}
|
|
297
|
+
async callHostApi(request) {
|
|
298
|
+
const api = this.options.api;
|
|
299
|
+
if (!api) {
|
|
300
|
+
return {
|
|
301
|
+
status: 0,
|
|
302
|
+
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)."
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
try {
|
|
306
|
+
const extra = api.headers ? await api.headers() : {};
|
|
307
|
+
const response = await this.doFetch(joinHostUrl(api.baseUrl, request.path), {
|
|
308
|
+
method: request.method,
|
|
309
|
+
credentials: api.credentials ?? "same-origin",
|
|
310
|
+
headers: {
|
|
311
|
+
accept: "application/json, text/plain;q=0.9, */*;q=0.8",
|
|
312
|
+
...request.body !== void 0 ? { "content-type": "application/json" } : {},
|
|
313
|
+
...extra
|
|
314
|
+
},
|
|
315
|
+
...request.body !== void 0 ? { body: JSON.stringify(request.body) } : {}
|
|
316
|
+
});
|
|
317
|
+
const body = await response.text().catch(() => "");
|
|
318
|
+
return { status: response.status, body };
|
|
319
|
+
} catch (error) {
|
|
320
|
+
return { status: 0, error: error instanceof Error ? error.message : String(error) };
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
async postToolResult(session, callId, result) {
|
|
324
|
+
await this.doFetch(`${this.baseUrl}/v1/chat/tool-result`, {
|
|
325
|
+
method: "POST",
|
|
326
|
+
headers: {
|
|
327
|
+
"content-type": "application/json",
|
|
328
|
+
"x-frankie-key": this.options.publishableKey,
|
|
329
|
+
authorization: `Bearer ${session.sessionToken}`
|
|
330
|
+
},
|
|
331
|
+
body: JSON.stringify({ callId, result })
|
|
332
|
+
});
|
|
333
|
+
}
|
|
243
334
|
async unwrap(response) {
|
|
244
335
|
if (response.ok) return response.json();
|
|
245
336
|
let code = "internal";
|
|
@@ -253,6 +344,12 @@ var FrankieClient = class {
|
|
|
253
344
|
throw new FrankieApiError(message, response.status, code);
|
|
254
345
|
}
|
|
255
346
|
};
|
|
347
|
+
function joinHostUrl(baseUrl, path) {
|
|
348
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(path) || path.startsWith("//")) {
|
|
349
|
+
throw new Error("A client tool path must be relative to the configured host API base URL.");
|
|
350
|
+
}
|
|
351
|
+
return `${baseUrl.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
|
|
352
|
+
}
|
|
256
353
|
async function* readEventStream(body) {
|
|
257
354
|
const reader = body.getReader();
|
|
258
355
|
const decoder = new TextDecoder();
|
|
@@ -285,6 +382,68 @@ function parseFrame(frame) {
|
|
|
285
382
|
}
|
|
286
383
|
}
|
|
287
384
|
|
|
385
|
+
// src/lib/theme-inherit.ts
|
|
386
|
+
function parseRgb(value) {
|
|
387
|
+
const match = value.match(/rgba?\(\s*([\d.]+)[,\s]+([\d.]+)[,\s]+([\d.]+)(?:\s*[,/]\s*([\d.]+%?))?\s*\)/i);
|
|
388
|
+
if (!match) return null;
|
|
389
|
+
const alpha = match[4] === void 0 ? 1 : match[4].endsWith("%") ? Number(match[4].slice(0, -1)) / 100 : Number(match[4]);
|
|
390
|
+
return [Number(match[1]), Number(match[2]), Number(match[3]), alpha];
|
|
391
|
+
}
|
|
392
|
+
function toCss([r, g, b, a]) {
|
|
393
|
+
return a >= 1 ? `rgb(${r}, ${g}, ${b})` : `rgba(${r}, ${g}, ${b}, ${a})`;
|
|
394
|
+
}
|
|
395
|
+
function sameColor(a, b) {
|
|
396
|
+
return Math.abs(a[0] - b[0]) < 12 && Math.abs(a[1] - b[1]) < 12 && Math.abs(a[2] - b[2]) < 12;
|
|
397
|
+
}
|
|
398
|
+
function luminance([r, g, b]) {
|
|
399
|
+
const channel = (c) => {
|
|
400
|
+
const v = c / 255;
|
|
401
|
+
return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;
|
|
402
|
+
};
|
|
403
|
+
return 0.2126 * channel(r) + 0.7152 * channel(g) + 0.0722 * channel(b);
|
|
404
|
+
}
|
|
405
|
+
function contrastRatio(a, b) {
|
|
406
|
+
const la = luminance(a), lb = luminance(b);
|
|
407
|
+
return (Math.max(la, lb) + 0.05) / (Math.min(la, lb) + 0.05);
|
|
408
|
+
}
|
|
409
|
+
var ACCENT_SELECTORS = ["[data-frankie-accent]", "button[type=submit]", "a[href]", "button", "[class*=primary]", "[class*=btn]"];
|
|
410
|
+
function inferHostTheme() {
|
|
411
|
+
if (typeof document === "undefined" || typeof getComputedStyle !== "function") return null;
|
|
412
|
+
try {
|
|
413
|
+
const bodyStyle = getComputedStyle(document.body);
|
|
414
|
+
const background = parseRgb(bodyStyle.backgroundColor);
|
|
415
|
+
const foreground = parseRgb(bodyStyle.color);
|
|
416
|
+
if (!background || !foreground) return null;
|
|
417
|
+
if (background[3] < 0.5) return null;
|
|
418
|
+
if (contrastRatio(background, foreground) < 2) return null;
|
|
419
|
+
let accent = null;
|
|
420
|
+
for (const selector of ACCENT_SELECTORS) {
|
|
421
|
+
const element = document.querySelector(selector);
|
|
422
|
+
if (!element) continue;
|
|
423
|
+
const style = getComputedStyle(element);
|
|
424
|
+
const surface = parseRgb(style.backgroundColor);
|
|
425
|
+
if (surface && surface[3] > 0.5 && !sameColor(surface, background)) {
|
|
426
|
+
accent = surface;
|
|
427
|
+
break;
|
|
428
|
+
}
|
|
429
|
+
const text = parseRgb(style.color);
|
|
430
|
+
if (text && text[3] > 0.5 && !sameColor(text, foreground)) {
|
|
431
|
+
accent = text;
|
|
432
|
+
break;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
if (!accent) return null;
|
|
436
|
+
return {
|
|
437
|
+
background: toCss(background),
|
|
438
|
+
foreground: toCss(foreground),
|
|
439
|
+
primary: toCss(accent),
|
|
440
|
+
primaryForeground: luminance(accent) > 0.5 ? "#0a0a0a" : "#fafafa"
|
|
441
|
+
};
|
|
442
|
+
} catch {
|
|
443
|
+
return null;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
288
447
|
// src/hooks/useFrankieChat.ts
|
|
289
448
|
var import_react = require("react");
|
|
290
449
|
|
|
@@ -463,6 +622,41 @@ function useFrankieChatState() {
|
|
|
463
622
|
setMessages(next);
|
|
464
623
|
const abort = new AbortController();
|
|
465
624
|
abortRef.current = abort;
|
|
625
|
+
let bufferedText = "";
|
|
626
|
+
let pendingThinking = "";
|
|
627
|
+
let frame = null;
|
|
628
|
+
const raf = typeof requestAnimationFrame === "function" ? requestAnimationFrame : (cb) => setTimeout(() => cb(performance.now()), 16);
|
|
629
|
+
const cancelRaf = typeof cancelAnimationFrame === "function" ? cancelAnimationFrame : clearTimeout;
|
|
630
|
+
const revealTick = () => {
|
|
631
|
+
frame = null;
|
|
632
|
+
let addText = "";
|
|
633
|
+
if (bufferedText) {
|
|
634
|
+
const step = Math.max(3, Math.ceil(bufferedText.length / 6));
|
|
635
|
+
addText = bufferedText.slice(0, step);
|
|
636
|
+
bufferedText = bufferedText.slice(step);
|
|
637
|
+
}
|
|
638
|
+
const addThinking = pendingThinking;
|
|
639
|
+
pendingThinking = "";
|
|
640
|
+
if (addText || addThinking) {
|
|
641
|
+
patchAssistant(assistantId, (message) => ({ ...message, text: message.text + addText, thinking: message.thinking + addThinking }));
|
|
642
|
+
}
|
|
643
|
+
if (bufferedText) scheduleReveal();
|
|
644
|
+
};
|
|
645
|
+
const scheduleReveal = () => {
|
|
646
|
+
if (frame === null) frame = raf(revealTick);
|
|
647
|
+
};
|
|
648
|
+
const flushDeltas = () => {
|
|
649
|
+
if (frame !== null) {
|
|
650
|
+
cancelRaf(frame);
|
|
651
|
+
frame = null;
|
|
652
|
+
}
|
|
653
|
+
if (!bufferedText && !pendingThinking) return;
|
|
654
|
+
const addText = bufferedText;
|
|
655
|
+
const addThinking = pendingThinking;
|
|
656
|
+
bufferedText = "";
|
|
657
|
+
pendingThinking = "";
|
|
658
|
+
patchAssistant(assistantId, (message) => ({ ...message, text: message.text + addText, thinking: message.thinking + addThinking }));
|
|
659
|
+
};
|
|
466
660
|
try {
|
|
467
661
|
let receivedText = false;
|
|
468
662
|
let receivedToolActivity = false;
|
|
@@ -484,11 +678,23 @@ function useFrankieChatState() {
|
|
|
484
678
|
return;
|
|
485
679
|
}
|
|
486
680
|
if (event.type === "ping" || event.type === "session") return;
|
|
487
|
-
if (event.type === "text_delta")
|
|
681
|
+
if (event.type === "text_delta") {
|
|
682
|
+
receivedText = true;
|
|
683
|
+
bufferedText += event.text;
|
|
684
|
+
scheduleReveal();
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
if (event.type === "thinking_delta") {
|
|
688
|
+
pendingThinking += event.text;
|
|
689
|
+
scheduleReveal();
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
488
692
|
if (event.type === "tool_start" || event.type === "tool_end") receivedToolActivity = true;
|
|
693
|
+
flushDeltas();
|
|
489
694
|
patchAssistant(assistantId, (message) => applyEvent(message, event));
|
|
490
695
|
}
|
|
491
696
|
);
|
|
697
|
+
flushDeltas();
|
|
492
698
|
if (!receivedText) {
|
|
493
699
|
patchAssistant(assistantId, (message) => ({
|
|
494
700
|
...message,
|
|
@@ -497,6 +703,7 @@ function useFrankieChatState() {
|
|
|
497
703
|
}));
|
|
498
704
|
}
|
|
499
705
|
} catch (caught) {
|
|
706
|
+
flushDeltas();
|
|
500
707
|
if (abort.signal.aborted) {
|
|
501
708
|
patchAssistant(assistantId, (message) => ({
|
|
502
709
|
...message,
|
|
@@ -509,6 +716,7 @@ function useFrankieChatState() {
|
|
|
509
716
|
patchAssistant(assistantId, (current) => ({ ...current, status: "error", error: message }));
|
|
510
717
|
}
|
|
511
718
|
} finally {
|
|
719
|
+
flushDeltas();
|
|
512
720
|
patchAssistant(
|
|
513
721
|
assistantId,
|
|
514
722
|
(message) => message.status === "streaming" ? { ...message, status: "done" } : message
|
|
@@ -556,6 +764,7 @@ function FrankieProvider({
|
|
|
556
764
|
context,
|
|
557
765
|
theme,
|
|
558
766
|
config: providedConfig,
|
|
767
|
+
injectStyles = true,
|
|
559
768
|
...clientOptions
|
|
560
769
|
}) {
|
|
561
770
|
const tokenCallback = (0, import_react2.useRef)(clientOptions.getUserToken);
|
|
@@ -563,7 +772,7 @@ function FrankieProvider({
|
|
|
563
772
|
const client = (0, import_react2.useMemo)(
|
|
564
773
|
() => new FrankieClient({ ...clientOptions, ...clientOptions.getUserToken ? { getUserToken: () => tokenCallback.current?.() } : {} }),
|
|
565
774
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
566
|
-
[clientOptions.baseUrl, clientOptions.publishableKey, clientOptions.storageNamespace, clientOptions.conversationId, clientOptions.persist, clientOptions.fetchImpl, Boolean(clientOptions.getUserToken)]
|
|
775
|
+
[clientOptions.baseUrl, clientOptions.publishableKey, clientOptions.storageNamespace, clientOptions.conversationId, clientOptions.persist, clientOptions.fetchImpl, clientOptions.api?.baseUrl, clientOptions.api?.credentials, Boolean(clientOptions.getUserToken)]
|
|
567
776
|
);
|
|
568
777
|
const [config, setConfig] = (0, import_react2.useState)(providedConfig);
|
|
569
778
|
const [configError, setConfigError] = (0, import_react2.useState)(void 0);
|
|
@@ -590,9 +799,10 @@ function FrankieProvider({
|
|
|
590
799
|
...configError ? { configError } : {},
|
|
591
800
|
loading,
|
|
592
801
|
...context ? { context } : {},
|
|
593
|
-
...theme ? { theme } : {}
|
|
802
|
+
...theme ? { theme } : {},
|
|
803
|
+
injectStyles
|
|
594
804
|
}),
|
|
595
|
-
[client, config, configError, loading, context, theme]
|
|
805
|
+
[client, config, configError, loading, context, theme, injectStyles]
|
|
596
806
|
);
|
|
597
807
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FrankieContext.Provider, { value, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConversationState, { children }, client.namespace + ":" + (client.conversationId ?? "")) });
|
|
598
808
|
}
|
|
@@ -607,12 +817,82 @@ function useFrankie() {
|
|
|
607
817
|
}
|
|
608
818
|
function useFrankieTheme() {
|
|
609
819
|
const { config, theme } = useFrankie();
|
|
610
|
-
|
|
820
|
+
const merged = (0, import_react2.useMemo)(() => ({ ...config?.theme, ...theme }), [config?.theme, theme]);
|
|
821
|
+
const inheriting = merged?.source === "inherit";
|
|
822
|
+
const [inferred, setInferred] = (0, import_react2.useState)(null);
|
|
823
|
+
(0, import_react2.useEffect)(() => {
|
|
824
|
+
setInferred(inheriting ? inferHostTheme() : null);
|
|
825
|
+
}, [inheriting]);
|
|
826
|
+
return (0, import_react2.useMemo)(() => {
|
|
827
|
+
if (!inheriting || !inferred) return merged;
|
|
828
|
+
return { ...merged, ...inferred };
|
|
829
|
+
}, [merged, inheriting, inferred]);
|
|
611
830
|
}
|
|
612
831
|
|
|
613
832
|
// src/components/chat/FrankieWidget.tsx
|
|
614
|
-
var
|
|
615
|
-
var
|
|
833
|
+
var import_lucide_react7 = require("lucide-react");
|
|
834
|
+
var import_react10 = require("react");
|
|
835
|
+
|
|
836
|
+
// src/lib/styles-ready.ts
|
|
837
|
+
var import_react3 = require("react");
|
|
838
|
+
|
|
839
|
+
// src/generated/css-content.ts
|
|
840
|
+
var FRANKIE_CSS_CONTENT = `/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
|
|
841
|
+
@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{: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;--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-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}}`;
|
|
842
|
+
|
|
843
|
+
// src/lib/ensure-frankie-styles.ts
|
|
844
|
+
var STYLE_ELEMENT_ID = "frankie-fallback-styles";
|
|
845
|
+
var attempted = false;
|
|
846
|
+
function ensureFrankieStyles() {
|
|
847
|
+
if (attempted || typeof document === "undefined") return;
|
|
848
|
+
attempted = true;
|
|
849
|
+
if (document.getElementById(STYLE_ELEMENT_ID)) return;
|
|
850
|
+
const style = document.createElement("style");
|
|
851
|
+
style.id = STYLE_ELEMENT_ID;
|
|
852
|
+
style.textContent = FRANKIE_CSS_CONTENT;
|
|
853
|
+
document.head.appendChild(style);
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// src/lib/styles-ready.ts
|
|
857
|
+
var useIsomorphicLayoutEffect = typeof window === "undefined" ? import_react3.useEffect : import_react3.useLayoutEffect;
|
|
858
|
+
var SENTINEL = "--frankie-styles-ready";
|
|
859
|
+
function useStylesReady(ref, timeoutMs = 600, injectFallback = true) {
|
|
860
|
+
const [ready, setReady] = (0, import_react3.useState)(false);
|
|
861
|
+
useIsomorphicLayoutEffect(() => {
|
|
862
|
+
if (ready) return;
|
|
863
|
+
const node = ref.current;
|
|
864
|
+
if (!node || typeof window === "undefined" || typeof window.getComputedStyle !== "function") {
|
|
865
|
+
setReady(true);
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
const applied = () => window.getComputedStyle(node).getPropertyValue(SENTINEL).trim() === "1";
|
|
869
|
+
if (applied()) {
|
|
870
|
+
setReady(true);
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
const deadline = Date.now() + timeoutMs;
|
|
874
|
+
let frame = 0;
|
|
875
|
+
const poll = () => {
|
|
876
|
+
if (applied()) {
|
|
877
|
+
setReady(true);
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
if (Date.now() >= deadline) {
|
|
881
|
+
if (injectFallback) ensureFrankieStyles();
|
|
882
|
+
setReady(true);
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
frame = window.requestAnimationFrame(poll);
|
|
886
|
+
};
|
|
887
|
+
if (typeof window.requestAnimationFrame !== "function") {
|
|
888
|
+
setReady(true);
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
frame = window.requestAnimationFrame(poll);
|
|
892
|
+
return () => window.cancelAnimationFrame(frame);
|
|
893
|
+
}, [ref, ready, timeoutMs, injectFallback]);
|
|
894
|
+
return ready;
|
|
895
|
+
}
|
|
616
896
|
|
|
617
897
|
// src/lib/theme.ts
|
|
618
898
|
function themeToCssVars(theme) {
|
|
@@ -672,8 +952,8 @@ function Button({ className, variant, size, asChild = false, ...props }) {
|
|
|
672
952
|
}
|
|
673
953
|
|
|
674
954
|
// src/components/chat/ChatPanel.tsx
|
|
675
|
-
var
|
|
676
|
-
var
|
|
955
|
+
var import_lucide_react6 = require("lucide-react");
|
|
956
|
+
var import_react9 = require("react");
|
|
677
957
|
|
|
678
958
|
// src/components/ui/avatar.tsx
|
|
679
959
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
@@ -723,9 +1003,9 @@ function CardFooter({ className, ...props }) {
|
|
|
723
1003
|
}
|
|
724
1004
|
|
|
725
1005
|
// src/components/ui/scroll-area.tsx
|
|
726
|
-
var
|
|
1006
|
+
var import_react4 = require("react");
|
|
727
1007
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
728
|
-
var ScrollArea = (0,
|
|
1008
|
+
var ScrollArea = (0, import_react4.forwardRef)(
|
|
729
1009
|
function ScrollArea2({ className, ...props }, ref) {
|
|
730
1010
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { ref, className: cn("frankie-scroll overflow-y-auto overscroll-contain", className), ...props });
|
|
731
1011
|
}
|
|
@@ -779,12 +1059,12 @@ function AgentContext() {
|
|
|
779
1059
|
|
|
780
1060
|
// src/components/chat/Composer.tsx
|
|
781
1061
|
var import_lucide_react2 = require("lucide-react");
|
|
782
|
-
var
|
|
1062
|
+
var import_react6 = require("react");
|
|
783
1063
|
|
|
784
1064
|
// src/components/ui/textarea.tsx
|
|
785
|
-
var
|
|
1065
|
+
var import_react5 = require("react");
|
|
786
1066
|
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
787
|
-
var Textarea = (0,
|
|
1067
|
+
var Textarea = (0, import_react5.forwardRef)(
|
|
788
1068
|
function Textarea2({ className, ...props }, ref) {
|
|
789
1069
|
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
790
1070
|
"textarea",
|
|
@@ -803,21 +1083,28 @@ var Textarea = (0, import_react4.forwardRef)(
|
|
|
803
1083
|
// src/components/chat/Composer.tsx
|
|
804
1084
|
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
805
1085
|
var MAX_ROWS_PX = 140;
|
|
806
|
-
function Composer({ onSend, onStop, busy, placeholder, disabled, autoFocus, attachments, modelPicker }) {
|
|
807
|
-
const [value, setValue] = (0,
|
|
808
|
-
const [files, setFiles] = (0,
|
|
809
|
-
const [fileError, setFileError] = (0,
|
|
810
|
-
const textareaRef = (0,
|
|
811
|
-
const fileInputRef = (0,
|
|
1086
|
+
function Composer({ onSend, onStop, busy, placeholder, disabled, autoFocus, attachments, modelPicker, focusHandleRef }) {
|
|
1087
|
+
const [value, setValue] = (0, import_react6.useState)("");
|
|
1088
|
+
const [files, setFiles] = (0, import_react6.useState)([]);
|
|
1089
|
+
const [fileError, setFileError] = (0, import_react6.useState)(void 0);
|
|
1090
|
+
const textareaRef = (0, import_react6.useRef)(null);
|
|
1091
|
+
const fileInputRef = (0, import_react6.useRef)(null);
|
|
1092
|
+
(0, import_react6.useEffect)(() => {
|
|
1093
|
+
if (!focusHandleRef) return;
|
|
1094
|
+
focusHandleRef.current = () => textareaRef.current?.focus();
|
|
1095
|
+
return () => {
|
|
1096
|
+
focusHandleRef.current = null;
|
|
1097
|
+
};
|
|
1098
|
+
}, [focusHandleRef]);
|
|
812
1099
|
const canAttach = Boolean(attachments?.enabled);
|
|
813
1100
|
const models = modelPicker?.models ?? [];
|
|
814
|
-
(0,
|
|
1101
|
+
(0, import_react6.useEffect)(() => {
|
|
815
1102
|
const node = textareaRef.current;
|
|
816
1103
|
if (!node) return;
|
|
817
1104
|
node.style.height = "auto";
|
|
818
1105
|
node.style.height = `${Math.min(node.scrollHeight, MAX_ROWS_PX)}px`;
|
|
819
1106
|
}, [value]);
|
|
820
|
-
const addFiles = (0,
|
|
1107
|
+
const addFiles = (0, import_react6.useCallback)(
|
|
821
1108
|
async (list) => {
|
|
822
1109
|
if (!list || !attachments) return;
|
|
823
1110
|
setFileError(void 0);
|
|
@@ -850,7 +1137,7 @@ function Composer({ onSend, onStop, busy, placeholder, disabled, autoFocus, atta
|
|
|
850
1137
|
[attachments, files.length]
|
|
851
1138
|
);
|
|
852
1139
|
const removeFile = (id) => setFiles((current) => current.filter((file) => file.id !== id));
|
|
853
|
-
const submit = (0,
|
|
1140
|
+
const submit = (0, import_react6.useCallback)(() => {
|
|
854
1141
|
const text = value.trim();
|
|
855
1142
|
if (!text && files.length === 0 || busy || disabled) return;
|
|
856
1143
|
setValue("");
|
|
@@ -944,19 +1231,22 @@ function Composer({ onSend, onStop, busy, placeholder, disabled, autoFocus, atta
|
|
|
944
1231
|
"aria-label": "Message"
|
|
945
1232
|
}
|
|
946
1233
|
),
|
|
947
|
-
|
|
1234
|
+
busy ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Button, { type: "button", size: "icon", variant: "outline", onClick: onStop, "aria-label": "Stop", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react2.Square, {}) }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Button, { type: "submit", size: "icon", disabled: disabled || nothingToSend, "aria-label": "Send", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react2.ArrowUp, {}) })
|
|
1235
|
+
] }),
|
|
1236
|
+
models.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "frankie-composer-meta", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { className: "frankie-model-picker", title: "Model", children: [
|
|
1237
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "frankie-sr-only", children: "Model" }),
|
|
1238
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
948
1239
|
"select",
|
|
949
1240
|
{
|
|
950
1241
|
value: modelPicker?.value ?? models[0]?.id,
|
|
951
1242
|
onChange: (event) => modelPicker?.onChange(event.target.value),
|
|
952
1243
|
disabled,
|
|
953
1244
|
"aria-label": "Model",
|
|
954
|
-
className: "frankie-
|
|
1245
|
+
className: "frankie-model-select",
|
|
955
1246
|
children: models.map((model) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("option", { value: model.id, children: model.label }, model.id))
|
|
956
1247
|
}
|
|
957
|
-
)
|
|
958
|
-
|
|
959
|
-
] })
|
|
1248
|
+
)
|
|
1249
|
+
] }) }) : null
|
|
960
1250
|
]
|
|
961
1251
|
}
|
|
962
1252
|
);
|
|
@@ -980,15 +1270,77 @@ function readAsBase64(file) {
|
|
|
980
1270
|
}
|
|
981
1271
|
|
|
982
1272
|
// src/components/chat/MessageBubble.tsx
|
|
983
|
-
var
|
|
984
|
-
var
|
|
1273
|
+
var import_lucide_react5 = require("lucide-react");
|
|
1274
|
+
var import_react8 = require("react");
|
|
985
1275
|
|
|
986
|
-
// src/
|
|
987
|
-
var
|
|
1276
|
+
// src/lib/choices.ts
|
|
1277
|
+
var CLOSED = /```frankie:choices[ \t]*\r?\n([\s\S]*?)```/;
|
|
1278
|
+
var OPEN = /```frankie:choices[ \t]*\r?\n?[\s\S]*$/;
|
|
1279
|
+
function extractChoices(text) {
|
|
1280
|
+
const closed = CLOSED.exec(text);
|
|
1281
|
+
if (closed) {
|
|
1282
|
+
const options = parseOptionLines(closed[1] ?? "");
|
|
1283
|
+
const body = (text.slice(0, closed.index) + text.slice(closed.index + closed[0].length)).replace(/\n{3,}/g, "\n\n").trimEnd();
|
|
1284
|
+
return { body, options: options.length ? options : null };
|
|
1285
|
+
}
|
|
1286
|
+
const open = OPEN.exec(text);
|
|
1287
|
+
if (open) return { body: text.slice(0, open.index).trimEnd(), options: null };
|
|
1288
|
+
return { body: text, options: null };
|
|
1289
|
+
}
|
|
1290
|
+
function parseOptionLines(block) {
|
|
1291
|
+
return block.split("\n").map((line) => line.trim().replace(/^([-*+]|\d+[.)])\s+/, "").trim()).filter(Boolean);
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
// src/components/chat/Choices.tsx
|
|
1295
|
+
var import_lucide_react3 = require("lucide-react");
|
|
988
1296
|
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
989
|
-
function
|
|
990
|
-
|
|
1297
|
+
function Choices({
|
|
1298
|
+
options,
|
|
1299
|
+
onPick,
|
|
1300
|
+
onSomethingElse,
|
|
1301
|
+
disabled
|
|
1302
|
+
}) {
|
|
1303
|
+
if (options.length === 0) return null;
|
|
1304
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "frankie-choices", role: "group", "aria-label": "Choose an option", children: [
|
|
1305
|
+
options.map((option, index) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
1306
|
+
"button",
|
|
1307
|
+
{
|
|
1308
|
+
type: "button",
|
|
1309
|
+
className: "frankie-choice",
|
|
1310
|
+
disabled,
|
|
1311
|
+
onClick: () => onPick(option),
|
|
1312
|
+
children: [
|
|
1313
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "frankie-choice-index", "aria-hidden": true, children: index + 1 }),
|
|
1314
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "frankie-choice-label", children: option }),
|
|
1315
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react3.ArrowRight, { className: "frankie-choice-go size-4", "aria-hidden": true })
|
|
1316
|
+
]
|
|
1317
|
+
},
|
|
1318
|
+
`${index}-${option}`
|
|
1319
|
+
)),
|
|
1320
|
+
onSomethingElse ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
1321
|
+
"button",
|
|
1322
|
+
{
|
|
1323
|
+
type: "button",
|
|
1324
|
+
className: "frankie-choice frankie-choice-other",
|
|
1325
|
+
disabled,
|
|
1326
|
+
onClick: onSomethingElse,
|
|
1327
|
+
children: [
|
|
1328
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "frankie-choice-index", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react3.PencilLine, { className: "size-3.5" }) }),
|
|
1329
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "frankie-choice-label", children: "Something else\u2026" }),
|
|
1330
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "frankie-choice-hint", "aria-hidden": true, children: "Type your own" })
|
|
1331
|
+
]
|
|
1332
|
+
}
|
|
1333
|
+
) : null
|
|
1334
|
+
] });
|
|
991
1335
|
}
|
|
1336
|
+
|
|
1337
|
+
// src/components/chat/Markdown.tsx
|
|
1338
|
+
var import_react7 = require("react");
|
|
1339
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
1340
|
+
var Markdown = (0, import_react7.memo)(function Markdown2({ text, className }) {
|
|
1341
|
+
const blocks = (0, import_react7.useMemo)(() => renderBlocks(text), [text]);
|
|
1342
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: cn("frankie-prose", className), children: blocks });
|
|
1343
|
+
});
|
|
992
1344
|
function renderBlocks(text) {
|
|
993
1345
|
const lines = text.split("\n");
|
|
994
1346
|
const blocks = [];
|
|
@@ -997,13 +1349,13 @@ function renderBlocks(text) {
|
|
|
997
1349
|
let code = null;
|
|
998
1350
|
const flushParagraph = () => {
|
|
999
1351
|
if (paragraph.length === 0) return;
|
|
1000
|
-
blocks.push(/* @__PURE__ */ (0,
|
|
1352
|
+
blocks.push(/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { children: renderInline(paragraph.join(" ")) }, `p${blocks.length}`));
|
|
1001
1353
|
paragraph = [];
|
|
1002
1354
|
};
|
|
1003
1355
|
const flushList = () => {
|
|
1004
1356
|
if (!list) return;
|
|
1005
|
-
const items = list.items.map((item, index) => /* @__PURE__ */ (0,
|
|
1006
|
-
blocks.push(list.ordered ? /* @__PURE__ */ (0,
|
|
1357
|
+
const items = list.items.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("li", { children: renderInline(item) }, index));
|
|
1358
|
+
blocks.push(list.ordered ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("ol", { children: items }, `l${blocks.length}`) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("ul", { children: items }, `l${blocks.length}`));
|
|
1007
1359
|
list = null;
|
|
1008
1360
|
};
|
|
1009
1361
|
for (const line of lines) {
|
|
@@ -1011,7 +1363,7 @@ function renderBlocks(text) {
|
|
|
1011
1363
|
if (fence) {
|
|
1012
1364
|
if (code) {
|
|
1013
1365
|
blocks.push(
|
|
1014
|
-
/* @__PURE__ */ (0,
|
|
1366
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("pre", { children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("code", { children: code.lines.join("\n") }) }, `c${blocks.length}`)
|
|
1015
1367
|
);
|
|
1016
1368
|
code = null;
|
|
1017
1369
|
} else {
|
|
@@ -1042,11 +1394,13 @@ function renderBlocks(text) {
|
|
|
1042
1394
|
flushList();
|
|
1043
1395
|
continue;
|
|
1044
1396
|
}
|
|
1045
|
-
const heading =
|
|
1397
|
+
const heading = /^(#{1,6})\s+(.*)$/.exec(line);
|
|
1046
1398
|
if (heading) {
|
|
1047
1399
|
flushParagraph();
|
|
1048
1400
|
flushList();
|
|
1049
|
-
|
|
1401
|
+
const level = (heading[1] ?? "#").length;
|
|
1402
|
+
const Tag = level <= 1 ? "h3" : level === 2 ? "h4" : "h5";
|
|
1403
|
+
blocks.push(/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Tag, { className: "frankie-h", children: renderInline(heading[2] ?? "") }, `h${blocks.length}`));
|
|
1050
1404
|
continue;
|
|
1051
1405
|
}
|
|
1052
1406
|
flushList();
|
|
@@ -1054,7 +1408,7 @@ function renderBlocks(text) {
|
|
|
1054
1408
|
}
|
|
1055
1409
|
if (code) {
|
|
1056
1410
|
blocks.push(
|
|
1057
|
-
/* @__PURE__ */ (0,
|
|
1411
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("pre", { children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("code", { children: code.lines.join("\n") }) }, `c${blocks.length}`)
|
|
1058
1412
|
);
|
|
1059
1413
|
}
|
|
1060
1414
|
flushParagraph();
|
|
@@ -1069,28 +1423,28 @@ function renderInline(text) {
|
|
|
1069
1423
|
const token = match[0];
|
|
1070
1424
|
const start = match.index;
|
|
1071
1425
|
if (start > lastIndex) nodes.push(text.slice(lastIndex, start));
|
|
1072
|
-
nodes.push(/* @__PURE__ */ (0,
|
|
1426
|
+
nodes.push(/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react7.Fragment, { children: renderToken(token) }, start));
|
|
1073
1427
|
lastIndex = start + token.length;
|
|
1074
1428
|
}
|
|
1075
1429
|
if (lastIndex < text.length) nodes.push(text.slice(lastIndex));
|
|
1076
1430
|
return nodes;
|
|
1077
1431
|
}
|
|
1078
1432
|
function renderToken(token) {
|
|
1079
|
-
if (token.startsWith("**")) return /* @__PURE__ */ (0,
|
|
1080
|
-
if (token.startsWith("`")) return /* @__PURE__ */ (0,
|
|
1433
|
+
if (token.startsWith("**")) return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("strong", { children: token.slice(2, -2) });
|
|
1434
|
+
if (token.startsWith("`")) return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("code", { children: token.slice(1, -1) });
|
|
1081
1435
|
const link = /^\[([^\]]+)\]\(([^)]+)\)$/.exec(token);
|
|
1082
1436
|
const href = link ? link[2] ?? "" : token;
|
|
1083
1437
|
const label = link ? link[1] ?? "" : token;
|
|
1084
1438
|
if (!/^(https?:\/\/|\/)/.test(href)) return token;
|
|
1085
|
-
return /* @__PURE__ */ (0,
|
|
1439
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("a", { href, target: "_blank", rel: "noopener noreferrer nofollow", children: label });
|
|
1086
1440
|
}
|
|
1087
1441
|
|
|
1088
1442
|
// src/components/chat/ToolActivityList.tsx
|
|
1089
|
-
var
|
|
1443
|
+
var import_lucide_react4 = require("lucide-react");
|
|
1090
1444
|
|
|
1091
1445
|
// src/components/ui/badge.tsx
|
|
1092
1446
|
var import_class_variance_authority2 = require("class-variance-authority");
|
|
1093
|
-
var
|
|
1447
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
1094
1448
|
var badgeVariants = (0, import_class_variance_authority2.cva)(
|
|
1095
1449
|
"inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-[0.6875rem] font-medium",
|
|
1096
1450
|
{
|
|
@@ -1110,44 +1464,45 @@ function Badge({
|
|
|
1110
1464
|
variant,
|
|
1111
1465
|
...props
|
|
1112
1466
|
}) {
|
|
1113
|
-
return /* @__PURE__ */ (0,
|
|
1467
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: cn(badgeVariants({ variant }), className), ...props });
|
|
1114
1468
|
}
|
|
1115
1469
|
|
|
1116
1470
|
// src/components/chat/ToolActivityList.tsx
|
|
1117
|
-
var
|
|
1471
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
1118
1472
|
function ToolActivityList({ tools, renderTool }) {
|
|
1119
1473
|
if (tools.length === 0) return null;
|
|
1120
1474
|
const running = tools.some((tool) => tool.status === "running");
|
|
1121
|
-
return /* @__PURE__ */ (0,
|
|
1122
|
-
/* @__PURE__ */ (0,
|
|
1123
|
-
/* @__PURE__ */ (0,
|
|
1124
|
-
/* @__PURE__ */ (0,
|
|
1125
|
-
/* @__PURE__ */ (0,
|
|
1475
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("details", { className: "frankie-tool-activity mt-2", open: running, children: [
|
|
1476
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.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: [
|
|
1477
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react4.ChevronDown, { className: "frankie-tool-chevron size-3.5 transition-transform", "aria-hidden": "true" }),
|
|
1478
|
+
running ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react4.Loader2, { className: "size-3.5 animate-spin", "aria-hidden": "true" }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react4.Wrench, { className: "size-3.5", "aria-hidden": "true" }),
|
|
1479
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { children: running ? "Working" : "Activity" }),
|
|
1480
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "text-[11px]", children: [
|
|
1126
1481
|
"\xB7 ",
|
|
1127
1482
|
tools.length,
|
|
1128
1483
|
" tool call",
|
|
1129
1484
|
tools.length === 1 ? "" : "s"
|
|
1130
1485
|
] })
|
|
1131
1486
|
] }),
|
|
1132
|
-
/* @__PURE__ */ (0,
|
|
1487
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("ul", { className: "mt-1 flex flex-col gap-1 border-l border-border pl-2", children: tools.map((tool) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("li", { children: renderTool ? renderTool(tool) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ToolActivityRow, { tool }) }, tool.id)) })
|
|
1133
1488
|
] });
|
|
1134
1489
|
}
|
|
1135
1490
|
function ToolActivityRow({ tool }) {
|
|
1136
|
-
return /* @__PURE__ */ (0,
|
|
1137
|
-
/* @__PURE__ */ (0,
|
|
1138
|
-
/* @__PURE__ */ (0,
|
|
1491
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Badge, { variant: badgeVariant(tool.status), title: tool.detail ?? tool.name, children: [
|
|
1492
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ToolIcon, { status: tool.status }),
|
|
1493
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "truncate", children: tool.label })
|
|
1139
1494
|
] });
|
|
1140
1495
|
}
|
|
1141
1496
|
function ToolIcon({ status }) {
|
|
1142
1497
|
switch (status) {
|
|
1143
1498
|
case "running":
|
|
1144
|
-
return /* @__PURE__ */ (0,
|
|
1499
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react4.Loader2, { className: "size-3 animate-spin", "aria-hidden": "true" });
|
|
1145
1500
|
case "done":
|
|
1146
|
-
return /* @__PURE__ */ (0,
|
|
1501
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react4.Check, { className: "size-3", "aria-hidden": "true" });
|
|
1147
1502
|
case "denied":
|
|
1148
|
-
return /* @__PURE__ */ (0,
|
|
1503
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react4.ShieldAlert, { className: "size-3", "aria-hidden": "true" });
|
|
1149
1504
|
default:
|
|
1150
|
-
return /* @__PURE__ */ (0,
|
|
1505
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react4.X, { className: "size-3", "aria-hidden": "true" });
|
|
1151
1506
|
}
|
|
1152
1507
|
}
|
|
1153
1508
|
function badgeVariant(status) {
|
|
@@ -1158,47 +1513,56 @@ function badgeVariant(status) {
|
|
|
1158
1513
|
}
|
|
1159
1514
|
|
|
1160
1515
|
// src/components/chat/MessageBubble.tsx
|
|
1161
|
-
var
|
|
1162
|
-
|
|
1516
|
+
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
1517
|
+
var MessageBubble = (0, import_react8.memo)(function MessageBubble2({
|
|
1163
1518
|
message,
|
|
1164
1519
|
assistantName,
|
|
1165
1520
|
avatarUrl,
|
|
1166
1521
|
showToolActivity,
|
|
1167
|
-
renderTool
|
|
1522
|
+
renderTool,
|
|
1523
|
+
onChoose,
|
|
1524
|
+
onRequestInput,
|
|
1525
|
+
active,
|
|
1526
|
+
busy
|
|
1168
1527
|
}) {
|
|
1169
1528
|
if (message.role === "user") {
|
|
1170
|
-
return /* @__PURE__ */ (0,
|
|
1171
|
-
message.attachments?.length ? /* @__PURE__ */ (0,
|
|
1529
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "frankie-user-message flex flex-col items-end gap-1", children: [
|
|
1530
|
+
message.attachments?.length ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "flex max-w-[85%] flex-wrap justify-end gap-1", children: message.attachments.map((file, index) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
1172
1531
|
"span",
|
|
1173
1532
|
{
|
|
1174
1533
|
className: "inline-flex max-w-[12rem] items-center gap-1 rounded-[var(--frankie-radius)] border border-border bg-muted px-2 py-1 text-xs",
|
|
1175
1534
|
children: [
|
|
1176
|
-
/* @__PURE__ */ (0,
|
|
1177
|
-
/* @__PURE__ */ (0,
|
|
1535
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react5.Paperclip, { className: "size-3 shrink-0 opacity-70" }),
|
|
1536
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "truncate", children: file.name })
|
|
1178
1537
|
]
|
|
1179
1538
|
},
|
|
1180
1539
|
`${file.name}-${index}`
|
|
1181
1540
|
)) }) : null,
|
|
1182
|
-
message.text ? /* @__PURE__ */ (0,
|
|
1183
|
-
/* @__PURE__ */ (0,
|
|
1184
|
-
/* @__PURE__ */ (0,
|
|
1541
|
+
message.text ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex max-w-[85%] items-end gap-1", children: [
|
|
1542
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "frankie-user-bubble rounded-[var(--frankie-radius)] bg-primary px-3 py-2 text-sm text-primary-foreground", children: message.text }),
|
|
1543
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(MessageCopyButton, { text: message.text })
|
|
1185
1544
|
] }) : null
|
|
1186
1545
|
] });
|
|
1187
1546
|
}
|
|
1188
|
-
const
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1547
|
+
const { body, options } = extractChoices(message.text);
|
|
1548
|
+
const showChoices = Boolean(active && onChoose && options && message.status === "done");
|
|
1549
|
+
const toolsRunning = message.tools.some((tool) => tool.status === "running");
|
|
1550
|
+
const waiting = message.status === "streaming" && body.length === 0 && !toolsRunning;
|
|
1551
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "frankie-assistant-message flex gap-2", children: [
|
|
1552
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "frankie-assistant-actions mt-0.5 flex shrink-0 flex-col items-center gap-1", children: [
|
|
1553
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Avatar, { ...avatarUrl ? { src: avatarUrl } : {}, name: assistantName }),
|
|
1554
|
+
body ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(MessageCopyButton, { text: body }) : null
|
|
1193
1555
|
] }),
|
|
1194
|
-
/* @__PURE__ */ (0,
|
|
1195
|
-
message.status === "error" ? /* @__PURE__ */ (0,
|
|
1196
|
-
|
|
1556
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "frankie-assistant-content min-w-0 max-w-[85%]", children: [
|
|
1557
|
+
message.status === "error" ? /* @__PURE__ */ (0, import_jsx_runtime13.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 }) : body ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Markdown, { text: body, className: "text-sm" }) : null,
|
|
1558
|
+
showChoices ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Choices, { options: options ?? [], onPick: onChoose, ...onRequestInput ? { onSomethingElse: onRequestInput } : {}, disabled: busy }) : null,
|
|
1559
|
+
showToolActivity ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ToolActivityList, { tools: message.tools, ...renderTool ? { renderTool } : {} }) : null,
|
|
1560
|
+
waiting ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Thinking, {}) : null
|
|
1197
1561
|
] })
|
|
1198
1562
|
] });
|
|
1199
|
-
}
|
|
1563
|
+
});
|
|
1200
1564
|
function MessageCopyButton({ text }) {
|
|
1201
|
-
const [copied, setCopied] = (0,
|
|
1565
|
+
const [copied, setCopied] = (0, import_react8.useState)(false);
|
|
1202
1566
|
const copy = async () => {
|
|
1203
1567
|
try {
|
|
1204
1568
|
await navigator.clipboard.writeText(text);
|
|
@@ -1207,12 +1571,12 @@ function MessageCopyButton({ text }) {
|
|
|
1207
1571
|
} catch {
|
|
1208
1572
|
}
|
|
1209
1573
|
};
|
|
1210
|
-
return /* @__PURE__ */ (0,
|
|
1574
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.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_runtime13.jsx)(import_lucide_react5.Check, { className: "size-3.5", "aria-hidden": "true" }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react5.Copy, { className: "size-3.5", "aria-hidden": "true" }) });
|
|
1211
1575
|
}
|
|
1212
1576
|
function TypingDots() {
|
|
1213
|
-
return /* @__PURE__ */ (0,
|
|
1214
|
-
/* @__PURE__ */ (0,
|
|
1215
|
-
[0, 1, 2].map((index) => /* @__PURE__ */ (0,
|
|
1577
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { className: "frankie-thinking inline-flex items-center gap-1 py-2", role: "status", "aria-label": "Thinking", children: [
|
|
1578
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "frankie-thinking-label", children: "Thinking" }),
|
|
1579
|
+
[0, 1, 2].map((index) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1216
1580
|
"span",
|
|
1217
1581
|
{
|
|
1218
1582
|
className: cn("size-1.5 animate-pulse rounded-full bg-muted-foreground"),
|
|
@@ -1222,16 +1586,23 @@ function TypingDots() {
|
|
|
1222
1586
|
))
|
|
1223
1587
|
] });
|
|
1224
1588
|
}
|
|
1589
|
+
function Thinking() {
|
|
1590
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "frankie-skeleton", role: "status", "aria-label": "Thinking", children: [
|
|
1591
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "frankie-skeleton-line", style: { width: "62%" } }),
|
|
1592
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "frankie-skeleton-line", style: { width: "92%" } }),
|
|
1593
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "frankie-skeleton-line", style: { width: "48%" } })
|
|
1594
|
+
] });
|
|
1595
|
+
}
|
|
1225
1596
|
|
|
1226
1597
|
// src/components/chat/SuggestedPrompts.tsx
|
|
1227
|
-
var
|
|
1598
|
+
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
1228
1599
|
function SuggestedPrompts({
|
|
1229
1600
|
prompts,
|
|
1230
1601
|
onPick,
|
|
1231
1602
|
disabled
|
|
1232
1603
|
}) {
|
|
1233
1604
|
if (prompts.length === 0) return null;
|
|
1234
|
-
return /* @__PURE__ */ (0,
|
|
1605
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "flex flex-wrap gap-2", children: prompts.map((prompt) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
1235
1606
|
Button,
|
|
1236
1607
|
{
|
|
1237
1608
|
type: "button",
|
|
@@ -1246,7 +1617,7 @@ function SuggestedPrompts({
|
|
|
1246
1617
|
}
|
|
1247
1618
|
|
|
1248
1619
|
// src/components/chat/ChatPanel.tsx
|
|
1249
|
-
var
|
|
1620
|
+
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
1250
1621
|
function ChatPanel({
|
|
1251
1622
|
className,
|
|
1252
1623
|
header,
|
|
@@ -1262,12 +1633,14 @@ function ChatPanel({
|
|
|
1262
1633
|
const { config, configError, loading } = useFrankie();
|
|
1263
1634
|
const theme = useFrankieTheme();
|
|
1264
1635
|
const { messages, busy, restoring, error, send, stop, reset } = useFrankieChat();
|
|
1265
|
-
const scrollRef = (0,
|
|
1266
|
-
const atBottomRef = (0,
|
|
1636
|
+
const scrollRef = (0, import_react9.useRef)(null);
|
|
1637
|
+
const atBottomRef = (0, import_react9.useRef)(true);
|
|
1267
1638
|
const features = config?.features;
|
|
1268
1639
|
const pickerModels = features?.modelPicker.enabled ? features.modelPicker.models : [];
|
|
1269
|
-
const [model, setModel] = (0,
|
|
1270
|
-
(0,
|
|
1640
|
+
const [model, setModel] = (0, import_react9.useState)(void 0);
|
|
1641
|
+
const composerFocus = (0, import_react9.useRef)(null);
|
|
1642
|
+
const focusComposer = () => composerFocus.current?.();
|
|
1643
|
+
(0, import_react9.useEffect)(() => {
|
|
1271
1644
|
const node = scrollRef.current;
|
|
1272
1645
|
if (!node) return;
|
|
1273
1646
|
const onScroll = () => {
|
|
@@ -1276,14 +1649,14 @@ function ChatPanel({
|
|
|
1276
1649
|
node.addEventListener("scroll", onScroll, { passive: true });
|
|
1277
1650
|
return () => node.removeEventListener("scroll", onScroll);
|
|
1278
1651
|
}, []);
|
|
1279
|
-
(0,
|
|
1652
|
+
(0, import_react9.useEffect)(() => {
|
|
1280
1653
|
const node = scrollRef.current;
|
|
1281
1654
|
if (!node || !atBottomRef.current) return;
|
|
1282
1655
|
node.scrollTop = node.scrollHeight;
|
|
1283
1656
|
}, [messages]);
|
|
1284
1657
|
const name = config?.branding.name ?? "Assistant";
|
|
1285
1658
|
const mode = resolveMode(theme?.mode);
|
|
1286
|
-
return /* @__PURE__ */ (0,
|
|
1659
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
|
|
1287
1660
|
Card,
|
|
1288
1661
|
{
|
|
1289
1662
|
className: cn(
|
|
@@ -1296,44 +1669,49 @@ function ChatPanel({
|
|
|
1296
1669
|
style: themeToCssVars(theme),
|
|
1297
1670
|
"data-frankie-mode": mode,
|
|
1298
1671
|
children: [
|
|
1299
|
-
header === false ? null : header !== void 0 ? header : /* @__PURE__ */ (0,
|
|
1300
|
-
/* @__PURE__ */ (0,
|
|
1301
|
-
/* @__PURE__ */ (0,
|
|
1302
|
-
/* @__PURE__ */ (0,
|
|
1303
|
-
config?.branding.tagline ? /* @__PURE__ */ (0,
|
|
1672
|
+
header === false ? null : header !== void 0 ? header : /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(CardHeader, { children: [
|
|
1673
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Avatar, { ...config?.branding.avatarUrl ? { src: config.branding.avatarUrl } : {}, name }),
|
|
1674
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "min-w-0 flex-1", children: [
|
|
1675
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(CardTitle, { children: name }),
|
|
1676
|
+
config?.branding.tagline ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(CardDescription, { children: config.branding.tagline }) : null
|
|
1304
1677
|
] }),
|
|
1305
|
-
/* @__PURE__ */ (0,
|
|
1306
|
-
onClose ? /* @__PURE__ */ (0,
|
|
1678
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Button, { size: "icon", variant: "ghost", onClick: reset, "aria-label": "Start a new conversation", title: "New conversation", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react6.RotateCcw, {}) }),
|
|
1679
|
+
onClose ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Button, { size: "icon", variant: "ghost", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react6.X, {}) }) : null
|
|
1307
1680
|
] }),
|
|
1308
|
-
/* @__PURE__ */ (0,
|
|
1309
|
-
/* @__PURE__ */ (0,
|
|
1310
|
-
/* @__PURE__ */ (0,
|
|
1311
|
-
/* @__PURE__ */ (0,
|
|
1312
|
-
/* @__PURE__ */ (0,
|
|
1313
|
-
] }) : loading ? /* @__PURE__ */ (0,
|
|
1314
|
-
/* @__PURE__ */ (0,
|
|
1315
|
-
/* @__PURE__ */ (0,
|
|
1316
|
-
] }) : /* @__PURE__ */ (0,
|
|
1317
|
-
(message) => renderMessage ? /* @__PURE__ */ (0,
|
|
1681
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(AgentContext, {}),
|
|
1682
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(CardContent, { className: "frankie-conversation-content", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ScrollArea, { ref: scrollRef, className: "frankie-transcript flex h-full flex-col gap-4 p-4", "aria-live": "polite", children: configError ? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "frankie-config-error", role: "alert", children: [
|
|
1683
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("strong", { children: "Frankie couldn\u2019t start" }),
|
|
1684
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { children: configError }),
|
|
1685
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { children: "Refresh this page after checking the workspace\u2019s allowed origin and sign-in settings." })
|
|
1686
|
+
] }) : loading ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: "text-sm text-muted-foreground", children: "Starting Frankie\u2026" }) : messages.length === 0 ? emptyState ?? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex flex-col gap-4", children: [
|
|
1687
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: "text-sm text-muted-foreground", children: config?.greeting }),
|
|
1688
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(SuggestedPrompts, { prompts: config?.suggestedPrompts ?? [], onPick: send, disabled: busy })
|
|
1689
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "frankie-message-list flex flex-col gap-4", children: messages.map(
|
|
1690
|
+
(message, index) => renderMessage ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { children: renderMessage(message) }, message.id) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
1318
1691
|
MessageBubble,
|
|
1319
1692
|
{
|
|
1320
1693
|
message,
|
|
1321
1694
|
assistantName: name,
|
|
1322
1695
|
...config?.branding.avatarUrl ? { avatarUrl: config.branding.avatarUrl } : {},
|
|
1323
1696
|
showToolActivity: config?.showToolActivity ?? true,
|
|
1324
|
-
...renderTool ? { renderTool } : {}
|
|
1697
|
+
...renderTool ? { renderTool } : {},
|
|
1698
|
+
onChoose: send,
|
|
1699
|
+
onRequestInput: focusComposer,
|
|
1700
|
+
active: index === messages.length - 1,
|
|
1701
|
+
busy
|
|
1325
1702
|
},
|
|
1326
1703
|
message.id
|
|
1327
1704
|
)
|
|
1328
1705
|
) }) }) }),
|
|
1329
|
-
/* @__PURE__ */ (0,
|
|
1330
|
-
error ? /* @__PURE__ */ (0,
|
|
1331
|
-
/* @__PURE__ */ (0,
|
|
1706
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(CardFooter, { className: "frankie-composer-footer flex flex-col gap-2", children: [
|
|
1707
|
+
error ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: "w-full text-xs text-red-600 dark:text-red-400", children: error }) : null,
|
|
1708
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "w-full", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
1332
1709
|
Composer,
|
|
1333
1710
|
{
|
|
1334
1711
|
onSend: send,
|
|
1335
1712
|
onStop: stop,
|
|
1336
1713
|
busy,
|
|
1714
|
+
focusHandleRef: composerFocus,
|
|
1337
1715
|
disabled: loading || restoring || Boolean(configError),
|
|
1338
1716
|
...placeholder ? { placeholder } : {},
|
|
1339
1717
|
...autoFocus ? { autoFocus } : {},
|
|
@@ -1349,7 +1727,7 @@ function ChatPanel({
|
|
|
1349
1727
|
}
|
|
1350
1728
|
|
|
1351
1729
|
// src/components/chat/FrankieWidget.tsx
|
|
1352
|
-
var
|
|
1730
|
+
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
1353
1731
|
function FrankieWidget({
|
|
1354
1732
|
position = "bottom-right",
|
|
1355
1733
|
offset = 24,
|
|
@@ -1360,15 +1738,17 @@ function FrankieWidget({
|
|
|
1360
1738
|
renderLauncher,
|
|
1361
1739
|
...panelProps
|
|
1362
1740
|
}) {
|
|
1363
|
-
const { config } = useFrankie();
|
|
1741
|
+
const { config, injectStyles } = useFrankie();
|
|
1364
1742
|
const theme = useFrankieTheme();
|
|
1365
|
-
const
|
|
1743
|
+
const rootRef = (0, import_react10.useRef)(null);
|
|
1744
|
+
const stylesReady = useStylesReady(rootRef, void 0, injectStyles);
|
|
1745
|
+
const [uncontrolledOpen, setUncontrolledOpen] = (0, import_react10.useState)(defaultOpen);
|
|
1366
1746
|
const open = controlledOpen ?? uncontrolledOpen;
|
|
1367
1747
|
const setOpen = (next) => {
|
|
1368
1748
|
if (controlledOpen === void 0) setUncontrolledOpen(next);
|
|
1369
1749
|
onOpenChange?.(next);
|
|
1370
1750
|
};
|
|
1371
|
-
(0,
|
|
1751
|
+
(0, import_react10.useEffect)(() => {
|
|
1372
1752
|
if (!open) return;
|
|
1373
1753
|
const onKeyDown = (event) => {
|
|
1374
1754
|
if (event.key === "Escape") setOpen(false);
|
|
@@ -1379,15 +1759,17 @@ function FrankieWidget({
|
|
|
1379
1759
|
const label = config?.branding.launcherLabel ?? "Need a hand?";
|
|
1380
1760
|
const mode = resolveMode(theme?.mode);
|
|
1381
1761
|
const side = position === "bottom-right" ? { right: offset } : { left: offset };
|
|
1382
|
-
return /* @__PURE__ */ (0,
|
|
1762
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1383
1763
|
"div",
|
|
1384
1764
|
{
|
|
1765
|
+
ref: rootRef,
|
|
1385
1766
|
className: cn("frankie-root frankie-widget", mode === "dark" && "dark"),
|
|
1386
|
-
style: { ...themeToCssVars(theme), position: "fixed", bottom: offset, ...side, zIndex, pointerEvents: "none" },
|
|
1767
|
+
style: { ...themeToCssVars(theme), position: "fixed", bottom: offset, ...side, zIndex, pointerEvents: "none", ...stylesReady ? null : { opacity: 0 } },
|
|
1387
1768
|
"data-frankie-mode": mode,
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
/* @__PURE__ */ (0,
|
|
1769
|
+
"data-frankie-styles": stylesReady ? "ready" : "pending",
|
|
1770
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: cn("flex flex-col gap-3", position === "bottom-left" ? "items-start" : "items-end"), children: [
|
|
1771
|
+
open ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { pointerEvents: "auto", maxHeight: "calc(100dvh - 100px)", overflow: "hidden" }, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ChatPanel, { ...panelProps, className: cn("frankie-widget-panel", panelProps.className), onClose: () => setOpen(false), autoFocus: true }) }) : null,
|
|
1772
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { pointerEvents: "auto" }, children: renderLauncher ? renderLauncher({ open, toggle: () => setOpen(!open), label }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1391
1773
|
Button,
|
|
1392
1774
|
{
|
|
1393
1775
|
size: "icon",
|
|
@@ -1396,7 +1778,7 @@ function FrankieWidget({
|
|
|
1396
1778
|
"aria-expanded": open,
|
|
1397
1779
|
"aria-label": open ? "Close the assistant" : label,
|
|
1398
1780
|
title: label,
|
|
1399
|
-
children: open ? /* @__PURE__ */ (0,
|
|
1781
|
+
children: open ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react7.X, { className: "size-5" }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react7.MessageCircle, { className: "size-5" })
|
|
1400
1782
|
}
|
|
1401
1783
|
) })
|
|
1402
1784
|
] })
|
|
@@ -1405,39 +1787,45 @@ function FrankieWidget({
|
|
|
1405
1787
|
}
|
|
1406
1788
|
|
|
1407
1789
|
// src/components/chat/FrankieInline.tsx
|
|
1408
|
-
var
|
|
1790
|
+
var import_react11 = require("react");
|
|
1791
|
+
var import_jsx_runtime17 = require("react/jsx-runtime");
|
|
1409
1792
|
function FrankieInline({ className, style, ...panelProps }) {
|
|
1793
|
+
const { injectStyles } = useFrankie();
|
|
1410
1794
|
const theme = useFrankieTheme();
|
|
1411
1795
|
const mode = resolveMode(theme?.mode);
|
|
1412
|
-
|
|
1796
|
+
const rootRef = (0, import_react11.useRef)(null);
|
|
1797
|
+
const stylesReady = useStylesReady(rootRef, void 0, injectStyles);
|
|
1798
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
1413
1799
|
"div",
|
|
1414
1800
|
{
|
|
1801
|
+
ref: rootRef,
|
|
1415
1802
|
className: cn("frankie-root frankie-inline", mode === "dark" && "dark", className),
|
|
1416
|
-
style: { ...themeToCssVars(theme), display: "flex", height: "100%", width: "100%", ...style },
|
|
1803
|
+
style: { ...themeToCssVars(theme), display: "flex", height: "100%", width: "100%", ...style, ...stylesReady ? null : { opacity: 0 } },
|
|
1417
1804
|
"data-frankie-mode": mode,
|
|
1418
|
-
|
|
1805
|
+
"data-frankie-styles": stylesReady ? "ready" : "pending",
|
|
1806
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(ChatPanel, { ...panelProps, fill: true })
|
|
1419
1807
|
}
|
|
1420
1808
|
);
|
|
1421
1809
|
}
|
|
1422
1810
|
|
|
1423
1811
|
// src/components/chat/FrankieChatPage.tsx
|
|
1424
|
-
var
|
|
1425
|
-
var
|
|
1426
|
-
var
|
|
1812
|
+
var import_react12 = require("react");
|
|
1813
|
+
var import_lucide_react8 = require("lucide-react");
|
|
1814
|
+
var import_jsx_runtime18 = require("react/jsx-runtime");
|
|
1427
1815
|
function PageEmptyState() {
|
|
1428
1816
|
const { config } = useFrankie();
|
|
1429
1817
|
const { send, busy, restoring } = useFrankieChat();
|
|
1430
1818
|
const name = config?.branding.name ?? "Frankie";
|
|
1431
|
-
return /* @__PURE__ */ (0,
|
|
1432
|
-
/* @__PURE__ */ (0,
|
|
1433
|
-
/* @__PURE__ */ (0,
|
|
1434
|
-
/* @__PURE__ */ (0,
|
|
1435
|
-
/* @__PURE__ */ (0,
|
|
1436
|
-
/* @__PURE__ */ (0,
|
|
1819
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("section", { className: "frankie-page-empty", "aria-label": "Start a conversation", children: [
|
|
1820
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "frankie-page-avatar", "aria-hidden": true, children: name.trim().slice(0, 1).toUpperCase() }),
|
|
1821
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [
|
|
1822
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "frankie-page-kicker", children: "Your product assistant" }),
|
|
1823
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h1", { children: "How can I help?" }),
|
|
1824
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { children: config?.greeting ?? "Ask a question or choose a task to get started." })
|
|
1437
1825
|
] }),
|
|
1438
|
-
/* @__PURE__ */ (0,
|
|
1439
|
-
/* @__PURE__ */ (0,
|
|
1440
|
-
/* @__PURE__ */ (0,
|
|
1826
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "frankie-page-prompts", children: config?.suggestedPrompts?.slice(0, 3).map((prompt) => /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("button", { type: "button", disabled: busy || restoring, onClick: () => void send(prompt), children: [
|
|
1827
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("strong", { children: prompt }),
|
|
1828
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("b", { "aria-hidden": true, children: "\u2197" })
|
|
1441
1829
|
] }, prompt)) })
|
|
1442
1830
|
] });
|
|
1443
1831
|
}
|
|
@@ -1445,22 +1833,22 @@ function PageChrome({ showHistory, navigation, currentId, onSelect, onNew, ...pa
|
|
|
1445
1833
|
const { client, config } = useFrankie();
|
|
1446
1834
|
const { busy, messages } = useFrankieChat();
|
|
1447
1835
|
const theme = useFrankieTheme();
|
|
1448
|
-
const [conversations, setConversations] = (0,
|
|
1449
|
-
const [historyError, setHistoryError] = (0,
|
|
1450
|
-
const [historyLoading, setHistoryLoading] = (0,
|
|
1451
|
-
const [drawerOpen, setDrawerOpen] = (0,
|
|
1452
|
-
const [collapsed, setCollapsed] = (0,
|
|
1836
|
+
const [conversations, setConversations] = (0, import_react12.useState)([]);
|
|
1837
|
+
const [historyError, setHistoryError] = (0, import_react12.useState)("");
|
|
1838
|
+
const [historyLoading, setHistoryLoading] = (0, import_react12.useState)(false);
|
|
1839
|
+
const [drawerOpen, setDrawerOpen] = (0, import_react12.useState)(false);
|
|
1840
|
+
const [collapsed, setCollapsed] = (0, import_react12.useState)(() => {
|
|
1453
1841
|
try {
|
|
1454
1842
|
return window.localStorage.getItem("frankie.history-collapsed") === "1";
|
|
1455
1843
|
} catch {
|
|
1456
1844
|
return false;
|
|
1457
1845
|
}
|
|
1458
1846
|
});
|
|
1459
|
-
const [query, setQuery] = (0,
|
|
1460
|
-
const dialog = (0,
|
|
1461
|
-
const menu = (0,
|
|
1462
|
-
const requestVersion = (0,
|
|
1463
|
-
const refresh = (0,
|
|
1847
|
+
const [query, setQuery] = (0, import_react12.useState)("");
|
|
1848
|
+
const dialog = (0, import_react12.useRef)(null);
|
|
1849
|
+
const menu = (0, import_react12.useRef)(null);
|
|
1850
|
+
const requestVersion = (0, import_react12.useRef)(0);
|
|
1851
|
+
const refresh = (0, import_react12.useCallback)(async () => {
|
|
1464
1852
|
if (!showHistory || !config || config.allowAnonymous) {
|
|
1465
1853
|
setHistoryError("");
|
|
1466
1854
|
return;
|
|
@@ -1481,19 +1869,19 @@ function PageChrome({ showHistory, navigation, currentId, onSelect, onNew, ...pa
|
|
|
1481
1869
|
if (version === requestVersion.current) setHistoryLoading(false);
|
|
1482
1870
|
}
|
|
1483
1871
|
}, [client, showHistory, config?.allowAnonymous]);
|
|
1484
|
-
(0,
|
|
1872
|
+
(0, import_react12.useEffect)(() => {
|
|
1485
1873
|
if (!busy) void refresh();
|
|
1486
1874
|
return () => {
|
|
1487
1875
|
requestVersion.current++;
|
|
1488
1876
|
};
|
|
1489
1877
|
}, [refresh, busy]);
|
|
1490
|
-
(0,
|
|
1878
|
+
(0, import_react12.useEffect)(() => {
|
|
1491
1879
|
try {
|
|
1492
1880
|
window.localStorage.setItem("frankie.history-collapsed", collapsed ? "1" : "0");
|
|
1493
1881
|
} catch {
|
|
1494
1882
|
}
|
|
1495
1883
|
}, [collapsed]);
|
|
1496
|
-
(0,
|
|
1884
|
+
(0, import_react12.useEffect)(() => {
|
|
1497
1885
|
const node = dialog.current;
|
|
1498
1886
|
if (drawerOpen) {
|
|
1499
1887
|
node?.showModal?.();
|
|
@@ -1511,79 +1899,81 @@ function PageChrome({ showHistory, navigation, currentId, onSelect, onNew, ...pa
|
|
|
1511
1899
|
close();
|
|
1512
1900
|
}
|
|
1513
1901
|
};
|
|
1514
|
-
const history = /* @__PURE__ */ (0,
|
|
1515
|
-
/* @__PURE__ */ (0,
|
|
1516
|
-
/* @__PURE__ */ (0,
|
|
1517
|
-
/* @__PURE__ */ (0,
|
|
1902
|
+
const history = /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
|
|
1903
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "frankie-history-heading", children: [
|
|
1904
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("strong", { children: "Conversations" }),
|
|
1905
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("button", { type: "button", className: "frankie-mobile-only", "aria-label": "Close conversations", onClick: close, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react8.X, { size: 18 }) })
|
|
1518
1906
|
] }),
|
|
1519
|
-
/* @__PURE__ */ (0,
|
|
1907
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("button", { type: "button", className: "frankie-new-chat", disabled: busy, onClick: () => {
|
|
1520
1908
|
onNew();
|
|
1521
1909
|
close();
|
|
1522
1910
|
}, children: [
|
|
1523
|
-
/* @__PURE__ */ (0,
|
|
1911
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react8.Plus, { size: 17 }),
|
|
1524
1912
|
"New chat"
|
|
1525
1913
|
] }),
|
|
1526
|
-
/* @__PURE__ */ (0,
|
|
1527
|
-
/* @__PURE__ */ (0,
|
|
1528
|
-
/* @__PURE__ */ (0,
|
|
1914
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("label", { className: "frankie-history-search", children: [
|
|
1915
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { children: "Search conversations" }),
|
|
1916
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("input", { type: "search", placeholder: "Search chats\u2026", value: query, onChange: (event) => setQuery(event.target.value) })
|
|
1529
1917
|
] }),
|
|
1530
|
-
/* @__PURE__ */ (0,
|
|
1531
|
-
/* @__PURE__ */ (0,
|
|
1532
|
-
/* @__PURE__ */ (0,
|
|
1918
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("nav", { className: "frankie-history-list", "aria-label": "Conversations", children: conversations.filter((item) => item.title.toLowerCase().includes(query.toLowerCase())).map((item) => /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("button", { type: "button", "aria-current": item.id === currentId ? "page" : void 0, disabled: busy, onClick: () => select(item.id), children: [
|
|
1919
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { children: item.title }),
|
|
1920
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("small", { children: new Date(item.updatedAt).toLocaleDateString() })
|
|
1533
1921
|
] }, item.id)) }),
|
|
1534
|
-
historyLoading ? /* @__PURE__ */ (0,
|
|
1922
|
+
historyLoading ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { role: "status", children: "Loading history\u2026" }) : historyError ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("p", { role: "status", children: [
|
|
1535
1923
|
historyError,
|
|
1536
|
-
/* @__PURE__ */ (0,
|
|
1537
|
-
] }) : !conversations.length ? /* @__PURE__ */ (0,
|
|
1538
|
-
navigation ? /* @__PURE__ */ (0,
|
|
1924
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("button", { type: "button", onClick: () => void refresh(), children: "Try again" })
|
|
1925
|
+
] }) : !conversations.length ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { children: config?.allowAnonymous ? "Sign in to sync conversations across devices." : "Your conversations will appear here." }) : null,
|
|
1926
|
+
navigation ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "frankie-host-navigation", children: navigation }) : null
|
|
1539
1927
|
] });
|
|
1540
|
-
return /* @__PURE__ */ (0,
|
|
1541
|
-
showHistory ? /* @__PURE__ */ (0,
|
|
1542
|
-
/* @__PURE__ */ (0,
|
|
1928
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "frankie-page-shell" + (resolveMode(theme?.mode) === "dark" ? " dark" : ""), style: themeToCssVars(theme), children: [
|
|
1929
|
+
showHistory ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("aside", { className: "frankie-history-desktop" + (collapsed ? " frankie-history-collapsed" : ""), children: [
|
|
1930
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("button", { type: "button", className: "frankie-history-toggle", "aria-label": collapsed ? "Expand conversations" : "Collapse conversations", "aria-expanded": !collapsed, onClick: () => setCollapsed((value) => !value), children: collapsed ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react8.ChevronsRight, { size: 16 }) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react8.ChevronsLeft, { size: 16 }) }),
|
|
1543
1931
|
collapsed ? null : history
|
|
1544
1932
|
] }) : null,
|
|
1545
|
-
/* @__PURE__ */ (0,
|
|
1546
|
-
/* @__PURE__ */ (0,
|
|
1547
|
-
/* @__PURE__ */ (0,
|
|
1548
|
-
showHistory ? /* @__PURE__ */ (0,
|
|
1549
|
-
/* @__PURE__ */ (0,
|
|
1933
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "frankie-page-main", children: [
|
|
1934
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("header", { className: "frankie-page-header", children: [
|
|
1935
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [
|
|
1936
|
+
showHistory ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("button", { type: "button", className: "frankie-mobile-only", ref: menu, "aria-label": "Open conversations", "aria-haspopup": "dialog", "aria-expanded": drawerOpen, onClick: () => setDrawerOpen(true), children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react8.Menu, { size: 20 }) }) : null,
|
|
1937
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("strong", { className: "frankie-thread-title", children: conversations.find((item) => item.id === currentId)?.title ?? messages.find((message) => message.role === "user")?.text ?? "New chat" })
|
|
1550
1938
|
] }),
|
|
1551
|
-
/* @__PURE__ */ (0,
|
|
1552
|
-
/* @__PURE__ */ (0,
|
|
1553
|
-
/* @__PURE__ */ (0,
|
|
1554
|
-
/* @__PURE__ */ (0,
|
|
1939
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [
|
|
1940
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("button", { type: "button", disabled: busy, onClick: onNew, "aria-label": "Start a new conversation", children: [
|
|
1941
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react8.Plus, { size: 16 }),
|
|
1942
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { children: "New chat" })
|
|
1555
1943
|
] }),
|
|
1556
|
-
/* @__PURE__ */ (0,
|
|
1557
|
-
/* @__PURE__ */ (0,
|
|
1558
|
-
/* @__PURE__ */ (0,
|
|
1559
|
-
/* @__PURE__ */ (0,
|
|
1560
|
-
/* @__PURE__ */ (0,
|
|
1561
|
-
/* @__PURE__ */ (0,
|
|
1562
|
-
/* @__PURE__ */ (0,
|
|
1944
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("details", { className: "frankie-chat-settings", children: [
|
|
1945
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("summary", { "aria-label": "Chat settings", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react8.Settings, { size: 19 }) }),
|
|
1946
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [
|
|
1947
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("strong", { children: "Chat settings" }),
|
|
1948
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { children: "History syncs automatically for signed-in users." }),
|
|
1949
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("button", { type: "button", onClick: () => void refresh(), disabled: historyLoading || !showHistory, children: [
|
|
1950
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react8.History, { size: 16 }),
|
|
1563
1951
|
"Refresh history"
|
|
1564
1952
|
] }),
|
|
1565
|
-
/* @__PURE__ */ (0,
|
|
1953
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("small", { children: "Skills are checked at the start of each message." })
|
|
1566
1954
|
] })
|
|
1567
1955
|
] })
|
|
1568
1956
|
] })
|
|
1569
1957
|
] }),
|
|
1570
|
-
/* @__PURE__ */ (0,
|
|
1958
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "frankie-page-canvas", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(FrankieInline, { ...panel, header: panel.header ?? false, emptyState: panel.emptyState ?? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(PageEmptyState, {}) }) })
|
|
1571
1959
|
] }),
|
|
1572
|
-
showHistory ? /* @__PURE__ */ (0,
|
|
1960
|
+
showHistory ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("dialog", { ref: dialog, className: "frankie-history-dialog frankie-root", "aria-label": "Chat conversations", style: themeToCssVars(theme), onCancel: close, onClick: (event) => {
|
|
1573
1961
|
if (event.target === event.currentTarget) close();
|
|
1574
|
-
}, children: /* @__PURE__ */ (0,
|
|
1962
|
+
}, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("aside", { children: history }) }) : null
|
|
1575
1963
|
] });
|
|
1576
1964
|
}
|
|
1577
1965
|
function FrankieChatPage({ className, style, header, footer, emptyState, renderMessage, renderTool, placeholder, autoFocus, showHistory = true, navigation, ...provider }) {
|
|
1578
|
-
const [id, setId] = (0,
|
|
1966
|
+
const [id, setId] = (0, import_react12.useState)(() => provider.conversationId ?? crypto.randomUUID());
|
|
1579
1967
|
const onNew = () => setId(crypto.randomUUID());
|
|
1580
|
-
|
|
1968
|
+
const pageRef = (0, import_react12.useRef)(null);
|
|
1969
|
+
const stylesReady = useStylesReady(pageRef, void 0, provider.injectStyles);
|
|
1970
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("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__ */ (0, import_jsx_runtime18.jsx)(FrankieProvider, { ...provider, conversationId: id, storageNamespace: (provider.storageNamespace ?? "frankie." + provider.publishableKey) + "." + id, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(PageChrome, { showHistory, navigation, currentId: id, onSelect: setId, onNew, header, footer, emptyState, renderMessage, renderTool, placeholder, autoFocus }) }) });
|
|
1581
1971
|
}
|
|
1582
1972
|
|
|
1583
1973
|
// src/components/ui/separator.tsx
|
|
1584
|
-
var
|
|
1974
|
+
var import_jsx_runtime19 = require("react/jsx-runtime");
|
|
1585
1975
|
function Separator({ className }) {
|
|
1586
|
-
return /* @__PURE__ */ (0,
|
|
1976
|
+
return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { role: "separator", className: cn("h-px w-full bg-border", className) });
|
|
1587
1977
|
}
|
|
1588
1978
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1589
1979
|
0 && (module.exports = {
|
|
@@ -1597,6 +1987,7 @@ function Separator({ className }) {
|
|
|
1597
1987
|
CardHeader,
|
|
1598
1988
|
CardTitle,
|
|
1599
1989
|
ChatPanel,
|
|
1990
|
+
Choices,
|
|
1600
1991
|
Composer,
|
|
1601
1992
|
FRANKIE_CLOUD_URL,
|
|
1602
1993
|
FrankieApiError,
|
|
@@ -1611,12 +2002,14 @@ function Separator({ className }) {
|
|
|
1611
2002
|
Separator,
|
|
1612
2003
|
SuggestedPrompts,
|
|
1613
2004
|
Textarea,
|
|
2005
|
+
Thinking,
|
|
1614
2006
|
ToolActivityList,
|
|
1615
2007
|
ToolActivityRow,
|
|
1616
2008
|
TypingDots,
|
|
1617
2009
|
applyEvent,
|
|
1618
2010
|
buttonVariants,
|
|
1619
2011
|
cn,
|
|
2012
|
+
extractChoices,
|
|
1620
2013
|
newAssistantMessage,
|
|
1621
2014
|
newUserMessage,
|
|
1622
2015
|
readEventStream,
|