@neuronection/assistant-ui 0.51.0 → 0.53.0

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.
Files changed (89) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/README.md +4 -1
  3. package/dist/chat-branch-tree.d.ts +15 -2
  4. package/dist/chat-composer.d.ts +50 -2
  5. package/dist/chat-composer.js +1 -1
  6. package/dist/chat-core.d.ts +54 -4
  7. package/dist/chat-launcher.js +1 -1
  8. package/dist/chat-markdown.d.ts +8 -1
  9. package/dist/chat-markdown.js +1 -1
  10. package/dist/chat-message.d.ts +16 -1
  11. package/dist/chat-message.js +1 -1
  12. package/dist/chat-panel.js +1 -1
  13. package/dist/chat-pending.d.ts +25 -0
  14. package/dist/chat-pending.js +3 -0
  15. package/dist/chat-pending.js.map +1 -0
  16. package/dist/chat-reasoning.d.ts +10 -2
  17. package/dist/chat-reasoning.js +1 -1
  18. package/dist/chat-session-list.d.ts +7 -0
  19. package/dist/chat-session-list.js +1 -1
  20. package/dist/chat-suggestion-card.d.ts +19 -0
  21. package/dist/chat-suggestion-card.js +3 -0
  22. package/dist/chat-suggestion-card.js.map +1 -0
  23. package/dist/chat-tool-card.js +1 -1
  24. package/dist/chat-transcript.d.ts +81 -8
  25. package/dist/chat-transcript.js +1 -1
  26. package/dist/chat-turn-status.js +1 -1
  27. package/dist/chat-welcome.d.ts +23 -0
  28. package/dist/chat-welcome.js +3 -0
  29. package/dist/chat-welcome.js.map +1 -0
  30. package/dist/{chunk-PC3CE4W2.js → chunk-22MIIWC7.js} +45 -7
  31. package/dist/chunk-22MIIWC7.js.map +1 -0
  32. package/dist/{chunk-GDGKARRB.js → chunk-5C2OLUDR.js} +11 -2
  33. package/dist/chunk-5C2OLUDR.js.map +1 -0
  34. package/dist/{chunk-5BJ4VULP.js → chunk-EQPXM2R4.js} +23 -10
  35. package/dist/chunk-EQPXM2R4.js.map +1 -0
  36. package/dist/chunk-F4BTJQLK.js +43 -0
  37. package/dist/chunk-F4BTJQLK.js.map +1 -0
  38. package/dist/{chunk-TQDTFPM4.js → chunk-GIQZNEG5.js} +3 -3
  39. package/dist/chunk-GIQZNEG5.js.map +1 -0
  40. package/dist/{chunk-E3NZM4U7.js → chunk-GO4ILLS6.js} +17 -4
  41. package/dist/chunk-GO4ILLS6.js.map +1 -0
  42. package/dist/chunk-HXU6P5FD.js +479 -0
  43. package/dist/chunk-HXU6P5FD.js.map +1 -0
  44. package/dist/{chunk-MFM4W6ZO.js → chunk-I4R5BRC7.js} +3 -3
  45. package/dist/{chunk-MFM4W6ZO.js.map → chunk-I4R5BRC7.js.map} +1 -1
  46. package/dist/{chunk-5U45TQJE.js → chunk-I5H4KYEZ.js} +3 -3
  47. package/dist/{chunk-5U45TQJE.js.map → chunk-I5H4KYEZ.js.map} +1 -1
  48. package/dist/{chunk-S63SYV6O.js → chunk-INXQJM2K.js} +27 -6
  49. package/dist/chunk-INXQJM2K.js.map +1 -0
  50. package/dist/{chunk-HQ3X5SVR.js → chunk-KQLKITZL.js} +105 -16
  51. package/dist/chunk-KQLKITZL.js.map +1 -0
  52. package/dist/chunk-M4SYVPC4.js +3 -0
  53. package/dist/chunk-M4SYVPC4.js.map +1 -0
  54. package/dist/chunk-NOAKHVBJ.js +264 -0
  55. package/dist/chunk-NOAKHVBJ.js.map +1 -0
  56. package/dist/chunk-OOQO6VGO.js +33 -0
  57. package/dist/chunk-OOQO6VGO.js.map +1 -0
  58. package/dist/chunk-P3QUNCBR.js +366 -0
  59. package/dist/chunk-P3QUNCBR.js.map +1 -0
  60. package/dist/chunk-PABMR4RA.js +30 -0
  61. package/dist/chunk-PABMR4RA.js.map +1 -0
  62. package/dist/{chunk-SEZZVAGP.js → chunk-SR4IN564.js} +10 -3
  63. package/dist/chunk-SR4IN564.js.map +1 -0
  64. package/dist/index.d.ts +250 -35
  65. package/dist/index.js +40 -37
  66. package/dist/markdown-diff-view.js +1 -1
  67. package/dist/model-registry.js +1 -1
  68. package/dist/styles.css +1 -1
  69. package/dist/theme-scope.d.ts +1 -1
  70. package/dist/tokens.d.ts +1 -1
  71. package/dist/tokens.js +1 -1
  72. package/dist/{types-LhE1KFBf.d.ts → types-CxieoN2u.d.ts} +1 -1
  73. package/dist/types-pivJdPC8.d.ts +14 -0
  74. package/package.json +17 -5
  75. package/dist/chunk-5BJ4VULP.js.map +0 -1
  76. package/dist/chunk-C5JZCCU7.js +0 -220
  77. package/dist/chunk-C5JZCCU7.js.map +0 -1
  78. package/dist/chunk-E3NZM4U7.js.map +0 -1
  79. package/dist/chunk-GDGKARRB.js.map +0 -1
  80. package/dist/chunk-HQ3X5SVR.js.map +0 -1
  81. package/dist/chunk-NUD32RLL.js +0 -199
  82. package/dist/chunk-NUD32RLL.js.map +0 -1
  83. package/dist/chunk-PC3CE4W2.js.map +0 -1
  84. package/dist/chunk-S63SYV6O.js.map +0 -1
  85. package/dist/chunk-SEZZVAGP.js.map +0 -1
  86. package/dist/chunk-TQDTFPM4.js.map +0 -1
  87. package/dist/chunk-XOODPYJ3.js +0 -173
  88. package/dist/chunk-XOODPYJ3.js.map +0 -1
  89. package/dist/tree-4Hv9stjF.d.ts +0 -54
@@ -1,17 +1,60 @@
1
1
  import * as React from 'react';
2
- import { d as ChatMessageView } from './types-LhE1KFBf.js';
2
+ import { e as ChatMessageView } from './types-CxieoN2u.js';
3
+
4
+ interface ChatSearchFieldLabels {
5
+ search: string;
6
+ placeholder: string;
7
+ previous: string;
8
+ next: string;
9
+ clear: string;
10
+ matches: string;
11
+ }
12
+ interface ChatSearchFieldProps {
13
+ query: string;
14
+ onQueryChange: (query: string) => void;
15
+ /** Total matches in the transcript (drives the `n/N` readout). */
16
+ matchCount?: number;
17
+ /** 0-based active match index. */
18
+ activeIndex?: number;
19
+ onPrevious?: () => void;
20
+ onNext?: () => void;
21
+ labels?: Partial<ChatSearchFieldLabels>;
22
+ className?: string;
23
+ }
24
+ /**
25
+ * The in-conversation search field (plan 101 P2): controlled query input
26
+ * with an `n/N` match readout and prev/next controls. Enter jumps to the
27
+ * next match, Shift+Enter to the previous, Escape clears. Composed by
28
+ * `ChatTranscript` when its `search` prop is given, or standalone.
29
+ */
30
+ declare const ChatSearchField: React.ForwardRefExoticComponent<ChatSearchFieldProps & React.RefAttributes<HTMLDivElement>>;
3
31
 
4
32
  interface ChatTranscriptLabels {
5
33
  log: string;
6
34
  scrollToBottom: string;
35
+ newMessages: string;
7
36
  empty: string;
8
37
  replied: string;
38
+ search: string;
39
+ searchPlaceholder: string;
40
+ previousMatch: string;
41
+ nextMatch: string;
42
+ clearSearch: string;
43
+ matches: string;
44
+ today: string;
45
+ yesterday: string;
46
+ stop: string;
47
+ }
48
+ type ChatTranscriptGroupPos = 'single' | 'start' | 'mid' | 'end';
49
+ interface ChatTranscriptSearch {
50
+ query: string;
51
+ onQueryChange: (query: string) => void;
9
52
  }
