@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.js CHANGED
@@ -381,8 +381,22 @@ function sanitiseBlock(input) {
381
381
  }
382
382
  return { kind: "callout", tone: input.tone, text: input.text };
383
383
  }
384
+ function isBlockEmpty(b) {
385
+ if (b.kind === "paragraph_brief") {
386
+ if (b.prose && b.prose.trim()) return false;
387
+ return b.key_facts.length === 0 || b.key_facts.every((f) => !f.trim());
388
+ }
389
+ if (b.kind === "list") {
390
+ return b.items.length === 0 || b.items.every((i) => !i.trim());
391
+ }
392
+ if (b.kind === "callout") return !b.text.trim();
393
+ if (b.kind === "chart") return b.data.length === 0;
394
+ if (b.kind === "table") return b.rows.length === 0;
395
+ return false;
396
+ }
384
397
  function AnswerBlocks({ blocks }) {
385
- return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-3", children: blocks.map((b, i) => /* @__PURE__ */ jsx(BlockView, { block: b }, i)) });
398
+ const visible = blocks.filter((b) => !isBlockEmpty(b));
399
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-3", children: visible.map((b, i) => /* @__PURE__ */ jsx(BlockView, { block: b }, i)) });
386
400
  }
387
401
  function BlockView({ block }) {
388
402
  if (block.kind === "paragraph_brief") {
@@ -523,6 +537,14 @@ function AiLineChart({
523
537
  ))
524
538
  ] }) }) });
525
539
  }
540
+ function formatDuration(ms) {
541
+ if (ms < 0) ms = 0;
542
+ const totalSec = Math.round(ms / 1e3);
543
+ if (totalSec < 60) return `${totalSec}s`;
544
+ const m = Math.floor(totalSec / 60);
545
+ const s = totalSec % 60;
546
+ return s === 0 ? `${m}m` : `${m}m ${s}s`;
547
+ }
526
548
  var PROVIDER_LABELS = {
527
549
  claude: "Claude",
528
550
  grok: "Grok",
@@ -697,7 +719,7 @@ function AiChat({
697
719
  }
698
720
  setAnswers((prev) => [
699
721
  ...prev,
700
- { question: trimmed, blocks: [], done: false }
722
+ { question: trimmed, blocks: [], done: false, startedAt: Date.now() }
701
723
  ]);
702
724
  const ac = new AbortController();
703
725
  abortRef.current = ac;
@@ -718,6 +740,7 @@ function AiChat({
718
740
  (prev) => updateLast(prev, (a) => ({
719
741
  ...a,
720
742
  done: true,
743
+ durationMs: a.startedAt != null ? Date.now() - a.startedAt : void 0,
721
744
  error: { code: "NETWORK", message }
722
745
  }))
723
746
  );
@@ -729,6 +752,7 @@ function AiChat({
729
752
  (prev) => updateLast(prev, (a) => ({
730
753
  ...a,
731
754
  done: true,
755
+ durationMs: a.startedAt != null ? Date.now() - a.startedAt : void 0,
732
756
  error: { code: "NO_BODY", message: "No response stream." }
733
757
  }))
734
758
  );
@@ -755,6 +779,7 @@ function AiChat({
755
779
  (prev) => updateLast(prev, (a) => ({
756
780
  ...a,
757
781
  done: true,
782
+ durationMs: a.startedAt != null ? Date.now() - a.startedAt : void 0,
758
783
  error: { code: "STREAM", message }
759
784
  }))
760
785
  );
@@ -787,7 +812,7 @@ function AiChat({
787
812
  /* @__PURE__ */ jsxs(
788
813
  "aside",
789
814
  {
790
- "aria-hidden": !sidebarOpen,
815
+ inert: !sidebarOpen,
791
816
  className: cn(
792
817
  "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",
793
818
  sidebarOpen ? "translate-x-0" : "-translate-x-full"
@@ -1053,6 +1078,14 @@ function AnswerView({
1053
1078
  }, [answer.blocks, answer.error]);
1054
1079
  const showActions = answer.done && (answer.blocks.length > 0 || answer.error != null);
1055
1080
  const isThinking = !answer.done && answer.blocks.length === 0 && !answer.error;
1081
+ const [, forceTick] = useState(0);
1082
+ useEffect(() => {
1083
+ if (answer.done || answer.startedAt == null) return;
1084
+ const id = window.setInterval(() => forceTick((n) => n + 1), 1e3);
1085
+ return () => window.clearInterval(id);
1086
+ }, [answer.done, answer.startedAt]);
1087
+ const liveElapsed = answer.startedAt != null ? Date.now() - answer.startedAt : null;
1088
+ const finalDuration = answer.durationMs;
1056
1089
  return /* @__PURE__ */ jsxs(
1057
1090
  "div",
1058
1091
  {
@@ -1071,7 +1104,12 @@ function AnswerView({
1071
1104
  /* @__PURE__ */ jsx("div", { className: "flex size-7 shrink-0 items-center justify-center rounded-full bg-primary/10 text-primary", children: /* @__PURE__ */ jsx(Sparkles, { className: "size-4" }) }),
1072
1105
  /* @__PURE__ */ jsxs("p", { className: "flex items-center text-sm text-muted-foreground", children: [
1073
1106
  /* @__PURE__ */ jsx(Loader2, { className: "mr-1 inline size-3.5 animate-spin" }),
1074
- "Thinking\u2026"
1107
+ "Thinking\u2026",
1108
+ liveElapsed != null && liveElapsed >= 1e3 && /* @__PURE__ */ jsxs("span", { className: "ml-2 text-xs tabular-nums", children: [
1109
+ "(",
1110
+ formatDuration(liveElapsed),
1111
+ ")"
1112
+ ] })
1075
1113
  ] })
1076
1114
  ] }) : /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
1077
1115
  /* @__PURE__ */ jsx("div", { className: "mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-full bg-primary/10 text-primary", children: /* @__PURE__ */ jsx(Sparkles, { className: "size-4" }) }),
@@ -1104,6 +1142,14 @@ function AnswerView({
1104
1142
  className: "inline-flex size-8 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground",
1105
1143
  children: /* @__PURE__ */ jsx(RotateCcw, { className: "size-4" })
1106
1144
  }
1145
+ ),
1146
+ finalDuration != null && finalDuration >= 1e3 && /* @__PURE__ */ jsx(
1147
+ "span",
1148
+ {
1149
+ className: "ml-1 text-xs text-muted-foreground tabular-nums",
1150
+ title: "Time taken to generate this response",
1151
+ children: formatDuration(finalDuration)
1152
+ }
1107
1153
  )
1108
1154
  ] })
1109
1155
  ] })
@@ -1309,7 +1355,13 @@ function handleEvent(raw, setAnswers) {
1309
1355
  })
1310
1356
  );
1311
1357
  } else if (event === "done") {
1312
- setAnswers((prev) => updateLast(prev, (a) => ({ ...a, done: true })));
1358
+ setAnswers(
1359
+ (prev) => updateLast(prev, (a) => ({
1360
+ ...a,
1361
+ done: true,
1362
+ durationMs: a.startedAt != null ? Date.now() - a.startedAt : void 0
1363
+ }))
1364
+ );
1313
1365
  } else if (event === "error") {
1314
1366
  setAnswers(
1315
1367
  (prev) => updateLast(prev, (a) => ({