@neuronection/assistant-ui 0.52.0 → 0.53.1
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/CHANGELOG.md +101 -0
- package/README.md +4 -1
- package/dist/chat-branch-tree.d.ts +15 -2
- package/dist/chat-composer.d.ts +43 -3
- package/dist/chat-composer.js +1 -1
- package/dist/chat-core.d.ts +54 -4
- package/dist/chat-launcher.js +1 -1
- package/dist/chat-markdown.d.ts +8 -1
- package/dist/chat-markdown.js +1 -1
- package/dist/chat-message.d.ts +16 -1
- package/dist/chat-message.js +1 -1
- package/dist/chat-panel.js +1 -1
- package/dist/chat-pending.d.ts +25 -0
- package/dist/chat-pending.js +3 -0
- package/dist/chat-pending.js.map +1 -0
- package/dist/chat-reasoning.d.ts +10 -2
- package/dist/chat-reasoning.js +1 -1
- package/dist/chat-session-list.d.ts +7 -0
- package/dist/chat-session-list.js +1 -1
- package/dist/chat-suggestion-card.d.ts +19 -0
- package/dist/chat-suggestion-card.js +3 -0
- package/dist/chat-suggestion-card.js.map +1 -0
- package/dist/chat-tool-card.js +1 -1
- package/dist/chat-transcript.d.ts +81 -8
- package/dist/chat-transcript.js +1 -1
- package/dist/chat-turn-status.js +1 -1
- package/dist/chat-welcome.d.ts +23 -0
- package/dist/chat-welcome.js +3 -0
- package/dist/chat-welcome.js.map +1 -0
- package/dist/{chunk-PC3CE4W2.js → chunk-22MIIWC7.js} +45 -7
- package/dist/chunk-22MIIWC7.js.map +1 -0
- package/dist/chunk-56QSIK4Y.js +264 -0
- package/dist/chunk-56QSIK4Y.js.map +1 -0
- package/dist/{chunk-GDGKARRB.js → chunk-5C2OLUDR.js} +11 -2
- package/dist/chunk-5C2OLUDR.js.map +1 -0
- package/dist/chunk-E2NNTHFB.js +479 -0
- package/dist/chunk-E2NNTHFB.js.map +1 -0
- package/dist/{chunk-5BJ4VULP.js → chunk-EQPXM2R4.js} +23 -10
- package/dist/chunk-EQPXM2R4.js.map +1 -0
- package/dist/chunk-F4BTJQLK.js +43 -0
- package/dist/chunk-F4BTJQLK.js.map +1 -0
- package/dist/{chunk-TQDTFPM4.js → chunk-GIQZNEG5.js} +3 -3
- package/dist/chunk-GIQZNEG5.js.map +1 -0
- package/dist/{chunk-E3NZM4U7.js → chunk-GO4ILLS6.js} +17 -4
- package/dist/chunk-GO4ILLS6.js.map +1 -0
- package/dist/{chunk-5U45TQJE.js → chunk-I5H4KYEZ.js} +3 -3
- package/dist/{chunk-5U45TQJE.js.map → chunk-I5H4KYEZ.js.map} +1 -1
- package/dist/{chunk-S63SYV6O.js → chunk-INXQJM2K.js} +27 -6
- package/dist/chunk-INXQJM2K.js.map +1 -0
- package/dist/{chunk-HQ3X5SVR.js → chunk-KLCULTOQ.js} +105 -16
- package/dist/chunk-KLCULTOQ.js.map +1 -0
- package/dist/chunk-M4SYVPC4.js +3 -0
- package/dist/chunk-M4SYVPC4.js.map +1 -0
- package/dist/chunk-OOQO6VGO.js +33 -0
- package/dist/chunk-OOQO6VGO.js.map +1 -0
- package/dist/chunk-P3QUNCBR.js +366 -0
- package/dist/chunk-P3QUNCBR.js.map +1 -0
- package/dist/chunk-PABMR4RA.js +30 -0
- package/dist/chunk-PABMR4RA.js.map +1 -0
- package/dist/{chunk-SEZZVAGP.js → chunk-SR4IN564.js} +10 -3
- package/dist/chunk-SR4IN564.js.map +1 -0
- package/dist/{chunk-MFM4W6ZO.js → chunk-TLFEVL42.js} +3 -3
- package/dist/{chunk-MFM4W6ZO.js.map → chunk-TLFEVL42.js.map} +1 -1
- package/dist/index.d.ts +243 -36
- package/dist/index.js +40 -37
- package/dist/markdown-diff-view.js +1 -1
- package/dist/model-registry.js +1 -1
- package/dist/styles.css +1 -1
- package/dist/theme-scope.d.ts +1 -1
- package/dist/tokens.d.ts +1 -1
- package/dist/tokens.js +1 -1
- package/dist/{types-LhE1KFBf.d.ts → types-CxieoN2u.d.ts} +1 -1
- package/dist/types-pivJdPC8.d.ts +14 -0
- package/package.json +13 -1
- package/dist/chunk-5BJ4VULP.js.map +0 -1
- package/dist/chunk-C5JZCCU7.js +0 -220
- package/dist/chunk-C5JZCCU7.js.map +0 -1
- package/dist/chunk-E3NZM4U7.js.map +0 -1
- package/dist/chunk-GDGKARRB.js.map +0 -1
- package/dist/chunk-HQ3X5SVR.js.map +0 -1
- package/dist/chunk-PC3CE4W2.js.map +0 -1
- package/dist/chunk-S63SYV6O.js.map +0 -1
- package/dist/chunk-SEZZVAGP.js.map +0 -1
- package/dist/chunk-TQDTFPM4.js.map +0 -1
- package/dist/chunk-XOODPYJ3.js +0 -173
- package/dist/chunk-XOODPYJ3.js.map +0 -1
- package/dist/chunk-YQQZMWHL.js +0 -201
- package/dist/chunk-YQQZMWHL.js.map +0 -1
- package/dist/tree-4Hv9stjF.d.ts +0 -54
|
@@ -1,17 +1,60 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
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
|
-
|
|
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
|
|
29
|
-
*
|
|
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 };
|
package/dist/chat-transcript.js
CHANGED
package/dist/chat-turn-status.js
CHANGED
|
@@ -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 @@
|
|
|
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
|
|
120
|
-
|
|
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: () =>
|
|
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(
|
|
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-
|
|
198
|
-
//# sourceMappingURL=chunk-
|
|
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"]}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { Button } from './chunk-4WBBF5LH.js';
|
|
2
|
+
import { cn } from './chunk-RVVXVTH3.js';
|
|
3
|
+
import * as React3 from 'react';
|
|
4
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
5
|
+
import { ChevronLeft, ChevronRight, Check, Copy, Pencil, RotateCcw, TriangleAlert } from 'lucide-react';
|
|
6
|
+
|
|
7
|
+
var ChatMessageEditor = React3.forwardRef(
|
|
8
|
+
function ChatMessageEditor2({ value, onValueChange, onSubmit, onCancel, submitDisabled = false, rows = 3, labels, className }, ref) {
|
|
9
|
+
const onKeyDown = (event) => {
|
|
10
|
+
if (event.key === "Escape") {
|
|
11
|
+
event.preventDefault();
|
|
12
|
+
onCancel();
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) {
|
|
16
|
+
event.preventDefault();
|
|
17
|
+
if (!submitDisabled) {
|
|
18
|
+
onSubmit();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
return /* @__PURE__ */ jsxs("div", { ref, "data-as": "chat-message-editor", className: cn("as-anim-slide-up flex w-full flex-col gap-2", className), children: [
|
|
23
|
+
/* @__PURE__ */ jsx(
|
|
24
|
+
"textarea",
|
|
25
|
+
{
|
|
26
|
+
autoFocus: true,
|
|
27
|
+
value,
|
|
28
|
+
rows,
|
|
29
|
+
"aria-label": labels?.ariaLabel ?? "Edit message",
|
|
30
|
+
onChange: (event) => onValueChange(event.target.value),
|
|
31
|
+
onKeyDown,
|
|
32
|
+
className: "w-full resize-y rounded-[var(--as-radius)] border border-[var(--as-border)] bg-[var(--as-surface)] px-3 py-2 text-sm text-[var(--as-fg)] placeholder:text-[var(--as-muted-fg)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--as-focus-ring)]"
|
|
33
|
+
}
|
|
34
|
+
),
|
|
35
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-2", children: [
|
|
36
|
+
/* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: onCancel, children: labels?.cancel ?? "Cancel" }),
|
|
37
|
+
/* @__PURE__ */ jsx(Button, { size: "sm", disabled: submitDisabled, onClick: onSubmit, children: labels?.save ?? "Save & resend" })
|
|
38
|
+
] })
|
|
39
|
+
] });
|
|
40
|
+
}
|
|
41
|
+
);
|
|
42
|
+
var MessageVariantSwitcher = React3.forwardRef(
|
|
43
|
+
function MessageVariantSwitcher2({ variants, onSelect, labels, className }, ref) {
|
|
44
|
+
const { index, count, siblingIds } = variants;
|
|
45
|
+
const previousId = index > 1 ? siblingIds[index - 2] : void 0;
|
|
46
|
+
const nextId = index < count ? siblingIds[index] : void 0;
|
|
47
|
+
const select = (event, messageId) => {
|
|
48
|
+
event.stopPropagation();
|
|
49
|
+
if (messageId !== void 0) {
|
|
50
|
+
onSelect(messageId);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
return /* @__PURE__ */ jsxs(
|
|
54
|
+
"div",
|
|
55
|
+
{
|
|
56
|
+
ref,
|
|
57
|
+
role: "group",
|
|
58
|
+
"aria-label": labels?.group ?? "Message variants",
|
|
59
|
+
"data-as": "message-variant-switcher",
|
|
60
|
+
className: cn("flex items-center gap-0.5 text-xs text-[var(--as-muted-fg)]", className),
|
|
61
|
+
children: [
|
|
62
|
+
/* @__PURE__ */ jsx(
|
|
63
|
+
"button",
|
|
64
|
+
{
|
|
65
|
+
type: "button",
|
|
66
|
+
disabled: previousId === void 0,
|
|
67
|
+
"aria-label": labels?.previous ?? "Previous variant",
|
|
68
|
+
title: labels?.previous ?? "Previous variant",
|
|
69
|
+
onClick: (event) => select(event, previousId),
|
|
70
|
+
className: "rounded-[var(--as-radius-sm)] p-0.5 transition-colors hover:bg-[var(--as-surface-raised)] hover:text-[var(--as-fg)] disabled:pointer-events-none disabled:opacity-30 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[var(--as-focus-ring)]",
|
|
71
|
+
children: /* @__PURE__ */ jsx(ChevronLeft, { className: "size-3.5", "aria-hidden": true })
|
|
72
|
+
}
|
|
73
|
+
),
|
|
74
|
+
/* @__PURE__ */ jsxs("span", { className: "tabular-nums", title: labels?.variantOf?.(index, count) ?? `Variant ${index} of ${count}`, children: [
|
|
75
|
+
index,
|
|
76
|
+
"/",
|
|
77
|
+
count
|
|
78
|
+
] }),
|
|
79
|
+
/* @__PURE__ */ jsx(
|
|
80
|
+
"button",
|
|
81
|
+
{
|
|
82
|
+
type: "button",
|
|
83
|
+
disabled: nextId === void 0,
|
|
84
|
+
"aria-label": labels?.next ?? "Next variant",
|
|
85
|
+
title: labels?.next ?? "Next variant",
|
|
86
|
+
onClick: (event) => select(event, nextId),
|
|
87
|
+
className: "rounded-[var(--as-radius-sm)] p-0.5 transition-colors hover:bg-[var(--as-surface-raised)] hover:text-[var(--as-fg)] disabled:pointer-events-none disabled:opacity-30 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[var(--as-focus-ring)]",
|
|
88
|
+
children: /* @__PURE__ */ jsx(ChevronRight, { className: "size-3.5", "aria-hidden": true })
|
|
89
|
+
}
|
|
90
|
+
)
|
|
91
|
+
]
|
|
92
|
+
}
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
);
|
|
96
|
+
var actionButtonClass = "rounded-[var(--as-radius-sm)] p-1 text-[var(--as-muted-fg)] transition-colors hover:bg-[var(--as-surface-raised)] 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)]";
|
|
97
|
+
var COPIED_FEEDBACK_MS = 1500;
|
|
98
|
+
var ChatMessage = React3.forwardRef(
|
|
99
|
+
function ChatMessage2({
|
|
100
|
+
role,
|
|
101
|
+
content,
|
|
102
|
+
reasoning,
|
|
103
|
+
children,
|
|
104
|
+
status = "done",
|
|
105
|
+
error,
|
|
106
|
+
actions,
|
|
107
|
+
editing = false,
|
|
108
|
+
variants,
|
|
109
|
+
onSelectVariant,
|
|
110
|
+
meta,
|
|
111
|
+
chips,
|
|
112
|
+
attachments,
|
|
113
|
+
avatar,
|
|
114
|
+
design = "bubbles",
|
|
115
|
+
labels,
|
|
116
|
+
compact = false,
|
|
117
|
+
className
|
|
118
|
+
}, ref) {
|
|
119
|
+
const isUser = role === "user";
|
|
120
|
+
const isSystem = role === "system";
|
|
121
|
+
const showActions = actions !== void 0 && (actions.onCopy !== void 0 || actions.onEdit !== void 0 || actions.onRegenerate !== void 0 || (actions.extras?.length ?? 0) > 0);
|
|
122
|
+
const editorLabels = { save: labels?.save, cancel: labels?.cancel, ariaLabel: labels?.ariaLabel };
|
|
123
|
+
const switcherLabels = { group: labels?.group, variantOf: labels?.variantOf, previous: labels?.previous, next: labels?.next };
|
|
124
|
+
const [copiedAt, setCopiedAt] = React3.useState(null);
|
|
125
|
+
const copied = actions?.copied ?? copiedAt !== null;
|
|
126
|
+
React3.useEffect(() => {
|
|
127
|
+
if (copiedAt === null) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const id = window.setTimeout(() => setCopiedAt(null), COPIED_FEEDBACK_MS);
|
|
131
|
+
return () => window.clearTimeout(id);
|
|
132
|
+
}, [copiedAt]);
|
|
133
|
+
const onCopyClick = () => {
|
|
134
|
+
actions?.onCopy?.();
|
|
135
|
+
if (actions?.copied === void 0) {
|
|
136
|
+
setCopiedAt(Date.now());
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
if (isSystem) {
|
|
140
|
+
return /* @__PURE__ */ jsx(
|
|
141
|
+
"div",
|
|
142
|
+
{
|
|
143
|
+
ref,
|
|
144
|
+
"data-as": "chat-message",
|
|
145
|
+
"data-role": role,
|
|
146
|
+
className: cn("flex justify-center", className),
|
|
147
|
+
children: /* @__PURE__ */ jsx("div", { className: "rounded-full border border-[var(--as-border)] bg-[var(--as-surface)] px-3 py-1 text-xs text-[var(--as-muted-fg)]", children: content })
|
|
148
|
+
}
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
return /* @__PURE__ */ jsxs(
|
|
152
|
+
"div",
|
|
153
|
+
{
|
|
154
|
+
ref,
|
|
155
|
+
"data-as": "chat-message",
|
|
156
|
+
"data-role": role,
|
|
157
|
+
"data-status": status,
|
|
158
|
+
"data-design": design,
|
|
159
|
+
className: cn("as-anim-message-in group/message flex w-full flex-col", isUser ? "items-end" : "items-start", className),
|
|
160
|
+
children: [
|
|
161
|
+
attachments ? /* @__PURE__ */ jsx("div", { className: "mb-1.5 flex w-full flex-wrap justify-end gap-1.5", children: attachments }) : null,
|
|
162
|
+
(() => {
|
|
163
|
+
const bubble = /* @__PURE__ */ jsxs(
|
|
164
|
+
"div",
|
|
165
|
+
{
|
|
166
|
+
className: cn(
|
|
167
|
+
"relative flex max-w-[92%] flex-col gap-1.5 rounded-[var(--as-radius)] px-3 py-2 text-sm",
|
|
168
|
+
compact && "px-2.5 py-1.5",
|
|
169
|
+
isUser ? "rounded-br-[var(--as-radius-sm)] bg-[var(--as-primary)] font-medium text-[var(--as-primary-fg)]" : design === "flat" ? "max-w-full rounded-bl-[var(--as-radius-sm)] bg-transparent px-0 py-0" : "rounded-bl-[var(--as-radius-sm)] bg-[var(--as-muted)] text-[var(--as-fg)]"
|
|
170
|
+
),
|
|
171
|
+
children: [
|
|
172
|
+
reasoning ? /* @__PURE__ */ jsx("div", { className: "not-prose", children: reasoning }) : null,
|
|
173
|
+
editing ? /* @__PURE__ */ jsx(
|
|
174
|
+
ChatMessageEditor,
|
|
175
|
+
{
|
|
176
|
+
value: editing.value,
|
|
177
|
+
onValueChange: editing.onValueChange,
|
|
178
|
+
onSubmit: editing.onSubmit,
|
|
179
|
+
onCancel: editing.onCancel,
|
|
180
|
+
submitDisabled: editing.submitDisabled,
|
|
181
|
+
labels: editorLabels
|
|
182
|
+
}
|
|
183
|
+
) : /* @__PURE__ */ jsx(
|
|
184
|
+
"div",
|
|
185
|
+
{
|
|
186
|
+
className: cn("min-w-0", variants && variants.count > 1 && "as-anim-fade-swap"),
|
|
187
|
+
children: content
|
|
188
|
+
},
|
|
189
|
+
variants && variants.count > 1 ? `variant-${variants.index}` : void 0
|
|
190
|
+
),
|
|
191
|
+
status === "interrupted" ? /* @__PURE__ */ jsx("span", { className: "as-anim-slide-up as-anim-pulse-once [--as-pulse-color:var(--as-warning)] self-start rounded-full bg-[var(--as-warning)]/15 px-2 py-0.5 text-[10px] font-medium uppercase tracking-wide text-[var(--as-warning)]", children: labels?.interrupted ?? "Stopped" }) : null,
|
|
192
|
+
status === "error" && error ? /* @__PURE__ */ jsxs(
|
|
193
|
+
"div",
|
|
194
|
+
{
|
|
195
|
+
role: "alert",
|
|
196
|
+
className: "as-anim-slide-up flex flex-col gap-1 rounded-[var(--as-radius)] border border-[var(--as-danger)]/40 bg-[var(--as-danger)]/10 px-2.5 py-1.5 text-xs text-[var(--as-danger)]",
|
|
197
|
+
children: [
|
|
198
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono font-semibold uppercase tracking-wide", children: error.code }),
|
|
199
|
+
/* @__PURE__ */ jsx("span", { className: "whitespace-pre-wrap break-words", children: error.message }),
|
|
200
|
+
error.retryable && actions?.onRetry ? /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-1.5", children: [
|
|
201
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", className: "self-start", onClick: actions.onRetry, children: labels?.retry ?? "Retry" }),
|
|
202
|
+
actions?.errorExtra
|
|
203
|
+
] }) : actions?.errorExtra ? /* @__PURE__ */ jsx("div", { className: "self-start", children: actions.errorExtra }) : null
|
|
204
|
+
]
|
|
205
|
+
}
|
|
206
|
+
) : null,
|
|
207
|
+
chips ? /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: chips }) : null,
|
|
208
|
+
children ? /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-1.5", children }) : null,
|
|
209
|
+
showActions && !editing ? /* @__PURE__ */ jsxs(
|
|
210
|
+
"div",
|
|
211
|
+
{
|
|
212
|
+
"data-as": "chat-message-actions",
|
|
213
|
+
className: cn(
|
|
214
|
+
"absolute top-1 flex items-center gap-0.5 rounded-[var(--as-radius)] border border-[var(--as-border)] bg-[var(--as-surface)] p-0.5 shadow-[var(--as-shadow-1)]",
|
|
215
|
+
isUser ? "-left-9" : "-right-9"
|
|
216
|
+
),
|
|
217
|
+
children: [
|
|
218
|
+
actions?.onCopy ? /* @__PURE__ */ jsx(
|
|
219
|
+
"button",
|
|
220
|
+
{
|
|
221
|
+
type: "button",
|
|
222
|
+
className: actionButtonClass,
|
|
223
|
+
"aria-label": copied ? labels?.copied ?? "Copied" : labels?.copy ?? "Copy",
|
|
224
|
+
title: copied ? labels?.copied ?? "Copied" : labels?.copy ?? "Copy",
|
|
225
|
+
onClick: onCopyClick,
|
|
226
|
+
children: copied ? /* @__PURE__ */ jsx(Check, { className: "as-anim-fade-swap size-3.5 text-[var(--as-success)]", "aria-hidden": true }) : /* @__PURE__ */ jsx(Copy, { className: "size-3.5", "aria-hidden": true })
|
|
227
|
+
}
|
|
228
|
+
) : null,
|
|
229
|
+
actions?.onEdit ? /* @__PURE__ */ jsx("button", { type: "button", className: actionButtonClass, "aria-label": labels?.edit ?? "Edit", title: labels?.edit ?? "Edit", onClick: actions.onEdit, children: /* @__PURE__ */ jsx(Pencil, { className: "size-3.5", "aria-hidden": true }) }) : null,
|
|
230
|
+
actions?.onRegenerate ? /* @__PURE__ */ jsx("button", { type: "button", className: actionButtonClass, "aria-label": labels?.regenerate ?? "Regenerate", title: labels?.regenerate ?? "Regenerate", onClick: actions.onRegenerate, children: /* @__PURE__ */ jsx(RotateCcw, { className: "size-3.5", "aria-hidden": true }) }) : null,
|
|
231
|
+
actions?.extras?.map((extra) => /* @__PURE__ */ jsx(
|
|
232
|
+
"button",
|
|
233
|
+
{
|
|
234
|
+
type: "button",
|
|
235
|
+
className: cn(actionButtonClass, extra.danger && "hover:text-[var(--as-danger)]"),
|
|
236
|
+
"aria-label": extra.label,
|
|
237
|
+
title: extra.label,
|
|
238
|
+
onClick: extra.onClick,
|
|
239
|
+
children: extra.icon ? /* @__PURE__ */ jsx(extra.icon, { className: "size-3.5", "aria-hidden": true }) : /* @__PURE__ */ jsx(TriangleAlert, { className: "size-3.5", "aria-hidden": true })
|
|
240
|
+
},
|
|
241
|
+
extra.key
|
|
242
|
+
))
|
|
243
|
+
]
|
|
244
|
+
}
|
|
245
|
+
) : null
|
|
246
|
+
]
|
|
247
|
+
}
|
|
248
|
+
);
|
|
249
|
+
return avatar !== void 0 ? /* @__PURE__ */ jsxs("div", { className: cn("flex w-full items-end gap-2", isUser ? "flex-row-reverse" : "flex-row"), children: [
|
|
250
|
+
/* @__PURE__ */ jsx("div", { "data-as": "chat-message-avatar", className: "shrink-0 pb-1", children: avatar }),
|
|
251
|
+
bubble
|
|
252
|
+
] }) : bubble;
|
|
253
|
+
})(),
|
|
254
|
+
meta ? /* @__PURE__ */ jsx("div", { className: "mt-0.5 flex flex-wrap items-center gap-1.5 px-1 text-[10px] text-[var(--as-muted-fg)]", children: meta }) : null,
|
|
255
|
+
variants && variants.count > 1 && onSelectVariant ? /* @__PURE__ */ jsx(MessageVariantSwitcher, { variants, onSelect: onSelectVariant, labels: switcherLabels, className: "mt-0.5 px-1" }) : null
|
|
256
|
+
]
|
|
257
|
+
}
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
export { ChatMessage, ChatMessageEditor, MessageVariantSwitcher };
|
|
263
|
+
//# sourceMappingURL=chunk-56QSIK4Y.js.map
|
|
264
|
+
//# sourceMappingURL=chunk-56QSIK4Y.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/chat-message/ChatMessageEditor.tsx","../src/components/chat-message/MessageVariantSwitcher.tsx","../src/components/chat-message/ChatMessage.tsx"],"names":["React","ChatMessageEditor","React2","MessageVariantSwitcher","jsxs","jsx","ChatMessage"],"mappings":"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","file":"chunk-56QSIK4Y.js","sourcesContent":["import * as React from 'react'\n\nimport { cn } from '../../lib/utils'\nimport { Button } from '../button/Button'\n\nexport interface ChatMessageEditorLabels {\n save: string\n cancel: string\n ariaLabel: string\n}\n\nexport interface ChatMessageEditorProps {\n value: string\n onValueChange: (value: string) => void\n /** Save & resend — branches the tree (family `edit` endpoint) and reruns the turn. */\n onSubmit: () => void\n onCancel: () => void\n submitDisabled?: boolean\n rows?: number\n labels?: Partial<ChatMessageEditorLabels>\n className?: string\n}\n\n/**\n * Inline user-prompt editor for the edit-and-resend flow (study pattern):\n * controlled textarea, Cmd/Ctrl+Enter saves, Escape cancels.\n */\nexport const ChatMessageEditor = React.forwardRef<HTMLDivElement, ChatMessageEditorProps>(\n function ChatMessageEditor(\n { value, onValueChange, onSubmit, onCancel, submitDisabled = false, rows = 3, labels, className },\n ref,\n ) {\n const onKeyDown: React.KeyboardEventHandler<HTMLTextAreaElement> = (event) => {\n if (event.key === 'Escape') {\n event.preventDefault()\n onCancel()\n return\n }\n if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {\n event.preventDefault()\n if (!submitDisabled) {\n onSubmit()\n }\n }\n }\n\n return (\n <div ref={ref} data-as=\"chat-message-editor\" className={cn('as-anim-slide-up flex w-full flex-col gap-2', className)}>\n <textarea\n autoFocus\n value={value}\n rows={rows}\n aria-label={labels?.ariaLabel ?? 'Edit message'}\n onChange={(event) => onValueChange(event.target.value)}\n onKeyDown={onKeyDown}\n className=\"w-full resize-y rounded-[var(--as-radius)] border border-[var(--as-border)] bg-[var(--as-surface)] px-3 py-2 text-sm text-[var(--as-fg)] placeholder:text-[var(--as-muted-fg)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--as-focus-ring)]\"\n />\n <div className=\"flex items-center justify-end gap-2\">\n <Button variant=\"ghost\" size=\"sm\" onClick={onCancel}>\n {labels?.cancel ?? 'Cancel'}\n </Button>\n <Button size=\"sm\" disabled={submitDisabled} onClick={onSubmit}>\n {labels?.save ?? 'Save & resend'}\n </Button>\n </div>\n </div>\n )\n },\n)\n","import * as React from 'react'\nimport { ChevronLeft, ChevronRight } from 'lucide-react'\n\nimport type { ChatMessageVariants } from '../chat-core/types'\nimport { cn } from '../../lib/utils'\n\nexport interface MessageVariantSwitcherLabels {\n group: string\n variantOf: (index: number, count: number) => string\n previous: string\n next: string\n}\n\nexport interface MessageVariantSwitcherProps {\n variants: ChatMessageVariants\n /** Flip the parent's active-child pointer to the sibling id (family `select` endpoint). */\n onSelect: (messageId: string) => void\n labels?: Partial<MessageVariantSwitcherLabels>\n className?: string\n}\n\n/**\n * The `‹ n/N ›` sibling-variant switcher under a branched message\n * (study's inline switcher, family branching contract).\n */\nexport const MessageVariantSwitcher = React.forwardRef<HTMLDivElement, MessageVariantSwitcherProps>(\n function MessageVariantSwitcher({ variants, onSelect, labels, className }, ref) {\n const { index, count, siblingIds } = variants\n const previousId = index > 1 ? siblingIds[index - 2] : undefined\n const nextId = index < count ? siblingIds[index] : undefined\n\n const select = (event: React.MouseEvent<HTMLButtonElement>, messageId: string | undefined) => {\n event.stopPropagation()\n if (messageId !== undefined) {\n onSelect(messageId)\n }\n }\n\n return (\n <div\n ref={ref}\n role=\"group\"\n aria-label={labels?.group ?? 'Message variants'}\n data-as=\"message-variant-switcher\"\n className={cn('flex items-center gap-0.5 text-xs text-[var(--as-muted-fg)]', className)}\n >\n <button\n type=\"button\"\n disabled={previousId === undefined}\n aria-label={labels?.previous ?? 'Previous variant'}\n title={labels?.previous ?? 'Previous variant'}\n onClick={(event) => select(event, previousId)}\n className=\"rounded-[var(--as-radius-sm)] p-0.5 transition-colors hover:bg-[var(--as-surface-raised)] hover:text-[var(--as-fg)] disabled:pointer-events-none disabled:opacity-30 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[var(--as-focus-ring)]\"\n >\n <ChevronLeft className=\"size-3.5\" aria-hidden />\n </button>\n <span className=\"tabular-nums\" title={labels?.variantOf?.(index, count) ?? `Variant ${index} of ${count}`}>\n {index}/{count}\n </span>\n <button\n type=\"button\"\n disabled={nextId === undefined}\n aria-label={labels?.next ?? 'Next variant'}\n title={labels?.next ?? 'Next variant'}\n onClick={(event) => select(event, nextId)}\n className=\"rounded-[var(--as-radius-sm)] p-0.5 transition-colors hover:bg-[var(--as-surface-raised)] hover:text-[var(--as-fg)] disabled:pointer-events-none disabled:opacity-30 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[var(--as-focus-ring)]\"\n >\n <ChevronRight className=\"size-3.5\" aria-hidden />\n </button>\n </div>\n )\n },\n)\n","import * as React from 'react'\nimport { Check, Copy, Pencil, RotateCcw, TriangleAlert } from 'lucide-react'\nimport type { LucideIcon } from 'lucide-react'\n\nimport { cn } from '../../lib/utils'\nimport { Button } from '../button/Button'\nimport type { ChatError, ChatMessageVariants, ChatRole } from '../chat-core/types'\nimport { ChatMessageEditor, type ChatMessageEditorLabels } from './ChatMessageEditor'\nimport { MessageVariantSwitcher, type MessageVariantSwitcherLabels } from './MessageVariantSwitcher'\n\nexport interface ChatMessageAction {\n key: string\n label: string\n icon?: LucideIcon\n onClick: () => void\n danger?: boolean\n}\n\nexport interface ChatMessageActions {\n onCopy?: () => void\n /**\n * Controlled copied feedback for the copy action (check morph).\n * Uncontrolled by default: the library shows the check for 1.5 s\n * after each copy click.\n */\n copied?: boolean\n onEdit?: () => void\n onRegenerate?: () => void\n onRetry?: () => void\n /** Extra icon actions (app-specific). */\n extras?: ChatMessageAction[]\n /**\n * Extra node rendered inside the error card next to the Retry button —\n * e.g. an \"Open AI settings\" link when the error is a not-configured\n * failure. Rendered only while the error card is visible.\n */\n errorExtra?: React.ReactNode\n}\n\nexport interface ChatMessageLabels {\n copy: string\n copied: string\n edit: string\n regenerate: string\n retry: string\n interrupted: string\n error: string\n}\n\nexport interface ChatMessageProps {\n role: ChatRole\n /** Usually a `MarkdownSurface`; any node graph renders here. */\n content: React.ReactNode\n /** Thinking block slot (usually `ChatReasoning`). */\n reasoning?: React.ReactNode\n /** Tool cards / HITL cards below the content. */\n children?: React.ReactNode\n status?: 'streaming' | 'done' | 'error' | 'interrupted'\n error?: ChatError\n actions?: ChatMessageActions\n /** Edit-and-resend mode for user messages (renders `ChatMessageEditor`). */\n editing?: false | { value: string; onValueChange: (value: string) => void; onSubmit: () => void; onCancel: () => void; submitDisabled?: boolean }\n variants?: ChatMessageVariants\n onSelectVariant?: (messageId: string) => void\n /** Badge row (model, latency, tokens — app-defined). */\n meta?: React.ReactNode\n /** Deep-link chip row (career references, health citations). */\n chips?: React.ReactNode\n /** Attachment rail above the content. */\n attachments?: React.ReactNode\n /** Leading identity node beside the bubble (user: trailing). Render group-start avatars only when grouping transcripts. */\n avatar?: React.ReactNode\n /**\n * Visual design: `bubbles` (default) — paired role bubbles; `flat` —\n * assistant content renders as prose on the panel surface (user keeps\n * its bubble). Reasoning/tool cards are unaffected.\n */\n design?: 'bubbles' | 'flat'\n labels?: Partial<ChatMessageLabels & ChatMessageEditorLabels & MessageVariantSwitcherLabels>\n compact?: boolean\n className?: string\n}\n\nconst actionButtonClass =\n 'rounded-[var(--as-radius-sm)] p-1 text-[var(--as-muted-fg)] transition-colors hover:bg-[var(--as-surface-raised)] 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\nconst COPIED_FEEDBACK_MS = 1500\n\n/**\n * The family chat bubble: role-aligned, hover action row (copy / edit /\n * regenerate + app extras), variant switcher under branched messages,\n * reasoning and below-content slots, inline edit-and-resend, error +\n * interrupted states. Presentational — state lives app-side.\n */\nexport const ChatMessage = React.forwardRef<HTMLDivElement, ChatMessageProps>(\n function ChatMessage(\n {\n role,\n content,\n reasoning,\n children,\n status = 'done',\n error,\n actions,\n editing = false,\n variants,\n onSelectVariant,\n meta,\n chips,\n attachments,\n avatar,\n design = 'bubbles',\n labels,\n compact = false,\n className,\n },\n ref,\n ) {\n const isUser = role === 'user'\n const isSystem = role === 'system'\n const showActions =\n actions !== undefined && (actions.onCopy !== undefined || actions.onEdit !== undefined || actions.onRegenerate !== undefined || (actions.extras?.length ?? 0) > 0)\n const editorLabels = { save: labels?.save, cancel: labels?.cancel, ariaLabel: labels?.ariaLabel }\n const switcherLabels = { group: labels?.group, variantOf: labels?.variantOf, previous: labels?.previous, next: labels?.next }\n const [copiedAt, setCopiedAt] = React.useState<number | null>(null)\n const copied = actions?.copied ?? copiedAt !== null\n\n React.useEffect(() => {\n if (copiedAt === null) {\n return\n }\n const id = window.setTimeout(() => setCopiedAt(null), COPIED_FEEDBACK_MS)\n return () => window.clearTimeout(id)\n }, [copiedAt])\n\n const onCopyClick = () => {\n actions?.onCopy?.()\n if (actions?.copied === undefined) {\n setCopiedAt(Date.now())\n }\n }\n\n if (isSystem) {\n return (\n <div\n ref={ref}\n data-as=\"chat-message\"\n data-role={role}\n className={cn('flex justify-center', className)}\n >\n <div className=\"rounded-full border border-[var(--as-border)] bg-[var(--as-surface)] px-3 py-1 text-xs text-[var(--as-muted-fg)]\">\n {content}\n </div>\n </div>\n )\n }\n\n return (\n <div\n ref={ref}\n data-as=\"chat-message\"\n data-role={role}\n data-status={status}\n data-design={design}\n className={cn('as-anim-message-in group/message flex w-full flex-col', isUser ? 'items-end' : 'items-start', className)}\n >\n {attachments ? <div className=\"mb-1.5 flex w-full flex-wrap justify-end gap-1.5\">{attachments}</div> : null}\n {(() => {\n const bubble = (\n <div\n className={cn(\n 'relative flex max-w-[92%] flex-col gap-1.5 rounded-[var(--as-radius)] px-3 py-2 text-sm',\n compact && 'px-2.5 py-1.5',\n isUser\n ? 'rounded-br-[var(--as-radius-sm)] bg-[var(--as-primary)] font-medium text-[var(--as-primary-fg)]'\n : design === 'flat'\n ? 'max-w-full rounded-bl-[var(--as-radius-sm)] bg-transparent px-0 py-0'\n : 'rounded-bl-[var(--as-radius-sm)] bg-[var(--as-muted)] text-[var(--as-fg)]',\n )}\n >\n {reasoning ? <div className=\"not-prose\">{reasoning}</div> : null}\n {editing ? (\n <ChatMessageEditor\n value={editing.value}\n onValueChange={editing.onValueChange}\n onSubmit={editing.onSubmit}\n onCancel={editing.onCancel}\n submitDisabled={editing.submitDisabled}\n labels={editorLabels}\n />\n ) : (\n <div\n key={variants && variants.count > 1 ? `variant-${variants.index}` : undefined}\n className={cn('min-w-0', variants && variants.count > 1 && 'as-anim-fade-swap')}\n >\n {content}\n </div>\n )}\n {status === 'interrupted' ? (\n <span className=\"as-anim-slide-up as-anim-pulse-once [--as-pulse-color:var(--as-warning)] self-start rounded-full bg-[var(--as-warning)]/15 px-2 py-0.5 text-[10px] font-medium uppercase tracking-wide text-[var(--as-warning)]\">\n {labels?.interrupted ?? 'Stopped'}\n </span>\n ) : null}\n {status === 'error' && error ? (\n <div\n role=\"alert\"\n className=\"as-anim-slide-up flex flex-col gap-1 rounded-[var(--as-radius)] border border-[var(--as-danger)]/40 bg-[var(--as-danger)]/10 px-2.5 py-1.5 text-xs text-[var(--as-danger)]\"\n >\n <span className=\"font-mono font-semibold uppercase tracking-wide\">{error.code}</span>\n <span className=\"whitespace-pre-wrap break-words\">{error.message}</span>\n {error.retryable && actions?.onRetry ? (\n <div className=\"flex flex-wrap items-center gap-1.5\">\n <Button variant=\"outline\" size=\"sm\" className=\"self-start\" onClick={actions.onRetry}>\n {labels?.retry ?? 'Retry'}\n </Button>\n {actions?.errorExtra}\n </div>\n ) : actions?.errorExtra ? (\n <div className=\"self-start\">{actions.errorExtra}</div>\n ) : null}\n </div>\n ) : null}\n {chips ? <div className=\"flex flex-wrap gap-1.5\">{chips}</div> : null}\n {children ? <div className=\"flex flex-col gap-1.5\">{children}</div> : null}\n {showActions && !editing ? (\n <div\n data-as=\"chat-message-actions\"\n className={cn(\n 'absolute top-1 flex items-center gap-0.5 rounded-[var(--as-radius)] border border-[var(--as-border)] bg-[var(--as-surface)] p-0.5 shadow-[var(--as-shadow-1)]',\n isUser ? '-left-9' : '-right-9',\n )}\n >\n {actions?.onCopy ? (\n <button\n type=\"button\"\n className={actionButtonClass}\n aria-label={copied ? (labels?.copied ?? 'Copied') : (labels?.copy ?? 'Copy')}\n title={copied ? (labels?.copied ?? 'Copied') : (labels?.copy ?? 'Copy')}\n onClick={onCopyClick}\n >\n {copied ? (\n <Check className=\"as-anim-fade-swap size-3.5 text-[var(--as-success)]\" aria-hidden />\n ) : (\n <Copy className=\"size-3.5\" aria-hidden />\n )}\n </button>\n ) : null}\n {actions?.onEdit ? (\n <button type=\"button\" className={actionButtonClass} aria-label={labels?.edit ?? 'Edit'} title={labels?.edit ?? 'Edit'} onClick={actions.onEdit}>\n <Pencil className=\"size-3.5\" aria-hidden />\n </button>\n ) : null}\n {actions?.onRegenerate ? (\n <button type=\"button\" className={actionButtonClass} aria-label={labels?.regenerate ?? 'Regenerate'} title={labels?.regenerate ?? 'Regenerate'} onClick={actions.onRegenerate}>\n <RotateCcw className=\"size-3.5\" aria-hidden />\n </button>\n ) : null}\n {actions?.extras?.map((extra) => (\n <button\n key={extra.key}\n type=\"button\"\n className={cn(actionButtonClass, extra.danger && 'hover:text-[var(--as-danger)]')}\n aria-label={extra.label}\n title={extra.label}\n onClick={extra.onClick}\n >\n {extra.icon ? <extra.icon className=\"size-3.5\" aria-hidden /> : <TriangleAlert className=\"size-3.5\" aria-hidden />}\n </button>\n ))}\n </div>\n ) : null}\n </div>\n )\n return avatar !== undefined ? (\n <div className={cn('flex w-full items-end gap-2', isUser ? 'flex-row-reverse' : 'flex-row')}>\n <div data-as=\"chat-message-avatar\" className=\"shrink-0 pb-1\">\n {avatar}\n </div>\n {bubble}\n </div>\n ) : (\n bubble\n )\n })()}\n {meta ? <div className=\"mt-0.5 flex flex-wrap items-center gap-1.5 px-1 text-[10px] text-[var(--as-muted-fg)]\">{meta}</div> : null}\n {variants && variants.count > 1 && onSelectVariant ? (\n <MessageVariantSwitcher variants={variants} onSelect={onSelectVariant} labels={switcherLabels} className=\"mt-0.5 px-1\" />\n ) : null}\n </div>\n )\n },\n)\n"]}
|