@firstlovecenter/ai-chat 0.2.1 → 0.2.3

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/ui/index.cjs CHANGED
@@ -402,8 +402,22 @@ function sanitiseBlock(input) {
402
402
  }
403
403
  return { kind: "callout", tone: input.tone, text: input.text };
404
404
  }
405
+ function isBlockEmpty(b) {
406
+ if (b.kind === "paragraph_brief") {
407
+ if (b.prose && b.prose.trim()) return false;
408
+ return b.key_facts.length === 0 || b.key_facts.every((f) => !f.trim());
409
+ }
410
+ if (b.kind === "list") {
411
+ return b.items.length === 0 || b.items.every((i) => !i.trim());
412
+ }
413
+ if (b.kind === "callout") return !b.text.trim();
414
+ if (b.kind === "chart") return b.data.length === 0;
415
+ if (b.kind === "table") return b.rows.length === 0;
416
+ return false;
417
+ }
405
418
  function AnswerBlocks({ blocks }) {
406
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-3", children: blocks.map((b, i) => /* @__PURE__ */ jsxRuntime.jsx(BlockView, { block: b }, i)) });
419
+ const visible = blocks.filter((b) => !isBlockEmpty(b));
420
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-3", children: visible.map((b, i) => /* @__PURE__ */ jsxRuntime.jsx(BlockView, { block: b }, i)) });
407
421
  }
408
422
  function BlockView({ block }) {
409
423
  if (block.kind === "paragraph_brief") {
@@ -544,6 +558,14 @@ function AiLineChart({
544
558
  ))
545
559
  ] }) }) });
546
560
  }
561
+ function formatDuration(ms) {
562
+ if (ms < 0) ms = 0;
563
+ const totalSec = Math.round(ms / 1e3);
564
+ if (totalSec < 60) return `${totalSec}s`;
565
+ const m = Math.floor(totalSec / 60);
566
+ const s = totalSec % 60;
567
+ return s === 0 ? `${m}m` : `${m}m ${s}s`;
568
+ }
547
569
  var PROVIDER_LABELS = {
548
570
  claude: "Claude",
549
571
  grok: "Grok",
@@ -718,7 +740,7 @@ function AiChat({
718
740
  }
719
741
  setAnswers((prev) => [
720
742
  ...prev,
721
- { question: trimmed, blocks: [], done: false }
743
+ { question: trimmed, blocks: [], done: false, startedAt: Date.now() }
722
744
  ]);
723
745
  const ac = new AbortController();
724
746
  abortRef.current = ac;
@@ -739,6 +761,7 @@ function AiChat({
739
761
  (prev) => updateLast(prev, (a) => ({
740
762
  ...a,
741
763
  done: true,
764
+ durationMs: a.startedAt != null ? Date.now() - a.startedAt : void 0,
742
765
  error: { code: "NETWORK", message }
743
766
  }))
744
767
  );
@@ -750,6 +773,7 @@ function AiChat({
750
773
  (prev) => updateLast(prev, (a) => ({
751
774
  ...a,
752
775
  done: true,
776
+ durationMs: a.startedAt != null ? Date.now() - a.startedAt : void 0,
753
777
  error: { code: "NO_BODY", message: "No response stream." }
754
778
  }))
755
779
  );
@@ -776,6 +800,7 @@ function AiChat({
776
800
  (prev) => updateLast(prev, (a) => ({
777
801
  ...a,
778
802
  done: true,
803
+ durationMs: a.startedAt != null ? Date.now() - a.startedAt : void 0,
779
804
  error: { code: "STREAM", message }
780
805
  }))
781
806
  );
@@ -808,7 +833,7 @@ function AiChat({
808
833
  /* @__PURE__ */ jsxRuntime.jsxs(
809
834
  "aside",
810
835
  {
811
- "aria-hidden": !sidebarOpen,
836
+ inert: !sidebarOpen,
812
837
  className: cn(
813
838
  "absolute inset-y-0 left-0 z-20 flex w-72 max-w-[85vw] flex-col border-r border-border bg-sidebar text-sidebar-foreground shadow-lg transition-transform duration-200 ease-out",
814
839
  sidebarOpen ? "translate-x-0" : "-translate-x-full"
@@ -1074,6 +1099,14 @@ function AnswerView({
1074
1099
  }, [answer.blocks, answer.error]);
1075
1100
  const showActions = answer.done && (answer.blocks.length > 0 || answer.error != null);
1076
1101
  const isThinking = !answer.done && answer.blocks.length === 0 && !answer.error;
1102
+ const [, forceTick] = React.useState(0);
1103
+ React.useEffect(() => {
1104
+ if (answer.done || answer.startedAt == null) return;
1105
+ const id = window.setInterval(() => forceTick((n) => n + 1), 1e3);
1106
+ return () => window.clearInterval(id);
1107
+ }, [answer.done, answer.startedAt]);
1108
+ const liveElapsed = answer.startedAt != null ? Date.now() - answer.startedAt : null;
1109
+ const finalDuration = answer.durationMs;
1077
1110
  return /* @__PURE__ */ jsxRuntime.jsxs(
1078
1111
  "div",
1079
1112
  {
@@ -1092,7 +1125,12 @@ function AnswerView({
1092
1125
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex size-7 shrink-0 items-center justify-center rounded-full bg-primary/10 text-primary", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, { className: "size-4" }) }),
1093
1126
  /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "flex items-center text-sm text-muted-foreground", children: [
1094
1127
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "mr-1 inline size-3.5 animate-spin" }),
1095
- "Thinking\u2026"
1128
+ "Thinking\u2026",
1129
+ liveElapsed != null && liveElapsed >= 1e3 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ml-2 text-xs tabular-nums", children: [
1130
+ "(",
1131
+ formatDuration(liveElapsed),
1132
+ ")"
1133
+ ] })
1096
1134
  ] })
1097
1135
  ] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-3", children: [
1098
1136
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-full bg-primary/10 text-primary", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, { className: "size-4" }) }),
@@ -1125,6 +1163,14 @@ function AnswerView({
1125
1163
  className: "inline-flex size-8 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground",
1126
1164
  children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RotateCcw, { className: "size-4" })
1127
1165
  }
1166
+ ),
1167
+ finalDuration != null && finalDuration >= 1e3 && /* @__PURE__ */ jsxRuntime.jsx(
1168
+ "span",
1169
+ {
1170
+ className: "ml-1 text-xs text-muted-foreground tabular-nums",
1171
+ title: "Time taken to generate this response",
1172
+ children: formatDuration(finalDuration)
1173
+ }
1128
1174
  )
1129
1175
  ] })
1130
1176
  ] })
@@ -1330,7 +1376,13 @@ function handleEvent(raw, setAnswers) {
1330
1376
  })
1331
1377
  );
1332
1378
  } else if (event === "done") {
1333
- setAnswers((prev) => updateLast(prev, (a) => ({ ...a, done: true })));
1379
+ setAnswers(
1380
+ (prev) => updateLast(prev, (a) => ({
1381
+ ...a,
1382
+ done: true,
1383
+ durationMs: a.startedAt != null ? Date.now() - a.startedAt : void 0
1384
+ }))
1385
+ );
1334
1386
  } else if (event === "error") {
1335
1387
  setAnswers(
1336
1388
  (prev) => updateLast(prev, (a) => ({