10
53
  interface ChatTranscriptProps {
11
54
  items: ChatMessageView[];
12
- /** Override the default `ChatMessage` + `MarkdownSurface` rendering. */
13
- renderItem?: (message: ChatMessageView, index: number) => React.ReactNode;
14
- /** Streaming tail — the live turn bubble (or any node). */
55
+ /** Override the default `ChatMessage` + `MarkdownSurface` rendering (third arg: group position when `groupConsecutive`; fourth arg: active search query). */
56
+ renderItem?: (message: ChatMessageView, index: number, groupPos?: ChatTranscriptGroupPos, query?: string) => React.ReactNode;
57
+ /** Streaming tail — the live turn bubble (or `ChatPending`) or any node. */
15
58
  live?: React.ReactNode;
16
59
  emptyState?: React.ReactNode;
17
60
  /** Stick to bottom while the user hasn't scrolled away. Default true. */
@@ -20,14 +63,44 @@ interface ChatTranscriptProps {
20
63
  scrollThresholdPx?: number;
21
64
  /** Virtualize long transcripts (dynamic row measurement). Default false. */
22
65
  virtualized?: boolean;
23
- labels?: Partial<ChatTranscriptLabels>;
66
+ /**
67
+ * Tighten the spacing between consecutive same-role messages and tag
68
+ * each row with `data-group-pos` (`single | start | mid | end`).
69
+ * Default false.
70
+ */
71
+ groupConsecutive?: boolean;
72
+ /** Visual design for the default renderer: paired bubbles (default) or flat assistant prose over the panel surface. */
73
+ design?: 'bubbles' | 'flat';
74
+ /**
75
+ * In-conversation search (plan 101 P2): renders `ChatSearchField`
76
+ * above the log, highlights matches in the default renderer, and
77
+ * jumps between them (Enter/Shift+Enter in the field, or the
78
+ * prev/next buttons). Jump-scrolling requires a non-virtualized
79
+ * transcript; virtualized transcripts highlight visible rows only.
80
+ */
81
+ search?: ChatTranscriptSearch;
82
+ /**
83
+ * Today/Yesterday/date chips between day groups (reads
84
+ * `ChatMessageView.createdAt`; rows without one join the previous
85
+ * group). Default false.
86
+ */
87
+ showDateSeparators?: boolean;
88
+ /** Custom formatter for older-day chips (default `MMM d, yyyy`). */
89
+ formatDate?: (date: Date) => string;
90
+ /** Scroll a message into view and flash it when this changes (branch-tree clicks, citations, deep links). */
91
+ focusMessageId?: string | null;
92
+ /** Show a floating stop pill beside the jump pill while a live turn streams and the user scrolled away. */
93
+ onStop?: () => void;
94
+ labels?: Partial<ChatTranscriptLabels & ChatSearchFieldLabels>;
24
95
  className?: string;
25
96
  }
26
97
  /**
27
98
  * The conversation list: `role="log"` with polite turn-completion
28
- * announcements, stick-to-bottom auto-scroll with a jump-to-latest pill,
29
- * optional virtualization for thousand-message threads.
99
+ * announcements, stick-to-bottom auto-scroll with an unread-counting
100
+ * jump-to-latest pill, optional consecutive-message grouping, a top
101
+ * fade mask while scrolled into the thread, thin themed scrollbars,
102
+ * and optional virtualization for thousand-message threads.
30
103
  */
31
104
  declare const ChatTranscript: React.ForwardRefExoticComponent<ChatTranscriptProps & React.RefAttributes<HTMLDivElement>>;
32
105
 
33
- export { ChatTranscript, type ChatTranscriptLabels, type ChatTranscriptProps };
106
+ export { ChatSearchField, type ChatSearchFieldLabels, type ChatSearchFieldProps, ChatTranscript, type ChatTranscriptGroupPos, type ChatTranscriptLabels, type ChatTranscriptProps, type ChatTranscriptSearch };
@@ -1,3 +1,3 @@
1
- export { ChatTranscript } from './chunk-XOODPYJ3.js';
1
+ export { ChatSearchField, ChatTranscript } from './chunk-HXU6P5FD.js';
2
2
  //# sourceMappingURL=chat-transcript.js.map
3
3
  //# sourceMappingURL=chat-transcript.js.map
@@ -1,3 +1,3 @@
1
- export { ChatTurnStatus } from './chunk-TQDTFPM4.js';
1
+ export { ChatTurnStatus } from './chunk-GIQZNEG5.js';
2
2
  //# sourceMappingURL=chat-turn-status.js.map
3
3
  //# sourceMappingURL=chat-turn-status.js.map
@@ -0,0 +1,23 @@
1
+ import * as React from 'react';
2
+
3
+ interface ChatWelcomeProps {
4
+ /** Greeting headline, e.g. "Ask anything about your courses". */
5
+ title: string;
6
+ subtitle?: string;
7
+ /** Suggestion cards / chips — children stagger in (40 ms steps). */
8
+ suggestions?: React.ReactNode;
9
+ /** Node inside the breathing `--as-ai` orb. Default: `Sparkles`. */
10
+ icon?: React.ReactNode;
11
+ /** `page` centers generously; `sidebar` is the default density; `bubble` is compact. */
12
+ variant?: 'page' | 'sidebar' | 'bubble';
13
+ className?: string;
14
+ }
15
+ /**
16
+ * The chat empty-state kit (plan 100 S6): breathing `--as-ai` identity
17
+ * orb, greeting, subtitle and staggered suggestion slots — drop it into
18
+ * `ChatTranscript emptyState`. Presentational; suggestion targets stay
19
+ * app-side.
20
+ */
21
+ declare const ChatWelcome: React.ForwardRefExoticComponent<ChatWelcomeProps & React.RefAttributes<HTMLDivElement>>;
22
+
23
+ export { ChatWelcome, type ChatWelcomeProps };
@@ -0,0 +1,3 @@
1
+ export { ChatWelcome } from './chunk-OOQO6VGO.js';
2
+ //# sourceMappingURL=chat-welcome.js.map
3
+ //# sourceMappingURL=chat-welcome.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"chat-welcome.js"}
@@ -36,6 +36,7 @@ var ChatSessionList = React.forwardRef(
36
36
  onSelect,
37
37
  onNew,
38
38
  onRename,
39
+ onRenameCommit,
39
40
  onDelete,
40
41
  onExport,
41
42
  searchable = true,
@@ -45,6 +46,8 @@ var ChatSessionList = React.forwardRef(
45
46
  className
46
47
  }, ref) {
47
48
  const [query, setQuery] = React.useState("");
49
+ const [editingId, setEditingId] = React.useState(null);
50
+ const [draftTitle, setDraftTitle] = React.useState("");
48
51
  const NewIcon = icons?.newChat ?? MessageSquarePlus;
49
52
  const RenameIcon = icons?.rename ?? Pencil;
50
53
  const DeleteIcon = icons?.delete ?? Trash2;
@@ -111,17 +114,31 @@ var ChatSessionList = React.forwardRef(
111
114
  }
112
115
  ) : null
113
116
  ] }),
