@frankieai/react 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -354,6 +354,7 @@ function useFrankieChat() {
354
354
  const [restored, setRestored] = useState2(false);
355
355
  const abortRef = useRef(null);
356
356
  const mounted = useRef(true);
357
+ const messagesRef = useRef([]);
357
358
  useEffect2(() => {
358
359
  mounted.current = true;
359
360
  return () => {
@@ -369,13 +370,20 @@ function useFrankieChat() {
369
370
  try {
370
371
  const raw = window.localStorage.getItem(storageKey);
371
372
  const stored = raw ? JSON.parse(raw) : [];
372
- if (Array.isArray(stored) && stored.length && active) setMessages(stored);
373
+ if (Array.isArray(stored) && stored.length && active) {
374
+ messagesRef.current = stored;
375
+ setMessages(stored);
376
+ }
373
377
  } catch {
374
378
  }
375
379
  }
376
380
  try {
377
381
  const remote = await client.resumeConversation();
378
- if (active && remote?.messages.length) setMessages(remote.messages);
382
+ if (active && remote?.messages.length) {
383
+ const restoredMessages = remote.messages;
384
+ messagesRef.current = restoredMessages;
385
+ setMessages(restoredMessages);
386
+ }
379
387
  } catch {
380
388
  } finally {
381
389
  if (active) setRestored(true);
@@ -396,11 +404,11 @@ function useFrankieChat() {
396
404
  if (messages.length) void client.syncConversation(messages, conversationTitle(messages)).catch(() => void 0);
397
405
  }, [messages, busy, client, storageKey, restored]);
398
406
  const patchAssistant = useCallback((id, update) => {
399
- setMessages(
400
- (current) => current.map(
401
- (message) => message.id === id && message.role === "assistant" ? update(message) : message
402
- )
407
+ const next = messagesRef.current.map(
408
+ (message) => message.id === id && message.role === "assistant" ? update(message) : message
403
409
  );
410
+ messagesRef.current = next;
411
+ setMessages(next);
404
412
  }, []);
405
413
  const send = useCallback(
406
414
  async (text, opts) => {
@@ -411,15 +419,17 @@ function useFrankieChat() {
411
419
  const assistantId = nextId("a");
412
420
  setError(void 0);
413
421
  setBusy(true);
414
- setMessages((current) => [
415
- ...current,
422
+ const next = [
423
+ ...messagesRef.current,
416
424
  newUserMessage(
417
425
  nextId("u"),
418
426
  trimmed,
419
427
  attachments.map((file) => ({ name: file.name, mimeType: file.mimeType }))
420
428
  ),
421
429
  newAssistantMessage(assistantId)
422
- ]);
430
+ ];
431
+ messagesRef.current = next;
432
+ setMessages(next);
423
433
  const abort = new AbortController();
424
434
  abortRef.current = abort;
425
435
  try {
@@ -460,13 +470,15 @@ function useFrankieChat() {
460
470
  patchAssistant(assistantId, (current) => ({ ...current, status: "error", error: message }));
461
471
  }
462
472
  } finally {
463
- if (mounted.current) {
464
- setBusy(false);
465
- patchAssistant(
466
- assistantId,
467
- (message) => message.status === "streaming" ? { ...message, status: "done" } : message
468
- );
473
+ patchAssistant(
474
+ assistantId,
475
+ (message) => message.status === "streaming" ? { ...message, status: "done" } : message
476
+ );
477
+ const transcript = messagesRef.current;
478
+ if (transcript.length) {
479
+ await client.syncConversation(transcript, conversationTitle(transcript)).catch(() => void 0);
469
480
  }
481
+ if (mounted.current) setBusy(false);
470
482
  abortRef.current = null;
471
483
  }
472
484
  },
@@ -476,6 +488,7 @@ function useFrankieChat() {
476
488
  const reset = useCallback(() => {
477
489
  abortRef.current?.abort();
478
490
  client.resetSession();
491
+ messagesRef.current = [];
479
492
  setMessages([]);
480
493
  setError(void 0);
481
494
  setBusy(false);
@@ -1244,7 +1257,63 @@ function FrankieInline({ className, style, ...panelProps }) {
1244
1257
  }
1245
1258
 
1246
1259
  // src/components/chat/FrankieChatPage.tsx
1247
- import { jsx as jsx16 } from "react/jsx-runtime";
1260
+ import { Fragment as Fragment3, jsx as jsx16, jsxs as jsxs6 } from "react/jsx-runtime";
1261
+ function PageEmptyState() {
1262
+ const { config } = useFrankie();
1263
+ const { send, busy } = useFrankieChat();
1264
+ const name = config?.branding.name ?? "Frankie";
1265
+ const prompts = config?.suggestedPrompts?.slice(0, 3) ?? [];
1266
+ return /* @__PURE__ */ jsxs6("section", { className: "frankie-page-empty", "aria-label": "Start a conversation", children: [
1267
+ /* @__PURE__ */ jsx16("span", { className: "frankie-page-avatar", "aria-hidden": true, children: name.trim().slice(0, 1).toUpperCase() }),
1268
+ /* @__PURE__ */ jsxs6("div", { children: [
1269
+ /* @__PURE__ */ jsxs6("p", { className: "frankie-page-kicker", children: [
1270
+ name,
1271
+ " assistant"
1272
+ ] }),
1273
+ /* @__PURE__ */ jsx16("h1", { children: "What would you like to do?" }),
1274
+ /* @__PURE__ */ jsx16("p", { children: config?.greeting ?? "Ask a question or choose a task to get started." })
1275
+ ] }),
1276
+ prompts.length ? /* @__PURE__ */ jsx16("div", { className: "frankie-page-prompts", children: prompts.map((prompt, index) => /* @__PURE__ */ jsxs6("button", { type: "button", disabled: busy, onClick: () => send(prompt), children: [
1277
+ /* @__PURE__ */ jsx16("span", { children: String(index + 1).padStart(2, "0") }),
1278
+ /* @__PURE__ */ jsx16("strong", { children: prompt }),
1279
+ /* @__PURE__ */ jsx16("b", { "aria-hidden": true, children: "\u2197" })
1280
+ ] }, prompt)) }) : null
1281
+ ] });
1282
+ }
1283
+ function PageChrome({
1284
+ header,
1285
+ footer,
1286
+ emptyState,
1287
+ renderMessage,
1288
+ renderTool,
1289
+ placeholder,
1290
+ autoFocus
1291
+ }) {
1292
+ const { config } = useFrankie();
1293
+ const { reset } = useFrankieChat();
1294
+ const name = config?.branding.name ?? "Frankie";
1295
+ return /* @__PURE__ */ jsxs6(Fragment3, { children: [
1296
+ /* @__PURE__ */ jsxs6("header", { className: "frankie-page-header", children: [
1297
+ /* @__PURE__ */ jsxs6("div", { children: [
1298
+ /* @__PURE__ */ jsx16("span", { className: "frankie-page-status", "aria-hidden": true }),
1299
+ /* @__PURE__ */ jsx16("strong", { children: name })
1300
+ ] }),
1301
+ /* @__PURE__ */ jsx16("button", { type: "button", onClick: reset, "aria-label": "Start a new conversation", children: "New chat" })
1302
+ ] }),
1303
+ /* @__PURE__ */ jsx16("div", { className: "frankie-page-canvas", children: /* @__PURE__ */ jsx16(
1304
+ FrankieInline,
1305
+ {
1306
+ header: header ?? false,
1307
+ footer,
1308
+ emptyState: emptyState ?? /* @__PURE__ */ jsx16(PageEmptyState, {}),
1309
+ renderMessage,
1310
+ renderTool,
1311
+ placeholder,
1312
+ autoFocus
1313
+ }
1314
+ ) })
1315
+ ] });
1316
+ }
1248
1317
  function FrankieChatPage({
1249
1318
  className,
1250
1319
  style,
@@ -1260,10 +1329,10 @@ function FrankieChatPage({
1260
1329
  return /* @__PURE__ */ jsx16(
1261
1330
  "main",
1262
1331
  {
1263
- className,
1332
+ className: ["frankie-root", "frankie-chat-page", className].filter(Boolean).join(" "),
1264
1333
  style: { height: "100dvh", minHeight: "100dvh", width: "100%", ...style },
1265
1334
  children: /* @__PURE__ */ jsx16(FrankieProvider, { ...providerProps, children: /* @__PURE__ */ jsx16(
1266
- FrankieInline,
1335
+ PageChrome,
1267
1336
  {
1268
1337
  header,
1269
1338
  footer,