@firstlovecenter/ai-chat 0.2.0 → 0.2.2

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
@@ -523,6 +523,14 @@ function AiLineChart({
523
523
  ))
524
524
  ] }) }) });
525
525
  }
526
+ function formatDuration(ms) {
527
+ if (ms < 0) ms = 0;
528
+ const totalSec = Math.round(ms / 1e3);
529
+ if (totalSec < 60) return `${totalSec}s`;
530
+ const m = Math.floor(totalSec / 60);
531
+ const s = totalSec % 60;
532
+ return s === 0 ? `${m}m` : `${m}m ${s}s`;
533
+ }
526
534
  var PROVIDER_LABELS = {
527
535
  claude: "Claude",
528
536
  grok: "Grok",
@@ -697,7 +705,7 @@ function AiChat({
697
705
  }
698
706
  setAnswers((prev) => [
699
707
  ...prev,
700
- { question: trimmed, blocks: [], done: false }
708
+ { question: trimmed, blocks: [], done: false, startedAt: Date.now() }
701
709
  ]);
702
710
  const ac = new AbortController();
703
711
  abortRef.current = ac;
@@ -718,6 +726,7 @@ function AiChat({
718
726
  (prev) => updateLast(prev, (a) => ({
719
727
  ...a,
720
728
  done: true,
729
+ durationMs: a.startedAt != null ? Date.now() - a.startedAt : void 0,
721
730
  error: { code: "NETWORK", message }
722
731
  }))
723
732
  );
@@ -729,6 +738,7 @@ function AiChat({
729
738
  (prev) => updateLast(prev, (a) => ({
730
739
  ...a,
731
740
  done: true,
741
+ durationMs: a.startedAt != null ? Date.now() - a.startedAt : void 0,
732
742
  error: { code: "NO_BODY", message: "No response stream." }
733
743
  }))
734
744
  );
@@ -755,6 +765,7 @@ function AiChat({
755
765
  (prev) => updateLast(prev, (a) => ({
756
766
  ...a,
757
767
  done: true,
768
+ durationMs: a.startedAt != null ? Date.now() - a.startedAt : void 0,
758
769
  error: { code: "STREAM", message }
759
770
  }))
760
771
  );
@@ -787,7 +798,7 @@ function AiChat({
787
798
  /* @__PURE__ */ jsxs(
788
799
  "aside",
789
800
  {
790
- "aria-hidden": !sidebarOpen,
801
+ inert: !sidebarOpen,
791
802
  className: cn(
792
803
  "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
804
  sidebarOpen ? "translate-x-0" : "-translate-x-full"
@@ -919,22 +930,16 @@ function AiChat({
919
930
  ] }) : heroVisible ? /* @__PURE__ */ jsxs("div", { className: "flex h-full flex-col items-center justify-center text-center", children: [
920
931
  /* @__PURE__ */ jsx("h1", { className: "text-2xl font-medium tracking-tight text-foreground sm:text-3xl", children: greeting }),
921
932
  /* @__PURE__ */ jsx("p", { className: "mt-2 text-2xl font-light tracking-tight text-muted-foreground sm:text-3xl", children: "What's on your mind?" })
922
- ] }) : /* @__PURE__ */ jsxs("div", { className: "mx-auto flex w-full max-w-3xl flex-col gap-6", children: [
923
- answers.map((a, idx) => /* @__PURE__ */ jsx(
924
- AnswerView,
925
- {
926
- answer: a,
927
- onRetry: () => void submit(a.question),
928
- forwardRef: idx === answers.length - 1 ? lastAnswerRef : void 0,
929
- isLast: idx === answers.length - 1
930
- },
931
- idx
932
- )),
933
- pending && /* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground", children: [
934
- /* @__PURE__ */ jsx(Loader2, { className: "mr-1 inline size-3.5 animate-spin" }),
935
- "Thinking\u2026"
936
- ] })
937
- ] })
933
+ ] }) : /* @__PURE__ */ jsx("div", { className: "mx-auto flex w-full max-w-3xl flex-col gap-6", children: answers.map((a, idx) => /* @__PURE__ */ jsx(
934
+ AnswerView,
935
+ {
936
+ answer: a,
937
+ onRetry: () => void submit(a.question),
938
+ forwardRef: idx === answers.length - 1 ? lastAnswerRef : void 0,
939
+ isLast: idx === answers.length - 1
940
+ },
941
+ idx
942
+ )) })
938
943
  }
939
944
  ),
940
945
  /* @__PURE__ */ jsx("div", { className: "shrink-0 px-4 pb-4 pt-2", children: /* @__PURE__ */ jsxs(
@@ -1059,6 +1064,14 @@ function AnswerView({
1059
1064
  }, [answer.blocks, answer.error]);
1060
1065
  const showActions = answer.done && (answer.blocks.length > 0 || answer.error != null);
1061
1066
  const isThinking = !answer.done && answer.blocks.length === 0 && !answer.error;
1067
+ const [, forceTick] = useState(0);
1068
+ useEffect(() => {
1069
+ if (answer.done || answer.startedAt == null) return;
1070
+ const id = window.setInterval(() => forceTick((n) => n + 1), 1e3);
1071
+ return () => window.clearInterval(id);
1072
+ }, [answer.done, answer.startedAt]);
1073
+ const liveElapsed = answer.startedAt != null ? Date.now() - answer.startedAt : null;
1074
+ const finalDuration = answer.durationMs;
1062
1075
  return /* @__PURE__ */ jsxs(
1063
1076
  "div",
1064
1077
  {
@@ -1075,7 +1088,15 @@ function AnswerView({
1075
1088
  /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx(UserChip, { text: answer.question }) }),
1076
1089
  isThinking ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
1077
1090
  /* @__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" }) }),
1078
- /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Thinking\u2026" })
1091
+ /* @__PURE__ */ jsxs("p", { className: "flex items-center text-sm text-muted-foreground", children: [
1092
+ /* @__PURE__ */ jsx(Loader2, { className: "mr-1 inline size-3.5 animate-spin" }),
1093
+ "Thinking\u2026",
1094
+ liveElapsed != null && liveElapsed >= 1e3 && /* @__PURE__ */ jsxs("span", { className: "ml-1 tabular-nums", children: [
1095
+ "(",
1096
+ formatDuration(liveElapsed),
1097
+ ")"
1098
+ ] })
1099
+ ] })
1079
1100
  ] }) : /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
1080
1101
  /* @__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" }) }),
1081
1102
  /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-3", children: [
@@ -1107,6 +1128,14 @@ function AnswerView({
1107
1128
  className: "inline-flex size-8 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground",
1108
1129
  children: /* @__PURE__ */ jsx(RotateCcw, { className: "size-4" })
1109
1130
  }
1131
+ ),
1132
+ finalDuration != null && finalDuration >= 1e3 && /* @__PURE__ */ jsx(
1133
+ "span",
1134
+ {
1135
+ className: "ml-1 text-xs text-muted-foreground tabular-nums",
1136
+ title: "Time taken to generate this response",
1137
+ children: formatDuration(finalDuration)
1138
+ }
1110
1139
  )
1111
1140
  ] })
1112
1141
  ] })
@@ -1312,7 +1341,13 @@ function handleEvent(raw, setAnswers) {
1312
1341
  })
1313
1342
  );
1314
1343
  } else if (event === "done") {
1315
- setAnswers((prev) => updateLast(prev, (a) => ({ ...a, done: true })));
1344
+ setAnswers(
1345
+ (prev) => updateLast(prev, (a) => ({
1346
+ ...a,
1347
+ done: true,
1348
+ durationMs: a.startedAt != null ? Date.now() - a.startedAt : void 0
1349
+ }))
1350
+ );
1316
1351
  } else if (event === "error") {
1317
1352
  setAnswers(
1318
1353
  (prev) => updateLast(prev, (a) => ({