114
- sessions.length === 0 ? /* @__PURE__ */ jsx("p", { className: "px-2 py-3 text-xs text-[var(--as-muted-fg)]", children: labels?.empty ?? "No conversations yet" }) : filtered.length === 0 ? /* @__PURE__ */ jsx("p", { className: "px-2 py-3 text-xs text-[var(--as-muted-fg)]", children: labels?.noResults ?? "No matches" }) : /* @__PURE__ */ jsx("div", { role: "list", "aria-label": labels?.search ?? "Conversations", className: "min-h-0 flex-1 overflow-y-auto", children: groups.map((group) => /* @__PURE__ */ jsxs("div", { className: "mb-2", children: [
117
+ sessions.length === 0 ? /* @__PURE__ */ jsx("p", { className: "px-2 py-3 text-xs text-[var(--as-muted-fg)]", children: labels?.empty ?? "No conversations yet" }) : filtered.length === 0 ? /* @__PURE__ */ jsx("p", { className: "px-2 py-3 text-xs text-[var(--as-muted-fg)]", children: labels?.noResults ?? "No matches" }) : /* @__PURE__ */ jsx("div", { role: "list", "aria-label": labels?.search ?? "Conversations", "data-as": "chat-session-rows", className: "min-h-0 flex-1 overflow-y-auto", children: groups.map((group) => /* @__PURE__ */ jsxs("div", { className: "mb-2", children: [
115
118
  group.label ? /* @__PURE__ */ jsx("p", { className: "px-2 pb-1 pt-2 text-[10px] font-semibold uppercase tracking-wide text-[var(--as-muted-fg)]", children: group.label }) : null,
116
119
  group.sessions.map((session) => {
117
120
  const isActive = session.id === activeId;
118
121
  const date = toDate(session.updatedAt);
119
- const actions = onRename || onDelete || onExport ? [
120
- onRename ? {
122
+ const isEditing = editingId === session.id;
123
+ const commitRename = () => {
124
+ if (editingId !== null && draftTitle.trim() !== "") {
125
+ onRenameCommit?.(editingId, draftTitle.trim());
126
+ }
127
+ setEditingId(null);
128
+ };
129
+ const actions = onRename || onRenameCommit || onDelete || onExport ? [
130
+ onRename || onRenameCommit ? {
121
131
  key: "rename",
122
132
  label: labels?.rename ?? "Rename",
123
133
  icon: /* @__PURE__ */ jsx(RenameIcon, { className: "size-3.5", "aria-hidden": true }),
124
- onSelect: () => onRename(session.id)
134
+ onSelect: () => {
135
+ if (onRenameCommit !== void 0) {
136
+ setEditingId(session.id);
137
+ setDraftTitle(session.title);
138
+ } else {
139
+ onRename?.(session.id);
140
+ }
141
+ }
125
142
  } : null,
126
143
  onExport ? {
127
144
  key: "export",
@@ -156,7 +173,28 @@ var ChatSessionList = React.forwardRef(
156
173
  },
157
174
  tabIndex: 0,
158
175
  children: [
159
- /* @__PURE__ */ jsx("span", { className: "line-clamp-2 min-w-0 flex-1 text-[var(--as-fg)]", children: session.title }),
176
+ isEditing ? /* @__PURE__ */ jsx(
177
+ "input",
178
+ {
179
+ autoFocus: true,
180
+ value: draftTitle,
181
+ "aria-label": labels?.renameInput ?? "Conversation title",
182
+ onChange: (event) => setDraftTitle(event.target.value),
183
+ onClick: (event) => event.stopPropagation(),
184
+ onKeyDown: (event) => {
185
+ event.stopPropagation();
186
+ if (event.key === "Enter") {
187
+ event.preventDefault();
188
+ commitRename();
189
+ } else if (event.key === "Escape") {
190
+ event.preventDefault();
191
+ setEditingId(null);
192
+ }
193
+ },
194
+ onBlur: commitRename,
195
+ className: "min-w-0 flex-1 rounded-[var(--as-radius-sm)] border border-[var(--as-primary)] bg-[var(--as-surface)] px-1.5 py-0.5 text-xs text-[var(--as-fg)] focus-visible:outline-none"
196
+ }
197
+ ) : /* @__PURE__ */ jsx("span", { className: "line-clamp-2 min-w-0 flex-1 text-[var(--as-fg)]", children: session.title }),
160
198
  date ? /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[10px] text-[var(--as-muted-fg)]", children: formatDistanceToNow(date, { addSuffix: true }) }) : null,
161
199
  actions.length > 0 ? /* @__PURE__ */ jsx(
162
200
  "span",
@@ -194,5 +232,5 @@ var ChatSessionList = React.forwardRef(
194
232
  );
195
233
 
196
234
  export { ChatSessionList };
197
- //# sourceMappingURL=chunk-PC3CE4W2.js.map
198
- //# sourceMappingURL=chunk-PC3CE4W2.js.map
235
+ //# sourceMappingURL=chunk-22MIIWC7.js.map
236
+ //# sourceMappingURL=chunk-22MIIWC7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/chat-session-list/ChatSessionList.tsx"],"names":["ChatSessionList"],"mappings":"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","file":"chunk-22MIIWC7.js","sourcesContent":["import * as React from 'react'\nimport { Download, MessageSquarePlus, Pencil, Search, Trash2 } from 'lucide-react'\nimport type { LucideIcon } from 'lucide-react'\nimport { formatDistanceToNow, isToday, isYesterday, parseISO } from 'date-fns'\n\nimport { cn } from '../../lib/utils'\nimport { searchScore } from '../../lib/fuzzy'\n\nexport interface ChatSessionView {\n id: string\n title: string\n updatedAt?: string | number | Date\n meta?: Record<string, unknown>\n}\n\nexport interface ChatSessionListLabels {\n search: string\n searchPlaceholder: string\n newChat: string\n rename: string\n renameInput: string\n delete: string\n export: string\n today: string\n yesterday: string\n earlier: string\n empty: string\n noResults: string\n}\n\nexport interface ChatSessionListIcons {\n newChat: LucideIcon\n rename: LucideIcon\n delete: LucideIcon\n export: LucideIcon\n}\n\nexport interface ChatSessionListProps {\n sessions: ChatSessionView[]\n activeId?: string | null\n onSelect: (id: string) => void\n onNew?: () => void\n onRename?: (id: string) => void\n /**\n * Inline rename: when provided, the rename action edits the title in\n * place (Enter/blur commits, Escape cancels) instead of invoking\n * `onRename` — keep `onRename` for dialog-based flows only.\n */\n onRenameCommit?: (id: string, title: string) => void\n onDelete?: (id: string) => void\n onExport?: (id: string) => void\n searchable?: boolean\n groupByDate?: boolean\n labels?: Partial<ChatSessionListLabels>\n icons?: Partial<ChatSessionListIcons>\n className?: string\n}\n\nfunction toDate(value: string | number | Date | undefined): Date | undefined {\n if (value === undefined) {\n return undefined\n }\n if (value instanceof Date) {\n return value\n }\n if (typeof value === 'number') {\n return new Date(value)\n }\n return parseISO(value)\n}\n\nfunction bucketOf(date: Date | undefined): 'today' | 'yesterday' | 'earlier' | 'none' {\n if (date === undefined) {\n return 'none'\n }\n if (isToday(date)) {\n return 'today'\n }\n if (isYesterday(date)) {\n return 'yesterday'\n }\n return 'earlier'\n}\n\n/**\n * The session list beside a full-page chat (study's `ChatSessionList` +\n * health's history overlay, generalized): fuzzy search, date grouping,\n * active marking, per-session actions (rename/delete/export) via the\n * menu when handlers are provided. All actions stay app-side.\n */\nexport const ChatSessionList = React.forwardRef<HTMLDivElement, ChatSessionListProps>(\n function ChatSessionList(\n {\n sessions,\n activeId,\n onSelect,\n onNew,\n onRename,\n onRenameCommit,\n onDelete,\n onExport,\n searchable = true,\n groupByDate = true,\n labels,\n icons,\n className,\n },\n ref,\n ) {\n const [query, setQuery] = React.useState('')\n const [editingId, setEditingId] = React.useState<string | null>(null)\n const [draftTitle, setDraftTitle] = React.useState('')\n const NewIcon = icons?.newChat ?? MessageSquarePlus\n const RenameIcon = icons?.rename ?? Pencil\n const DeleteIcon = icons?.delete ?? Trash2\n const ExportIcon = icons?.export ?? Download\n\n const filtered = React.useMemo(() => {\n const trimmed = query.trim()\n if (trimmed === '') {\n return sessions\n }\n return sessions\n .map((session) => ({\n session,\n score: searchScore(trimmed, session.title),\n }))\n .filter((entry) => entry.score !== null)\n .sort((a, b) => (b.score ?? 0) - (a.score ?? 0))\n .map((entry) => entry.session)\n }, [sessions, query])\n\n const groups = React.useMemo(() => {\n if (!groupByDate) {\n return [{ key: 'all', label: null as string | null, sessions: filtered }]\n }\n const ordered = [...filtered].sort((a, b) => {\n const dateA = toDate(a.updatedAt)?.getTime() ?? 0\n const dateB = toDate(b.updatedAt)?.getTime() ?? 0\n return dateB - dateA\n })\n const byBucket = new Map<string, ChatSessionView[]>()\n for (const session of ordered) {\n const bucket = bucketOf(toDate(session.updatedAt))\n const list = byBucket.get(bucket) ?? []\n list.push(session)\n byBucket.set(bucket, list)\n }\n const order = ['today', 'yesterday', 'earlier', 'none']\n return order\n .filter((bucket) => byBucket.has(bucket))\n .map((bucket) => ({\n key: bucket,\n label:\n bucket === 'today'\n ? (labels?.today ?? 'Today')\n : bucket === 'yesterday'\n ? (labels?.yesterday ?? 'Yesterday')\n : bucket === 'earlier'\n ? (labels?.earlier ?? 'Earlier')\n : null,\n sessions: byBucket.get(bucket) ?? [],\n }))\n }, [filtered, groupByDate, labels])\n\n return (\n <div ref={ref} data-as=\"chat-session-list\" className={cn('flex h-full min-h-0 w-full flex-col gap-2', className)}>\n <div className=\"flex items-center gap-2\">\n {searchable ? (\n <div className=\"relative min-w-0 flex-1\">\n <Search className=\"pointer-events-none absolute start-2 top-1/2 size-3.5 -translate-y-1/2 text-[var(--as-muted-fg)]\" aria-hidden />\n <input\n type=\"search\"\n role=\"searchbox\"\n value={query}\n aria-label={labels?.search ?? 'Search conversations'}\n placeholder={labels?.searchPlaceholder ?? 'Search…'}\n onChange={(event) => setQuery(event.target.value)}\n className=\"w-full rounded-[var(--as-radius)] border border-[var(--as-border)] bg-[var(--as-surface)] py-1.5 pe-2 ps-8 text-xs text-[var(--as-fg)] placeholder:text-[var(--as-muted-fg)] focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[var(--as-focus-ring)]\"\n />\n </div>\n ) : null}\n {onNew ? (\n <button\n type=\"button\"\n onClick={onNew}\n aria-label={labels?.newChat ?? 'New conversation'}\n title={labels?.newChat ?? 'New conversation'}\n className=\"flex size-7 shrink-0 items-center justify-center rounded-[var(--as-radius)] border border-[var(--as-border)] bg-[var(--as-surface)] text-[var(--as-fg)] transition-colors hover:bg-[var(--as-secondary)] focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[var(--as-focus-ring)]\"\n >\n <NewIcon className=\"size-4\" aria-hidden />\n </button>\n ) : null}\n </div>\n {sessions.length === 0 ? (\n <p className=\"px-2 py-3 text-xs text-[var(--as-muted-fg)]\">{labels?.empty ?? 'No conversations yet'}</p>\n ) : filtered.length === 0 ? (\n <p className=\"px-2 py-3 text-xs text-[var(--as-muted-fg)]\">{labels?.noResults ?? 'No matches'}</p>\n ) : (\n <div role=\"list\" aria-label={labels?.search ?? 'Conversations'} data-as=\"chat-session-rows\" className=\"min-h-0 flex-1 overflow-y-auto\">\n {groups.map((group) => (\n <div key={group.key} className=\"mb-2\">\n {group.label ? (\n <p className=\"px-2 pb-1 pt-2 text-[10px] font-semibold uppercase tracking-wide text-[var(--as-muted-fg)]\">\n {group.label}\n </p>\n ) : null}\n {group.sessions.map((session) => {\n const isActive = session.id === activeId\n const date = toDate(session.updatedAt)\n const isEditing = editingId === session.id\n const commitRename = () => {\n if (editingId !== null && draftTitle.trim() !== '') {\n onRenameCommit?.(editingId, draftTitle.trim())\n }\n setEditingId(null)\n }\n const actions =\n onRename || onRenameCommit || onDelete || onExport\n ? [\n onRename || onRenameCommit\n ? {\n key: 'rename',\n label: labels?.rename ?? 'Rename',\n icon: <RenameIcon className=\"size-3.5\" aria-hidden />,\n onSelect: () => {\n if (onRenameCommit !== undefined) {\n setEditingId(session.id)\n setDraftTitle(session.title)\n } else {\n onRename?.(session.id)\n }\n },\n }\n : null,\n onExport\n ? {\n key: 'export',\n label: labels?.export ?? 'Export',\n icon: <ExportIcon className=\"size-3.5\" aria-hidden />,\n onSelect: () => onExport(session.id),\n }\n : null,\n onDelete\n ? {\n key: 'delete',\n label: labels?.delete ?? 'Delete',\n icon: <DeleteIcon className=\"size-3.5\" aria-hidden />,\n onSelect: () => onDelete(session.id),\n danger: true,\n }\n : null,\n ].filter((item): item is NonNullable<typeof item> => item !== null)\n : []\n return (\n <div\n key={session.id}\n role=\"listitem\"\n aria-current={isActive ? 'true' : undefined}\n data-active={isActive || undefined}\n className={cn(\n 'group/session flex cursor-pointer items-center gap-2 rounded-[var(--as-radius)] px-2 py-1.5 text-xs transition-colors hover:bg-[var(--as-surface-raised)]',\n isActive && 'bg-[var(--as-surface-raised)] font-medium text-[var(--as-fg)]',\n )}\n onClick={() => onSelect(session.id)}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n onSelect(session.id)\n }\n }}\n tabIndex={0}\n >\n {isEditing ? (\n <input\n autoFocus\n value={draftTitle}\n aria-label={labels?.renameInput ?? 'Conversation title'}\n onChange={(event) => setDraftTitle(event.target.value)}\n onClick={(event) => event.stopPropagation()}\n onKeyDown={(event) => {\n event.stopPropagation()\n if (event.key === 'Enter') {\n event.preventDefault()\n commitRename()\n } else if (event.key === 'Escape') {\n event.preventDefault()\n setEditingId(null)\n }\n }}\n onBlur={commitRename}\n className=\"min-w-0 flex-1 rounded-[var(--as-radius-sm)] border border-[var(--as-primary)] bg-[var(--as-surface)] px-1.5 py-0.5 text-xs text-[var(--as-fg)] focus-visible:outline-none\"\n />\n ) : (\n <span className=\"line-clamp-2 min-w-0 flex-1 text-[var(--as-fg)]\">{session.title}</span>\n )}\n {date ? (\n <span className=\"shrink-0 text-[10px] text-[var(--as-muted-fg)]\">\n {formatDistanceToNow(date, { addSuffix: true })}\n </span>\n ) : null}\n {actions.length > 0 ? (\n <span\n role=\"group\"\n aria-label={labels?.rename ?? 'Conversation actions'}\n className=\"flex shrink-0 items-center gap-0.5 opacity-0 transition-opacity focus-within:opacity-100 group-hover/session:opacity-100\"\n onClick={(event) => event.stopPropagation()}\n onKeyDown={(event) => event.stopPropagation()}\n >\n {actions.map((action) => (\n <button\n key={action.key}\n type=\"button\"\n aria-label={action.label}\n title={action.label}\n onClick={action.onSelect}\n className={cn(\n 'rounded-[var(--as-radius-sm)] p-1 text-[var(--as-muted-fg)] transition-colors hover:bg-[var(--as-surface)] hover:text-[var(--as-fg)] focus-visible:opacity-100 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[var(--as-focus-ring)]',\n action.danger && 'hover:text-[var(--as-danger)]',\n )}\n >\n {action.icon}\n </button>\n ))}\n </span>\n ) : null}\n </div>\n )\n })}\n </div>\n ))\n }\n </div>\n )}\n </div>\n )\n },\n)\n"]}
@@ -37,13 +37,22 @@ var semanticTokens = [
37
37
  "--as-radius-sm",
38
38
  "--as-radius",
39
39
  "--as-radius-lg",
40
+ "--as-duration-fast",
41
+ "--as-duration-base",
42
+ "--as-duration-slow",
43
+ "--as-duration-spring",
44
+ "--as-ease-out",
45
+ "--as-ease-standard",
46
+ "--as-ease-spring",
40
47
  "--as-font-sans",
41
48
  "--as-font-mono",
49
+ "--as-scrollbar-thumb",
50
+ "--as-scrollbar-track",
42
51
  "--as-shadow-1",
43
52
  "--as-shadow-2",
44
53
  "--as-shadow-3"
45
54
  ];
46
55
 
47
56
  export { rawTokens, semanticTokens };
48
- //# sourceMappingURL=chunk-GDGKARRB.js.map
49
- //# sourceMappingURL=chunk-GDGKARRB.js.map
57
+ //# sourceMappingURL=chunk-5C2OLUDR.js.map
58
+ //# sourceMappingURL=chunk-5C2OLUDR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/tokens/tokens.ts"],"names":[],"mappings":";AAAO,IAAM,SAAA,GAAY;AAAA,EACvB,aAAA;AAAA,EACA,cAAA;AAAA,EACA,eAAA;AAAA,EACA,eAAA;AAAA,EACA,eAAA;AAAA,EACA,eAAA;AAAA,EACA,eAAA;AAAA,EACA,eAAA;AAAA,EACA,aAAA;AAAA,EACA,oBAAA;AAAA,EACA,cAAA;AAAA,EACA,cAAA;AAAA,EACA,aAAA;AAAA,EACA;AACF;AAEO,IAAM,cAAA,GAAiB;AAAA,EAC5B,cAAA;AAAA,EACA,iBAAA;AAAA,EACA,gBAAA;AAAA,EACA,mBAAA;AAAA,EACA,cAAA;AAAA,EACA,qBAAA;AAAA,EACA,aAAA;AAAA,EACA,SAAA;AAAA,EACA,YAAA;AAAA,EACA,eAAA;AAAA,EACA,iBAAA;AAAA,EACA,iBAAA;AAAA,EACA,gBAAA;AAAA,EACA,YAAA;AAAA,EACA,iBAAA;AAAA,EACA,cAAA;AAAA,EACA,cAAA;AAAA,EACA,gBAAA;AAAA,EACA,gBAAA;AAAA,EACA,aAAA;AAAA,EACA,gBAAA;AAAA,EACA,oBAAA;AAAA,EACA,oBAAA;AAAA,EACA,oBAAA;AAAA,EACA,sBAAA;AAAA,EACA,eAAA;AAAA,EACA,oBAAA;AAAA,EACA,kBAAA;AAAA,EACA,gBAAA;AAAA,EACA,gBAAA;AAAA,EACA,sBAAA;AAAA,EACA,sBAAA;AAAA,EACA,eAAA;AAAA,EACA,eAAA;AAAA,EACA;AACF","file":"chunk-5C2OLUDR.js","sourcesContent":["export const rawTokens = [\n '--as-tone-0',\n '--as-tone-50',\n '--as-tone-100',\n '--as-tone-200',\n '--as-tone-300',\n '--as-tone-500',\n '--as-tone-700',\n '--as-tone-900',\n '--as-accent',\n '--as-accent-strong',\n '--as-success',\n '--as-warning',\n '--as-danger',\n '--as-ai',\n] as const\n\nexport const semanticTokens = [\n '--as-primary',\n '--as-primary-fg',\n '--as-secondary',\n '--as-secondary-fg',\n '--as-surface',\n '--as-surface-raised',\n '--as-border',\n '--as-fg',\n '--as-muted',\n '--as-muted-fg',\n '--as-success-fg',\n '--as-warning-fg',\n '--as-danger-fg',\n '--as-ai-fg',\n '--as-focus-ring',\n '--as-overlay',\n '--as-z-modal',\n '--as-z-popover',\n '--as-radius-sm',\n '--as-radius',\n '--as-radius-lg',\n '--as-duration-fast',\n '--as-duration-base',\n '--as-duration-slow',\n '--as-duration-spring',\n '--as-ease-out',\n '--as-ease-standard',\n '--as-ease-spring',\n '--as-font-sans',\n '--as-font-mono',\n '--as-scrollbar-thumb',\n '--as-scrollbar-track',\n '--as-shadow-1',\n '--as-shadow-2',\n '--as-shadow-3',\n] as const\n\nexport type RawTokenName = (typeof rawTokens)[number]\nexport type SemanticTokenName = (typeof semanticTokens)[number]\nexport type ThemeTokenName = SemanticTokenName | RawTokenName\nexport type ThemeTokens = Partial<Record<ThemeTokenName, string>>\n"]}
@@ -2,7 +2,7 @@ import { DetailValue } from './chunk-JVJA5BPB.js';
2
2
  import { Spinner } from './chunk-SQHW7TYF.js';
3
3
  import { cn } from './chunk-RVVXVTH3.js';
4
4
  import * as React from 'react';
5
- import { Wrench, Check, TriangleAlert, ChevronDown } from 'lucide-react';
5
+ import { Wrench, TriangleAlert, ChevronDown } from 'lucide-react';
6
6
  import { jsxs, jsx } from 'react/jsx-runtime';
7
7
 
8
8
  function formatDuration(ms) {
@@ -64,7 +64,8 @@ var ChatToolCard = React.forwardRef(
64
64
  "data-as": "chat-tool-card",
65
65
  "data-status": status,
66
66
  className: cn(
67
- "rounded-[var(--as-radius)] border border-[var(--as-border)] bg-[var(--as-surface)] text-xs",
67
+ "rounded-[var(--as-radius)] border border-[var(--as-border)] bg-[var(--as-surface)] text-xs transition-shadow",
68
+ status === "running" && "shadow-[inset_2px_0_0_var(--as-ai)]",
68
69
  className
69
70
  ),
70
71
  children: [
@@ -85,22 +86,34 @@ var ChatToolCard = React.forwardRef(
85
86
  {
86
87
  role: status === "running" ? "status" : void 0,
87
88
  className: cn(
88
- "flex shrink-0 items-center gap-1",
89
+ "as-anim-fade-swap flex shrink-0 items-center gap-1",
89
90
  status === "done" && "text-[var(--as-success)]",
90
91
  status === "failed" && "text-[var(--as-danger)]",
91
92
  status === "running" && "text-[var(--as-muted-fg)]"
92
93
  ),
93
94
  children: [
94
- status === "running" ? /* @__PURE__ */ jsx(Spinner, { size: "sm" }) : status === "done" ? /* @__PURE__ */ jsx(Check, { className: "size-3.5", "aria-hidden": true }) : /* @__PURE__ */ jsx(TriangleAlert, { className: "size-3.5", "aria-hidden": true }),
95
+ status === "running" ? /* @__PURE__ */ jsx(Spinner, { size: "sm" }) : status === "done" ? /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", "aria-hidden": true, className: "size-3.5", fill: "none", children: /* @__PURE__ */ jsx(
96
+ "path",
97
+ {
98
+ d: "M20 6 9 17l-5-5",
99
+ pathLength: 1,
100
+ className: "as-anim-check-draw",
101
+ stroke: "currentColor",
102
+ strokeWidth: 2,
103
+ strokeLinecap: "round",
104
+ strokeLinejoin: "round"
105
+ }
106
+ ) }) : /* @__PURE__ */ jsx(TriangleAlert, { className: "size-3.5", "aria-hidden": true }),
95
107
  /* @__PURE__ */ jsx("span", { className: "sr-only", children: statusLabel })
96
108
  ]
97
- }
109
+ },
110
+ status
98
111
  ),
99
- expandable ? /* @__PURE__ */ jsx(ChevronDown, { className: cn("size-3.5 shrink-0 text-[var(--as-muted-fg)] transition-transform", open && "rotate-180"), "aria-hidden": true }) : null
112
+ expandable ? /* @__PURE__ */ jsx(ChevronDown, { className: cn("size-3.5 shrink-0 text-[var(--as-muted-fg)] transition-transform duration-[var(--as-duration-base)] ease-[var(--as-ease-out)]", open && "rotate-180"), "aria-hidden": true }) : null
100
113
  ]
101
114
  }
102
115
  ),
103
- expandable && open ? /* @__PURE__ */ jsxs("div", { id: regionId, className: "flex flex-col gap-2 border-t border-[var(--as-border)] px-2.5 py-2", children: [
116
+ expandable ? /* @__PURE__ */ jsx("div", { id: regionId, "data-open": open, className: "as-collapse", children: /* @__PURE__ */ jsxs("div", { className: "as-collapse-inner flex flex-col gap-2 border-t border-[var(--as-border)] px-2.5 py-2", children: [
104
117
  args ? /* @__PURE__ */ jsxs("div", { children: [
105
118
  /* @__PURE__ */ jsx("p", { className: "mb-1 font-medium uppercase tracking-wide text-[var(--as-muted-fg)]", children: labels?.args ?? "Arguments" }),
106
119
  /* @__PURE__ */ jsx(DetailValue, { text: args })
@@ -109,7 +122,7 @@ var ChatToolCard = React.forwardRef(
109
122
  /* @__PURE__ */ jsx("p", { className: "mb-1 font-medium uppercase tracking-wide text-[var(--as-muted-fg)]", children: labels?.result ?? "Result" }),
110
123
  renderResult ? renderResult(result ?? "") : /* @__PURE__ */ jsx(DetailValue, { text: result ?? "" })
111
124
  ] }) : null
112
- ] }) : null
125
+ ] }) }) : null
113
126
  ]
114
127
  }
115
128
  );
@@ -117,5 +130,5 @@ var ChatToolCard = React.forwardRef(
117
130
  );
118
131
 
119
132
  export { ChatToolCard };
120
- //# sourceMappingURL=chunk-5BJ4VULP.js.map
121
- //# sourceMappingURL=chunk-5BJ4VULP.js.map
133
+ //# sourceMappingURL=chunk-EQPXM2R4.js.map
134
+ //# sourceMappingURL=chunk-EQPXM2R4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/chat-tool-card/ChatToolCard.tsx"],"names":["ChatToolCard"],"mappings":"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","file":"chunk-EQPXM2R4.js","sourcesContent":["import * as React from 'react'\nimport { ChevronDown, TriangleAlert, Wrench } from 'lucide-react'\nimport type { LucideIcon } from 'lucide-react'\n\nimport { cn } from '../../lib/utils'\nimport { Spinner } from '../spinner/Spinner'\nimport { DetailValue } from '../detail-value/DetailValue'\n\nexport interface ChatToolCardLabels {\n running: string\n done: string\n failed: string\n args: string\n result: string\n details: string\n}\n\nexport interface ChatToolCardProps {\n name: string\n /** Human title, e.g. \"Searching the job catalog\". */\n title?: string\n status: 'running' | 'done' | 'failed'\n /** Serialized arguments (JSON string or formatted text). */\n args?: string\n /** Serialized result. */\n result?: string\n /**\n * Custom result view (study's per-tool views, family plan 12 L2).\n * Called with the result string (possibly empty when the tool has no\n * serialized output); when absent the default `<pre>` pane renders.\n */\n renderResult?: (result: string) => React.ReactNode\n durationMs?: number\n open?: boolean\n defaultOpen?: boolean\n onOpenChange?: (open: boolean) => void\n labels?: Partial<ChatToolCardLabels>\n icon?: LucideIcon\n className?: string\n}\n\nfunction formatDuration(ms: number): string {\n if (ms < 1000) {\n return `${Math.round(ms)} ms`\n }\n return `${(ms / 1000).toFixed(1)} s`\n}\n\n/**\n * Inline tool-call observation card (study's `ToolCallCard` + health's tool\n * chips, generalized): status, duration, expandable args/result. The\n * inspector stays app-side (health opens its own modal from `onOpenChange`\n * or wrapping markup).\n */\nexport const ChatToolCard = React.forwardRef<HTMLDivElement, ChatToolCardProps>(\n function ChatToolCard(\n {\n name,\n title,\n status,\n args,\n result,\n renderResult,\n durationMs,\n open: openProp,\n defaultOpen = false,\n onOpenChange,\n labels,\n icon: Icon = Wrench,\n className,\n },\n ref,\n ) {\n const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen)\n const isControlled = openProp !== undefined\n const open = isControlled ? openProp : uncontrolledOpen\n const regionId = React.useId()\n const expandable = Boolean(args || result || renderResult)\n\n const toggle = () => {\n if (!expandable) {\n return\n }\n const next = !open\n if (!isControlled) {\n setUncontrolledOpen(next)\n }\n onOpenChange?.(next)\n }\n\n const onKeyDown: React.KeyboardEventHandler<HTMLButtonElement> = (event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n toggle()\n }\n }\n\n const statusLabel =\n status === 'running' ? (labels?.running ?? 'Running') : status === 'done' ? (labels?.done ?? 'Done') : (labels?.failed ?? 'Failed')\n\n const Header = (expandable ? 'button' : 'div') as 'button'\n const headerProps: React.ComponentPropsWithoutRef<'button'> = expandable\n ? {\n type: 'button',\n 'aria-expanded': open,\n 'aria-controls': regionId,\n onClick: toggle,\n onKeyDown,\n }\n : {}\n\n return (\n <div\n ref={ref}\n data-as=\"chat-tool-card\"\n data-status={status}\n className={cn(\n 'rounded-[var(--as-radius)] border border-[var(--as-border)] bg-[var(--as-surface)] text-xs transition-shadow',\n status === 'running' && 'shadow-[inset_2px_0_0_var(--as-ai)]',\n className,\n )}\n >\n <Header\n {...headerProps}\n className=\"flex w-full items-center gap-2 px-2.5 py-1.5 text-left focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--as-focus-ring)]\"\n >\n <Icon className=\"size-3.5 shrink-0 text-[var(--as-muted-fg)]\" aria-hidden />\n <span className=\"min-w-0 flex-1\">\n <span className=\"font-medium text-[var(--as-fg)]\">{title ?? name}</span>\n {title ? <span className=\"ml-1.5 font-mono text-[var(--as-muted-fg)]\">{name}</span> : null}\n </span>\n {durationMs !== undefined ? (\n <span className=\"shrink-0 tabular-nums text-[var(--as-muted-fg)]\">{formatDuration(durationMs)}</span>\n ) : null}\n <span\n key={status}\n role={status === 'running' ? 'status' : undefined}\n className={cn(\n 'as-anim-fade-swap flex shrink-0 items-center gap-1',\n status === 'done' && 'text-[var(--as-success)]',\n status === 'failed' && 'text-[var(--as-danger)]',\n status === 'running' && 'text-[var(--as-muted-fg)]',\n )}\n >\n {status === 'running' ? (\n <Spinner size=\"sm\" />\n ) : status === 'done' ? (\n <svg viewBox=\"0 0 24 24\" aria-hidden className=\"size-3.5\" fill=\"none\">\n <path\n d=\"M20 6 9 17l-5-5\"\n pathLength={1}\n className=\"as-anim-check-draw\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n ) : (\n <TriangleAlert className=\"size-3.5\" aria-hidden />\n )}\n <span className=\"sr-only\">{statusLabel}</span>\n </span>\n {expandable ? (\n <ChevronDown className={cn('size-3.5 shrink-0 text-[var(--as-muted-fg)] transition-transform duration-[var(--as-duration-base)] ease-[var(--as-ease-out)]', open && 'rotate-180')} aria-hidden />\n ) : null}\n </Header>\n {expandable ? (\n <div id={regionId} data-open={open} className=\"as-collapse\">\n <div className=\"as-collapse-inner flex flex-col gap-2 border-t border-[var(--as-border)] px-2.5 py-2\">\n {args ? (\n <div>\n <p className=\"mb-1 font-medium uppercase tracking-wide text-[var(--as-muted-fg)]\">{labels?.args ?? 'Arguments'}</p>\n <DetailValue text={args} />\n </div>\n ) : null}\n {result || renderResult ? (\n <div>\n <p className=\"mb-1 font-medium uppercase tracking-wide text-[var(--as-muted-fg)]\">{labels?.result ?? 'Result'}</p>\n {renderResult ? (\n renderResult(result ?? '')\n ) : (\n <DetailValue text={result ?? ''} />\n )}\n </div>\n ) : null}\n </div>\n </div>\n ) : null}\n </div>\n )\n },\n)\n"]}
@@ -0,0 +1,43 @@
1
+ import { ChatTurnStatus } from './chunk-GIQZNEG5.js';
2
+ import { cn } from './chunk-RVVXVTH3.js';
3
+ import * as React from 'react';
4
+ import { jsxs, jsx } from 'react/jsx-runtime';
5
+
6
+ var ChatPending = React.forwardRef(
7
+ function ChatPending2({ label = "thinking", startedAt, lines = 2, children, className }, ref) {
8
+ const count = Math.min(Math.max(Math.round(lines), 1), 6);
9
+ return /* @__PURE__ */ jsxs(
10
+ "div",
11
+ {
12
+ ref,
13
+ "data-as": "chat-pending",
14
+ className: cn("flex w-full max-w-[92%] flex-col gap-1.5", className),
15
+ children: [
16
+ /* @__PURE__ */ jsx(ChatTurnStatus, { label, startedAt }),
17
+ /* @__PURE__ */ jsx(
18
+ "div",
19
+ {
20
+ "aria-hidden": true,
21
+ className: "flex flex-col gap-2 rounded-[var(--as-radius)] rounded-bl-[var(--as-radius-sm)] border border-[var(--as-border)] bg-[var(--as-surface)] px-3 py-2.5 shadow-[inset_2px_0_0_var(--as-ai)]",
22
+ children: Array.from({ length: count }, (_, index) => /* @__PURE__ */ jsx(
23
+ "div",
24
+ {
25
+ className: cn(
26
+ "as-anim-shimmer h-3.5 rounded-[var(--as-radius-sm)] bg-[var(--as-muted)]",
27
+ index === count - 1 && count > 1 && "w-3/5"
28
+ )
29
+ },
30
+ index
31
+ ))
32
+ }
33
+ ),
34
+ children
35
+ ]
36
+ }
37
+ );
38
+ }
39
+ );
40
+
41
+ export { ChatPending };
42
+ //# sourceMappingURL=chunk-F4BTJQLK.js.map
43
+ //# sourceMappingURL=chunk-F4BTJQLK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/chat-pending/ChatPending.tsx"],"names":["ChatPending"],"mappings":";;;;;AA0BO,IAAM,WAAA,GAAoB,KAAA,CAAA,UAAA;AAAA,EAC/B,SAASA,YAAAA,CAAY,EAAE,KAAA,GAAQ,UAAA,EAAY,SAAA,EAAW,KAAA,GAAQ,CAAA,EAAG,QAAA,EAAU,SAAA,EAAU,EAAG,GAAA,EAAK;AAC3F,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,KAAA,CAAM,KAAK,CAAA,EAAG,CAAC,CAAA,EAAG,CAAC,CAAA;AACxD,IAAA,uBACE,IAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,GAAA;AAAA,QACA,SAAA,EAAQ,cAAA;AAAA,QACR,SAAA,EAAW,EAAA,CAAG,0CAAA,EAA4C,SAAS,CAAA;AAAA,QAEnE,QAAA,EAAA;AAAA,0BAAA,GAAA,CAAC,cAAA,EAAA,EAAe,OAAc,SAAA,EAAsB,CAAA;AAAA,0BACpD,GAAA;AAAA,YAAC,KAAA;AAAA,YAAA;AAAA,cACC,aAAA,EAAW,IAAA;AAAA,cACX,SAAA,EAAU,yLAAA;AAAA,cAET,QAAA,EAAA,KAAA,CAAM,KAAK,EAAE,MAAA,EAAQ,OAAM,EAAG,CAAC,GAAG,KAAA,qBACjC,GAAA;AAAA,gBAAC,KAAA;AAAA,gBAAA;AAAA,kBAEC,SAAA,EAAW,EAAA;AAAA,oBACT,0EAAA;AAAA,oBACA,KAAA,KAAU,KAAA,GAAQ,CAAA,IAAK,KAAA,GAAQ,CAAA,IAAK;AAAA;AACtC,iBAAA;AAAA,gBAJK;AAAA,eAMR;AAAA;AAAA,WACH;AAAA,UACC;AAAA;AAAA;AAAA,KACH;AAAA,EAEJ;AACF","file":"chunk-F4BTJQLK.js","sourcesContent":["import * as React from 'react'\n\nimport { cn } from '../../lib/utils'\nimport { ChatTurnStatus } from '../chat-turn-status/ChatTurnStatus'\n\nexport interface ChatPendingProps {\n /**\n * Phase label narrated while the first token is awaited, e.g.\n * \"thinking\" — usually the live turn's current node label.\n */\n label?: string\n /** Turn start timestamp (ms) — enables the elapsed timer. */\n startedAt?: number\n /** Placeholder line count. Default 2 (capped at 6). */\n lines?: number\n /** Extra node under the placeholder (model badge, app hints). */\n children?: React.ReactNode\n className?: string\n}\n\n/**\n * The first-token latency placeholder (plan 100 S2): narrated phase row\n * (dots + elapsed timer via `ChatTurnStatus`) over an assistant-aligned\n * shimmer placeholder tinted with the `--as-ai` accent. Render it as the\n * transcript's `live` tail between send and the first delta.\n */\nexport const ChatPending = React.forwardRef<HTMLDivElement, ChatPendingProps>(\n function ChatPending({ label = 'thinking', startedAt, lines = 2, children, className }, ref) {\n const count = Math.min(Math.max(Math.round(lines), 1), 6)\n return (\n <div\n ref={ref}\n data-as=\"chat-pending\"\n className={cn('flex w-full max-w-[92%] flex-col gap-1.5', className)}\n >\n <ChatTurnStatus label={label} startedAt={startedAt} />\n <div\n aria-hidden\n className=\"flex flex-col gap-2 rounded-[var(--as-radius)] rounded-bl-[var(--as-radius-sm)] border border-[var(--as-border)] bg-[var(--as-surface)] px-3 py-2.5 shadow-[inset_2px_0_0_var(--as-ai)]\"\n >\n {Array.from({ length: count }, (_, index) => (\n <div\n key={index}\n className={cn(\n 'as-anim-shimmer h-3.5 rounded-[var(--as-radius-sm)] bg-[var(--as-muted)]',\n index === count - 1 && count > 1 && 'w-3/5',\n )}\n />\n ))}\n </div>\n {children}\n </div>\n )\n },\n)\n"]}
@@ -43,7 +43,7 @@ var ChatTurnStatus = React.forwardRef(
43
43
  },
44
44
  index
45
45
  )) }),
46
- /* @__PURE__ */ jsx("span", { className: "text-xs", children: label }),
46
+ /* @__PURE__ */ jsx("span", { className: "as-anim-fade-swap text-xs", children: label }, label),
47
47
  elapsed !== null ? /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "font-mono text-[10px] tabular-nums", children: formatElapsed(elapsed) }) : null,
48
48
  labels?.timer ? /* @__PURE__ */ jsx("span", { className: "sr-only", children: labels.timer }) : null
49
49
  ]
@@ -53,5 +53,5 @@ var ChatTurnStatus = React.forwardRef(
53
53
  );
54
54
 
55
55
  export { ChatTurnStatus };
56
- //# sourceMappingURL=chunk-TQDTFPM4.js.map
57
- //# sourceMappingURL=chunk-TQDTFPM4.js.map
56
+ //# sourceMappingURL=chunk-GIQZNEG5.js.map
57
+ //# sourceMappingURL=chunk-GIQZNEG5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/chat-turn-status/ChatTurnStatus.tsx"],"names":["ChatTurnStatus"],"mappings":";;;;AAqBA,SAAS,cAAc,EAAA,EAAoB;AACzC,EAAA,IAAI,KAAK,GAAA,EAAM;AACb,IAAA,OAAO,CAAA,EAAG,KAAK,GAAA,CAAI,CAAA,EAAG,KAAK,KAAA,CAAM,EAAE,CAAC,CAAC,CAAA,GAAA,CAAA;AAAA,EACvC;AACA,EAAA,OAAO,CAAA,EAAA,CAAI,KAAK,GAAA,EAAM,OAAA,CAAQ,KAAK,GAAA,GAAQ,CAAA,GAAI,CAAC,CAAC,CAAA,EAAA,CAAA;AACnD;AAQO,IAAM,cAAA,GAAuB,KAAA,CAAA,UAAA;AAAA,EAClC,SAASA,eAAAA,CACP,EAAE,KAAA,EAAO,SAAA,EAAW,OAAA,GAAU,KAAA,EAAO,MAAA,GAAS,GAAA,EAAK,MAAA,EAAQ,SAAA,EAAU,EACrE,GAAA,EACA;AACA,IAAA,MAAM,CAAC,KAAK,MAAM,CAAA,GAAU,eAAS,MAAM,IAAA,CAAK,KAAK,CAAA;AAErD,IAAM,gBAAU,MAAM;AACpB,MAAA,IAAI,cAAc,MAAA,EAAW;AAC3B,QAAA;AAAA,MACF;AACA,MAAA,MAAA,CAAO,IAAA,CAAK,KAAK,CAAA;AACjB,MAAA,MAAM,EAAA,GAAK,OAAO,WAAA,CAAY,MAAM,OAAO,IAAA,CAAK,GAAA,EAAK,CAAA,EAAG,MAAM,CAAA;AAC9D,MAAA,OAAO,MAAM,MAAA,CAAO,aAAA,CAAc,EAAE,CAAA;AAAA,IACtC,CAAA,EAAG,CAAC,SAAA,EAAW,MAAM,CAAC,CAAA;AAEtB,IAAA,MAAM,OAAA,GAAU,cAAc,MAAA,GAAY,IAAA,GAAO,KAAK,GAAA,CAAI,CAAA,EAAG,MAAM,SAAS,CAAA;AAE5E,IAAA,uBACE,IAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,GAAA;AAAA,QACA,IAAA,EAAK,QAAA;AAAA,QACL,YAAA,EAAY,KAAA;AAAA,QACZ,SAAA,EAAQ,kBAAA;AAAA,QACR,cAAA,EAAc,OAAA;AAAA,QACd,SAAA,EAAW,EAAA;AAAA,UACT,mDAAA;AAAA,UACA,YAAY,MAAA,IACV,uGAAA;AAAA,UACF,YAAY,KAAA,IAAS,aAAA;AAAA,UACrB;AAAA,SACF;AAAA,QAEA,QAAA,EAAA;AAAA,0BAAA,GAAA,CAAC,MAAA,EAAA,EAAK,aAAA,EAAW,IAAA,EAAC,SAAA,EAAU,yBAAA,EACzB,QAAA,EAAA,CAAC,CAAA,EAAG,CAAA,EAAG,CAAC,CAAA,CAAE,GAAA,CAAI,CAAC,KAAA,qBACd,GAAA;AAAA,YAAC,MAAA;AAAA,YAAA;AAAA,cAEC,SAAA,EAAU,sDAAA;AAAA,cACV,OAAO,EAAE,cAAA,EAAgB,CAAA,EAAG,KAAA,GAAQ,IAAI,CAAA,CAAA,CAAA;AAAI,aAAA;AAAA,YAFvC;AAAA,WAIR,CAAA,EACH,CAAA;AAAA,0BACA,GAAA,CAAC,MAAA,EAAA,EAAiB,SAAA,EAAU,2BAAA,EAA6B,mBAA9C,KAAoD,CAAA;AAAA,UAC9D,OAAA,KAAY,IAAA,mBACX,GAAA,CAAC,MAAA,EAAA,EAAK,aAAA,EAAW,IAAA,EAAC,SAAA,EAAU,oCAAA,EACzB,QAAA,EAAA,aAAA,CAAc,OAAO,CAAA,EACxB,CAAA,GACE,IAAA;AAAA,UACH,MAAA,EAAQ,wBAAQ,GAAA,CAAC,MAAA,EAAA,EAAK,WAAU,SAAA,EAAW,QAAA,EAAA,MAAA,CAAO,OAAM,CAAA,GAAU;AAAA;AAAA;AAAA,KACrE;AAAA,EAEJ;AACF","file":"chunk-GIQZNEG5.js","sourcesContent":["import * as React from 'react'\n\nimport { cn } from '../../lib/utils'\n\nexport interface ChatTurnStatusLabels {\n timer: string\n}\n\nexport interface ChatTurnStatusProps {\n /** Current turn phase, e.g. \"searching the catalog\". */\n label: string\n /** Turn start timestamp (ms). Presence enables the elapsed timer. */\n startedAt?: number\n /** `row` = inline pre-text line; `card` = soft surface block. */\n variant?: 'row' | 'card'\n /** Elapsed-timer refresh interval. Default 100 ms (study-proven). */\n tickMs?: number\n labels?: Partial<ChatTurnStatusLabels>\n className?: string\n}\n\nfunction formatElapsed(ms: number): string {\n if (ms < 1000) {\n return `${Math.max(0, Math.round(ms))} ms`\n }\n return `${(ms / 1000).toFixed(ms < 10000 ? 1 : 0)} s`\n}\n\n/**\n * Live pre-text turn status (study's `TurnTraceStatus` + `ThinkingDots`,\n * generalized for family plan 12 L1): animated dots + phase label, with\n * an optional elapsed timer. Presentational and controlled — the turn\n * state itself lives in `useChatStream`; this only narrates it.\n */\nexport const ChatTurnStatus = React.forwardRef<HTMLDivElement, ChatTurnStatusProps>(\n function ChatTurnStatus(\n { label, startedAt, variant = 'row', tickMs = 100, labels, className },\n ref,\n ) {\n const [now, setNow] = React.useState(() => Date.now())\n\n React.useEffect(() => {\n if (startedAt === undefined) {\n return\n }\n setNow(Date.now())\n const id = window.setInterval(() => setNow(Date.now()), tickMs)\n return () => window.clearInterval(id)\n }, [startedAt, tickMs])\n\n const elapsed = startedAt === undefined ? null : Math.max(0, now - startedAt)\n\n return (\n <div\n ref={ref}\n role=\"status\"\n aria-label={label}\n data-as=\"chat-turn-status\"\n data-variant={variant}\n className={cn(\n 'flex items-center gap-2 text-[var(--as-muted-fg)]',\n variant === 'card' &&\n 'rounded-[var(--as-radius)] border border-[var(--as-border)] bg-[var(--as-surface-raised)] px-3 py-2.5',\n variant === 'row' && 'px-1 py-0.5',\n className,\n )}\n >\n <span aria-hidden className=\"flex items-center gap-1\">\n {[0, 1, 2].map((index) => (\n <span\n key={index}\n className=\"as-dot size-1.5 rounded-full bg-[var(--as-muted-fg)]\"\n style={{ animationDelay: `${index * 0.16}s` }}\n />\n ))}\n </span>\n <span key={label} className=\"as-anim-fade-swap text-xs\">{label}</span>\n {elapsed !== null ? (\n <span aria-hidden className=\"font-mono text-[10px] tabular-nums\">\n {formatElapsed(elapsed)}\n </span>\n ) : null}\n {labels?.timer ? <span className=\"sr-only\">{labels.timer}</span> : null}\n </div>\n )\n },\n)\n"]}
@@ -25,6 +25,12 @@ var ChatLauncher = React.forwardRef(
25
25
  const isControlled = openProp !== void 0;
26
26
  const open = isControlled ? openProp : uncontrolledOpen;
27
27
  const panelRef = React.useRef(null);
28
+ const prevOpenRef = React.useRef(open);
29
+ const [morphed, setMorphed] = React.useState(false);
30
+ if (prevOpenRef.current !== open) {
31
+ prevOpenRef.current = open;
32
+ setMorphed(true);
33
+ }
28
34
  const setOpen = (next) => {
29
35
  if (!isControlled) {
30
36
  setUncontrolledOpen(next);
@@ -89,8 +95,15 @@ var ChatLauncher = React.forwardRef(
89
95
  className
90
96
  ),
91
97
  children: [
92
- /* @__PURE__ */ jsx(Icon, { className: "size-5", "aria-hidden": true }),
93
- showBadge ? /* @__PURE__ */ jsx("span", { className: "absolute -right-0.5 -top-0.5 flex h-5 min-w-5 items-center justify-center rounded-full border-2 border-[var(--as-surface)] bg-[var(--as-danger)] px-1 text-[10px] font-semibold text-[var(--as-danger-fg)]", children: badge }) : null
98
+ open ? /* @__PURE__ */ jsx(CloseIcon, { className: "as-anim-morph-in size-5", "aria-hidden": true }) : /* @__PURE__ */ jsx(Icon, { className: cn("size-5", morphed && "as-anim-morph-in"), "aria-hidden": true }),
99
+ showBadge ? /* @__PURE__ */ jsx(
100
+ "span",
101
+ {
102
+ className: "as-anim-pulse-once [--as-pulse-color:var(--as-danger)] absolute -right-0.5 -top-0.5 flex h-5 min-w-5 items-center justify-center rounded-full border-2 border-[var(--as-surface)] bg-[var(--as-danger)] px-1 text-[10px] font-semibold text-[var(--as-danger-fg)]",
103
+ children: badge
104
+ },
105
+ String(badge)
106
+ ) : null
94
107
  ]
95
108
  }
96
109
  )
@@ -99,5 +112,5 @@ var ChatLauncher = React.forwardRef(
99
112
  );
100
113
 
101
114
  export { ChatLauncher };
102
- //# sourceMappingURL=chunk-E3NZM4U7.js.map
103
- //# sourceMappingURL=chunk-E3NZM4U7.js.map
115
+ //# sourceMappingURL=chunk-GO4ILLS6.js.map
116
+ //# sourceMappingURL=chunk-GO4ILLS6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/chat-launcher/ChatLauncher.tsx"],"names":["ChatLauncher"],"mappings":"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","file":"chunk-GO4ILLS6.js","sourcesContent":["import * as React from 'react'\nimport { MessageCircle, X } from 'lucide-react'\nimport type { LucideIcon } from 'lucide-react'\n\nimport { cn } from '../../lib/utils'\nimport { Portal } from '../portal/Portal'\n\nexport interface ChatLauncherProps {\n /** The anchored panel body — usually `ChatPanel variant=\"bubble\"`. */\n panel: React.ReactNode\n open?: boolean\n defaultOpen?: boolean\n onOpenChange?: (open: boolean) => void\n position?: 'bottom-right' | 'bottom-left'\n label?: string\n closeLabel?: string\n icon?: LucideIcon\n closeIcon?: LucideIcon\n /** Unread / activity badge. */\n badge?: number | string\n /**\n * Render the floating close button pinned to the panel's top-right\n * corner (default). Turn this off when the panel body renders its own\n * header actions — an overlaid close intercepts clicks meant for\n * top-right header buttons; compose the close into those actions\n * (calling `onOpenChange(false)`) instead.\n */\n showClose?: boolean\n container?: HTMLElement | null\n panelClassName?: string\n className?: string\n}\n\n/**\n * The floating chat bubble (career's `ChatWidget` launcher,\n * family-standard): fixed launcher button opening an anchored, non-modal\n * panel. `aria-expanded` wiring, Escape closes while focus is inside.\n */\nexport const ChatLauncher = React.forwardRef<HTMLButtonElement, ChatLauncherProps>(\n function ChatLauncher(\n {\n panel,\n open: openProp,\n defaultOpen = false,\n onOpenChange,\n position = 'bottom-right',\n label = 'Chat',\n closeLabel = 'Close chat',\n icon: Icon = MessageCircle,\n closeIcon: CloseIcon = X,\n badge,\n showClose = true,\n container,\n panelClassName,\n className,\n },\n ref,\n ) {\n const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen)\n const isControlled = openProp !== undefined\n const open = isControlled ? openProp : uncontrolledOpen\n const panelRef = React.useRef<HTMLDivElement | null>(null)\n const prevOpenRef = React.useRef(open)\n const [morphed, setMorphed] = React.useState(false)\n if (prevOpenRef.current !== open) {\n prevOpenRef.current = open\n setMorphed(true)\n }\n\n const setOpen = (next: boolean) => {\n if (!isControlled) {\n setUncontrolledOpen(next)\n }\n onOpenChange?.(next)\n }\n\n React.useEffect(() => {\n if (!open) {\n return\n }\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key === 'Escape' && panelRef.current?.contains(document.activeElement)) {\n setOpen(false)\n }\n }\n document.addEventListener('keydown', onKeyDown)\n return () => document.removeEventListener('keydown', onKeyDown)\n })\n\n const showBadge = badge !== undefined && badge !== 0 && badge !== ''\n\n return (\n <>\n {open ? (\n <Portal container={container}>\n <div\n ref={panelRef}\n data-as=\"chat-launcher-panel\"\n className={cn(\n 'as-anim-pop fixed bottom-20 z-[var(--as-z-modal)] flex h-[min(36rem,calc(100dvh-11rem))] w-[min(26rem,calc(100vw-2rem))] flex-col overflow-hidden rounded-[var(--as-radius-lg)] border border-[var(--as-border)] bg-[var(--as-surface)] text-[var(--as-fg)] shadow-[var(--as-shadow-3)]',\n position === 'bottom-right' ? 'right-4' : 'left-4',\n panelClassName,\n )}\n role=\"complementary\"\n aria-label={label}\n >\n {panel}\n {showClose ? (\n <button\n type=\"button\"\n aria-label={closeLabel}\n onClick={() => setOpen(false)}\n className=\"absolute right-2 top-2 z-10 rounded-[var(--as-radius-sm)] p-1 text-[var(--as-muted-fg)] transition-colors hover:bg-[var(--as-secondary)] hover:text-[var(--as-fg)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--as-focus-ring)]\"\n >\n <CloseIcon aria-hidden className=\"size-4\" />\n </button>\n ) : null}\n </div>\n </Portal>\n ) : null}\n <button\n ref={ref}\n type=\"button\"\n aria-expanded={open}\n aria-label={open ? closeLabel : label}\n title={open ? closeLabel : label}\n onClick={() => setOpen(!open)}\n data-as=\"chat-launcher\"\n data-open={open || undefined}\n className={cn(\n 'fixed bottom-4 z-[var(--as-z-popover)] flex size-12 items-center justify-center rounded-full bg-[var(--as-primary)] text-[var(--as-primary-fg)] shadow-[var(--as-shadow-2)] transition-transform hover:scale-105 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--as-focus-ring)]',\n position === 'bottom-right' ? 'right-4' : 'left-4',\n className,\n )}\n >\n {open ? (\n <CloseIcon className=\"as-anim-morph-in size-5\" aria-hidden />\n ) : (\n <Icon className={cn('size-5', morphed && 'as-anim-morph-in')} aria-hidden />\n )}\n {showBadge ? (\n <span\n key={String(badge)}\n className=\"as-anim-pulse-once [--as-pulse-color:var(--as-danger)] absolute -right-0.5 -top-0.5 flex h-5 min-w-5 items-center justify-center rounded-full border-2 border-[var(--as-surface)] bg-[var(--as-danger)] px-1 text-[10px] font-semibold text-[var(--as-danger-fg)]\"\n >\n {badge}\n </span>\n ) : null}\n </button>\n </>\n )\n },\n)\n"]}