@superxd-studio/adptr 0.5.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 (221) hide show
  1. package/GENERATIVE.md +101 -0
  2. package/INTERACTIONS.md +51 -0
  3. package/LICENSE +21 -0
  4. package/README.md +57 -0
  5. package/REUSE.md +170 -0
  6. package/REVIEW.md +68 -0
  7. package/THIRD-PARTY-NOTICES.md +28 -0
  8. package/ai-sdk/index.js +96 -0
  9. package/assets/ibm-plex-mono-cyrillic-400-normal.woff2 +0 -0
  10. package/assets/ibm-plex-mono-cyrillic-500-normal.woff2 +0 -0
  11. package/assets/ibm-plex-mono-cyrillic-ext-400-normal.woff2 +0 -0
  12. package/assets/ibm-plex-mono-cyrillic-ext-500-normal.woff2 +0 -0
  13. package/assets/ibm-plex-mono-latin-400-normal.woff2 +0 -0
  14. package/assets/ibm-plex-mono-latin-500-normal.woff2 +0 -0
  15. package/assets/ibm-plex-mono-latin-ext-400-normal.woff2 +0 -0
  16. package/assets/ibm-plex-mono-latin-ext-500-normal.woff2 +0 -0
  17. package/assets/ibm-plex-mono-vietnamese-400-normal.woff2 +0 -0
  18. package/assets/ibm-plex-mono-vietnamese-500-normal.woff2 +0 -0
  19. package/assets/instrument-sans-latin-ext-wght-normal.woff2 +0 -0
  20. package/assets/instrument-sans-latin-wght-normal.woff2 +0 -0
  21. package/assistant-ui/index.js +139 -0
  22. package/chunks/callout-DKMHSnjc.js +342 -0
  23. package/chunks/canvas-BJ15-XxE.js +598 -0
  24. package/chunks/controller-Cfpg0C7v.js +180 -0
  25. package/chunks/core-BCqVRLUy.js +312 -0
  26. package/chunks/text-field-BLtoaZFn.js +41 -0
  27. package/chunks/types-BGF7r1my.js +115 -0
  28. package/chunks/ui-9DyNsYXY.js +967 -0
  29. package/core/index.js +4 -0
  30. package/custom/index.js +64 -0
  31. package/examples/ai-sdk-backend/package.json +11 -0
  32. package/examples/ai-sdk-backend/server.mjs +316 -0
  33. package/examples/custom-backend/copy.mjs +7 -0
  34. package/examples/custom-backend/server.mjs +118 -0
  35. package/examples/generative/specs.json +2094 -0
  36. package/examples/review-backend/copy.mjs +27 -0
  37. package/examples/review-backend/package.json +11 -0
  38. package/examples/review-backend/server.mjs +276 -0
  39. package/examples/web-ai-sdk/index.html +12 -0
  40. package/examples/web-ai-sdk/package.json +21 -0
  41. package/examples/web-ai-sdk/src/copy.ts +1 -0
  42. package/examples/web-ai-sdk/src/main.tsx +64 -0
  43. package/examples/web-ai-sdk/tsconfig.json +13 -0
  44. package/examples/web-generative/index.html +12 -0
  45. package/examples/web-generative/package.json +23 -0
  46. package/examples/web-generative/src/copy.ts +42 -0
  47. package/examples/web-generative/src/main.tsx +74 -0
  48. package/examples/web-generative/tsconfig.json +14 -0
  49. package/examples/web-independent/index.html +12 -0
  50. package/examples/web-independent/package.json +20 -0
  51. package/examples/web-independent/src/main.tsx +32 -0
  52. package/examples/web-independent/tsconfig.json +14 -0
  53. package/examples/web-interactions/index.html +12 -0
  54. package/examples/web-interactions/package.json +21 -0
  55. package/examples/web-interactions/src/copy.ts +215 -0
  56. package/examples/web-interactions/src/examples.tsx +628 -0
  57. package/examples/web-interactions/src/main.tsx +116 -0
  58. package/examples/web-interactions/tsconfig.json +14 -0
  59. package/examples/web-review/index.html +12 -0
  60. package/examples/web-review/package.json +20 -0
  61. package/examples/web-review/src/main.tsx +59 -0
  62. package/examples/web-review/tsconfig.json +14 -0
  63. package/fonts.css +117 -0
  64. package/generative/index.js +1560 -0
  65. package/interactions/index.js +1591 -0
  66. package/langgraph-review/index.js +36 -0
  67. package/licenses/ai-sdk.txt +13 -0
  68. package/licenses/assistant-ui-generative.txt +21 -0
  69. package/licenses/assistant-ui.txt +23 -0
  70. package/licenses/ibm-plex-mono.txt +93 -0
  71. package/licenses/instrument-sans.txt +93 -0
  72. package/licenses/internationalized-date.txt +201 -0
  73. package/licenses/langgraph.txt +21 -0
  74. package/licenses/lucide.txt +43 -0
  75. package/licenses/react-markdown.txt +21 -0
  76. package/licenses/remark-gfm.txt +22 -0
  77. package/licenses/zod.txt +21 -0
  78. package/package.json +138 -0
  79. package/review/index.js +31 -0
  80. package/source/components/avatar.tsx +50 -0
  81. package/source/components/button.tsx +155 -0
  82. package/source/components/callout.tsx +65 -0
  83. package/source/components/canvas/connector.tsx +14 -0
  84. package/source/components/canvas/copy.ts +34 -0
  85. package/source/components/canvas/index.tsx +257 -0
  86. package/source/components/canvas/recorder.ts +36 -0
  87. package/source/components/date-time-field.tsx +77 -0
  88. package/source/components/flow-motion.tsx +109 -0
  89. package/source/components/icons.tsx +275 -0
  90. package/source/components/responsive-overlay.tsx +91 -0
  91. package/source/components/scenario-picker/copy.ts +5 -0
  92. package/source/components/scenario-picker/index.tsx +142 -0
  93. package/source/components/scroll-area.tsx +59 -0
  94. package/source/components/search-picker.tsx +127 -0
  95. package/source/components/segmented-control.tsx +123 -0
  96. package/source/components/skeleton.tsx +11 -0
  97. package/source/components/spinner.tsx +19 -0
  98. package/source/components/superxd-arrow.tsx +22 -0
  99. package/source/components/switch.tsx +45 -0
  100. package/source/components/text-field.tsx +81 -0
  101. package/source/components/thinking.tsx +16 -0
  102. package/source/components/ui/copy.ts +12 -0
  103. package/source/components/ui/display.tsx +276 -0
  104. package/source/components/ui/navigation.tsx +346 -0
  105. package/source/components/ui/portal-theme.tsx +28 -0
  106. package/source/components/ui/selection.tsx +336 -0
  107. package/source/lib/cn.ts +23 -0
  108. package/source/reusable/ai-sdk/index.ts +114 -0
  109. package/source/reusable/assistant-ui/index.tsx +266 -0
  110. package/source/reusable/core/attachments.ts +163 -0
  111. package/source/reusable/core/index.ts +417 -0
  112. package/source/reusable/custom/index.ts +90 -0
  113. package/source/reusable/generative/actions.ts +188 -0
  114. package/source/reusable/generative/copy.ts +47 -0
  115. package/source/reusable/generative/data.tsx +126 -0
  116. package/source/reusable/generative/index.tsx +517 -0
  117. package/source/reusable/generative/schema.ts +327 -0
  118. package/source/reusable/generative/timeline.tsx +214 -0
  119. package/source/reusable/generative/vocabulary.tsx +847 -0
  120. package/source/reusable/interactions/agent.ts +259 -0
  121. package/source/reusable/interactions/artifact.ts +236 -0
  122. package/source/reusable/interactions/context.tsx +38 -0
  123. package/source/reusable/interactions/copy.ts +117 -0
  124. package/source/reusable/interactions/feedback.ts +73 -0
  125. package/source/reusable/interactions/index.ts +11 -0
  126. package/source/reusable/interactions/presentation.tsx +483 -0
  127. package/source/reusable/interactions/store.ts +74 -0
  128. package/source/reusable/interactions/threads.ts +126 -0
  129. package/source/reusable/interactions/web.tsx +574 -0
  130. package/source/reusable/langgraph-review/index.ts +73 -0
  131. package/source/reusable/review/controller.ts +263 -0
  132. package/source/reusable/review/index.ts +47 -0
  133. package/source/reusable/review/types.ts +189 -0
  134. package/source/reusable/ui/index.ts +25 -0
  135. package/source/reusable/uploads/index.ts +48 -0
  136. package/source/reusable/web/attachments/copy.ts +23 -0
  137. package/source/reusable/web/attachments/index.tsx +233 -0
  138. package/source/reusable/web/copy.ts +22 -0
  139. package/source/reusable/web/index.tsx +470 -0
  140. package/source/reusable/web/message/copy.ts +26 -0
  141. package/source/reusable/web/message/index.tsx +388 -0
  142. package/source/reusable/web/message/streaming.ts +64 -0
  143. package/source/reusable/web/message/styles.css +21 -0
  144. package/source/reusable/web/review/copy.ts +78 -0
  145. package/source/reusable/web/review/flow.ts +24 -0
  146. package/source/reusable/web/review/index.tsx +506 -0
  147. package/source/reusable/web/styles.ts +2 -0
  148. package/source/styles/library.css +612 -0
  149. package/source/styles/theme.css +228 -0
  150. package/styles.css +3 -0
  151. package/styles.d.ts +1 -0
  152. package/types/components/avatar.d.ts +14 -0
  153. package/types/components/button.d.ts +36 -0
  154. package/types/components/callout.d.ts +19 -0
  155. package/types/components/canvas/connector.d.ts +2 -0
  156. package/types/components/canvas/copy.d.ts +34 -0
  157. package/types/components/canvas/index.d.ts +33 -0
  158. package/types/components/canvas/recorder.d.ts +10 -0
  159. package/types/components/date-time-field.d.ts +11 -0
  160. package/types/components/flow-motion.d.ts +38 -0
  161. package/types/components/icons.d.ts +138 -0
  162. package/types/components/responsive-overlay.d.ts +15 -0
  163. package/types/components/scenario-picker/copy.d.ts +5 -0
  164. package/types/components/scenario-picker/index.d.ts +14 -0
  165. package/types/components/scroll-area.d.ts +14 -0
  166. package/types/components/search-picker.d.ts +27 -0
  167. package/types/components/segmented-control.d.ts +23 -0
  168. package/types/components/skeleton.d.ts +4 -0
  169. package/types/components/spinner.d.ts +4 -0
  170. package/types/components/superxd-arrow.d.ts +5 -0
  171. package/types/components/switch.d.ts +8 -0
  172. package/types/components/text-field.d.ts +19 -0
  173. package/types/components/thinking.d.ts +8 -0
  174. package/types/components/ui/copy.d.ts +12 -0
  175. package/types/components/ui/display.d.ts +50 -0
  176. package/types/components/ui/navigation.d.ts +63 -0
  177. package/types/components/ui/portal-theme.d.ts +8 -0
  178. package/types/components/ui/selection.d.ts +37 -0
  179. package/types/lib/cn.d.ts +3 -0
  180. package/types/reusable/ai-sdk/index.d.ts +15 -0
  181. package/types/reusable/assistant-ui/index.d.ts +12 -0
  182. package/types/reusable/core/attachments.d.ts +54 -0
  183. package/types/reusable/core/index.d.ts +107 -0
  184. package/types/reusable/custom/index.d.ts +14 -0
  185. package/types/reusable/generative/actions.d.ts +45 -0
  186. package/types/reusable/generative/copy.d.ts +48 -0
  187. package/types/reusable/generative/data.d.ts +4 -0
  188. package/types/reusable/generative/index.d.ts +30 -0
  189. package/types/reusable/generative/schema.d.ts +303 -0
  190. package/types/reusable/generative/timeline.d.ts +21 -0
  191. package/types/reusable/generative/vocabulary.d.ts +34 -0
  192. package/types/reusable/interactions/agent.d.ts +98 -0
  193. package/types/reusable/interactions/artifact.d.ts +118 -0
  194. package/types/reusable/interactions/context.d.ts +130 -0
  195. package/types/reusable/interactions/copy.d.ts +115 -0
  196. package/types/reusable/interactions/feedback.d.ts +34 -0
  197. package/types/reusable/interactions/index.d.ts +10 -0
  198. package/types/reusable/interactions/presentation.d.ts +97 -0
  199. package/types/reusable/interactions/store.d.ts +29 -0
  200. package/types/reusable/interactions/threads.d.ts +38 -0
  201. package/types/reusable/interactions/web.d.ts +22 -0
  202. package/types/reusable/langgraph-review/index.d.ts +5 -0
  203. package/types/reusable/review/controller.d.ts +52 -0
  204. package/types/reusable/review/index.d.ts +12 -0
  205. package/types/reusable/review/types.d.ts +61 -0
  206. package/types/reusable/ui/index.d.ts +19 -0
  207. package/types/reusable/uploads/index.d.ts +11 -0
  208. package/types/reusable/web/attachments/copy.d.ts +25 -0
  209. package/types/reusable/web/attachments/index.d.ts +24 -0
  210. package/types/reusable/web/copy.d.ts +24 -0
  211. package/types/reusable/web/index.d.ts +41 -0
  212. package/types/reusable/web/message/copy.d.ts +27 -0
  213. package/types/reusable/web/message/index.d.ts +56 -0
  214. package/types/reusable/web/message/streaming.d.ts +27 -0
  215. package/types/reusable/web/review/copy.d.ts +72 -0
  216. package/types/reusable/web/review/flow.d.ts +3 -0
  217. package/types/reusable/web/review/index.d.ts +38 -0
  218. package/types/reusable/web/styles.d.ts +2 -0
  219. package/ui/index.js +4 -0
  220. package/uploads/index.js +34 -0
  221. package/web/index.js +931 -0
@@ -0,0 +1,470 @@
1
+ import {
2
+ useId,
3
+ useRef,
4
+ useState,
5
+ useLayoutEffect,
6
+ useSyncExternalStore,
7
+ type ReactNode,
8
+ } from "react";
9
+ import { Label, TextArea, TextField } from "react-aria-components";
10
+ import { Button } from "../../components/button.js";
11
+ import { Callout } from "../../components/callout.js";
12
+ import { Thinking } from "../../components/thinking.js";
13
+ import {
14
+ IconArrowUp,
15
+ IconArrowDown,
16
+ IconSquare,
17
+ IconEdit,
18
+ IconRotate,
19
+ } from "../../components/icons.js";
20
+ import { cn } from "../../lib/cn.js";
21
+ import type {
22
+ ChatActions,
23
+ ChatController,
24
+ ChatFile,
25
+ ChatMessage,
26
+ ChatSnapshot,
27
+ } from "../core/index.js";
28
+ import { AttachmentList } from "./attachments/index.js";
29
+ export { AttachmentList, AttachmentPicker, useAttachments } from "./attachments/index.js";
30
+ export type { AttachmentListProps, AttachmentPickerProps } from "./attachments/index.js";
31
+ export { attachmentCopy } from "./attachments/copy.js";
32
+ import { chatCopy, type ChatCopy } from "./copy.js";
33
+ import {
34
+ BranchPicker,
35
+ CopyButton,
36
+ MessageEditor,
37
+ MessageMarkdown,
38
+ StreamingMessage,
39
+ messageCopy,
40
+ type MessageCopy,
41
+ } from "./message/index.js";
42
+ export {
43
+ BranchPicker,
44
+ CopyButton,
45
+ CodeBlock,
46
+ MessageEditor,
47
+ MessageMarkdown,
48
+ StreamingMessage,
49
+ messageCopy,
50
+ } from "./message/index.js";
51
+ export type {
52
+ BranchPickerProps,
53
+ CopyButtonProps,
54
+ CodeBlockProps,
55
+ MessageEditorProps,
56
+ MessageMarkdownProps,
57
+ MessageCopy,
58
+ } from "./message/index.js";
59
+ export type { ChatCopy } from "./copy.js";
60
+ export type { ChatActions, ChatMessage, ChatSnapshot } from "../core/index.js";
61
+
62
+ export function useChatController(controller: ChatController): ChatSnapshot {
63
+ return useSyncExternalStore(controller.subscribe, controller.getSnapshot, controller.getSnapshot);
64
+ }
65
+
66
+ export interface ChatProps {
67
+ snapshot: ChatSnapshot;
68
+ actions: ChatActions;
69
+ copy?: ChatCopy;
70
+ className?: string;
71
+ /** Render supplied text using the application's safe Markdown renderer if wanted. */
72
+ renderMessage?: (message: ChatMessage) => ReactNode;
73
+ footer?: ReactNode;
74
+ attachmentPicker?: ReactNode;
75
+ draftFiles?: readonly ChatFile[];
76
+ isAttachmentBlocked?: boolean;
77
+ /** Clear a draft queue only after the state owner accepts the message. */
78
+ onSent?: () => void;
79
+ messageCopy?: MessageCopy;
80
+ writeText?: (text: string) => Promise<void>;
81
+ /** Opt into the existing bounded conversation height; follows only while at the bottom. */
82
+ scrollable?: boolean;
83
+ autoScroll?: boolean;
84
+ /** Prompts offered in an empty conversation. Choosing one puts it in the message box. */
85
+ suggestions?: readonly string[];
86
+ /** Text already in the message box when the conversation first appears. */
87
+ defaultDraft?: string;
88
+ /** A user message already open for editing when it first appears (specimens; no focus move). */
89
+ defaultEditing?: string;
90
+ }
91
+
92
+ /** A controlled presentation: state comes from the host, not a mandatory provider. */
93
+ export function Chat({
94
+ snapshot,
95
+ actions,
96
+ copy = chatCopy,
97
+ className,
98
+ renderMessage,
99
+ footer,
100
+ attachmentPicker,
101
+ draftFiles = [],
102
+ isAttachmentBlocked = false,
103
+ onSent,
104
+ messageCopy: words = messageCopy,
105
+ writeText,
106
+ scrollable = false,
107
+ autoScroll = true,
108
+ suggestions = [],
109
+ defaultDraft = "",
110
+ defaultEditing,
111
+ }: ChatProps) {
112
+ const [draft, setDraft] = useState(defaultDraft);
113
+ const [editing, setEditing] = useState<string | undefined>(defaultEditing);
114
+ // An editor the visitor opens takes focus; one shown as a specimen doesn't
115
+ const [editFocus, setEditFocus] = useState(false);
116
+ const root = useRef<HTMLElement>(null);
117
+ const viewport = useRef<HTMLDivElement>(null);
118
+ const follow = useRef(autoScroll);
119
+ const [showLatest, setShowLatest] = useState(false);
120
+ useLayoutEffect(() => {
121
+ if (scrollable && autoScroll && follow.current && viewport.current)
122
+ viewport.current.scrollTop = viewport.current.scrollHeight;
123
+ }, [snapshot.messages, snapshot.status, scrollable, autoScroll]);
124
+ const jump = () => {
125
+ follow.current = true;
126
+ setShowLatest(false);
127
+ if (viewport.current) viewport.current.scrollTop = viewport.current.scrollHeight;
128
+ };
129
+ const focusComposer = () =>
130
+ root.current
131
+ ?.querySelector<HTMLTextAreaElement>("[data-chat-composer] textarea")
132
+ ?.focus({ preventScroll: true });
133
+ const closeEdit = () => {
134
+ setEditing(undefined);
135
+ window.requestAnimationFrame(focusComposer);
136
+ };
137
+ const headingId = useId();
138
+ const hintId = useId();
139
+ const running = snapshot.status === "waiting" || snapshot.status === "streaming";
140
+ const recoverable = ["error", "interrupted", "cancelled"].includes(snapshot.status);
141
+ const send = () => {
142
+ if (isAttachmentBlocked || running) return;
143
+ if (actions.send(draft, draftFiles)) {
144
+ setDraft("");
145
+ jump();
146
+ onSent?.();
147
+ }
148
+ };
149
+ return (
150
+ <section
151
+ ref={root}
152
+ aria-labelledby={headingId}
153
+ className={cn(
154
+ "relative flex min-w-0 flex-col gap-6 rounded-surface bg-surface p-4 sm:p-6",
155
+ scrollable && "h-conversation",
156
+ className,
157
+ )}
158
+ >
159
+ <h2 id={headingId} className="sr-only">
160
+ {copy.label}
161
+ </h2>
162
+ <div
163
+ ref={viewport}
164
+ role={scrollable ? "region" : undefined}
165
+ // Named after this conversation too, so several on one page stay distinguishable
166
+ aria-label={scrollable ? `${words.transcript}: ${copy.label}` : undefined}
167
+ tabIndex={scrollable ? 0 : undefined}
168
+ onScroll={() => {
169
+ const el = viewport.current;
170
+ if (!el) return;
171
+ const bottom = el.scrollHeight - el.scrollTop - el.clientHeight < 24;
172
+ follow.current = bottom;
173
+ setShowLatest(!bottom);
174
+ }}
175
+ className={cn(
176
+ "min-w-0",
177
+ scrollable &&
178
+ "min-h-0 flex-1 overflow-x-hidden overflow-y-auto rounded-cell outline-none focus-visible:focus-ring",
179
+ )}
180
+ >
181
+ {snapshot.messages.length ? (
182
+ <ol className="flex min-w-0 list-none flex-col gap-6 p-0">
183
+ {snapshot.messages.map((message, index) => (
184
+ <li
185
+ key={message.id}
186
+ aria-label={message.role === "user" ? copy.user : copy.assistant}
187
+ className="flex min-w-0 flex-col gap-2"
188
+ >
189
+ <p className="font-mono type-caption text-fg-subtle">
190
+ {message.role === "user" ? copy.user : copy.assistant}
191
+ </p>
192
+ {editing === message.id && actions.edit ? (
193
+ <MessageEditor
194
+ autoFocus={editFocus}
195
+ text={message.text}
196
+ hasFiles={Boolean(message.files?.length)}
197
+ isDisabled={running}
198
+ onCancel={closeEdit}
199
+ copy={words}
200
+ onSave={(text) => {
201
+ const accepted = actions.edit?.(message.id, text) ?? false;
202
+ if (accepted) {
203
+ closeEdit();
204
+ jump();
205
+ }
206
+ return accepted;
207
+ }}
208
+ />
209
+ ) : (
210
+ <div
211
+ className={cn(
212
+ "min-w-0 type-body wrap-anywhere whitespace-pre-wrap text-fg-emphasis",
213
+ message.role === "user" &&
214
+ "self-end rounded-surface bg-surface-muted px-control-md-x py-control-y",
215
+ )}
216
+ >
217
+ {renderMessage ? (
218
+ renderMessage(message)
219
+ ) : message.role === "assistant" &&
220
+ snapshot.status === "streaming" &&
221
+ index === snapshot.messages.length - 1 ? (
222
+ <StreamingMessage text={message.text} copy={words} writeText={writeText} />
223
+ ) : message.role === "assistant" ? (
224
+ <MessageMarkdown
225
+ text={message.text}
226
+ isStreaming={running}
227
+ copy={words}
228
+ writeText={writeText}
229
+ />
230
+ ) : (
231
+ message.text
232
+ )}
233
+ {message.role === "assistant" && !message.text && running ? <Thinking /> : null}
234
+ </div>
235
+ )}
236
+ {message.files?.length ? (
237
+ <AttachmentList
238
+ items={message.files.map((file) => ({
239
+ id: file.id,
240
+ name: file.name,
241
+ mediaType: file.mediaType,
242
+ size: file.size,
243
+ status: "ready",
244
+ file,
245
+ }))}
246
+ variant="inline"
247
+ />
248
+ ) : null}
249
+ {editing !== message.id ? (
250
+ <div className="flex min-w-0 flex-wrap items-center gap-1">
251
+ {message.role === "assistant" && message.text ? (
252
+ <CopyButton
253
+ text={message.text}
254
+ iconOnly
255
+ isDisabled={running}
256
+ copy={words}
257
+ writeText={writeText}
258
+ />
259
+ ) : null}
260
+ {message.role === "user" && snapshot.capabilities.edit && actions.edit ? (
261
+ <Button
262
+ size="icon-sm"
263
+ variant="ghost"
264
+ aria-label={words.edit}
265
+ isDisabled={running}
266
+ onPress={() => {
267
+ setEditFocus(true);
268
+ setEditing(message.id);
269
+ }}
270
+ >
271
+ <IconEdit />
272
+ </Button>
273
+ ) : null}
274
+ {message.role === "assistant" &&
275
+ snapshot.capabilities.regenerate &&
276
+ actions.regenerate ? (
277
+ <Button
278
+ size="icon-sm"
279
+ variant="ghost"
280
+ aria-label={words.regenerate}
281
+ isDisabled={running}
282
+ onPress={() => {
283
+ if (actions.regenerate?.(message.id)) {
284
+ focusComposer();
285
+ jump();
286
+ }
287
+ }}
288
+ >
289
+ <IconRotate />
290
+ </Button>
291
+ ) : null}
292
+ {snapshot.capabilities.branches &&
293
+ snapshot.branches?.[message.id] &&
294
+ actions.selectBranch ? (
295
+ <BranchPicker
296
+ branch={snapshot.branches[message.id] ?? { index: 0, count: 0 }}
297
+ isDisabled={running}
298
+ copy={words}
299
+ onPrevious={() => {
300
+ if (actions.selectBranch?.(message.id, "previous")) focusComposer();
301
+ }}
302
+ onNext={() => {
303
+ if (actions.selectBranch?.(message.id, "next")) focusComposer();
304
+ }}
305
+ />
306
+ ) : null}
307
+ </div>
308
+ ) : null}
309
+ </li>
310
+ ))}
311
+ </ol>
312
+ ) : (
313
+ <div className="flex min-h-48 flex-col items-center justify-center gap-2 text-center">
314
+ <h3 className="type-heading text-fg-emphasis">{copy.emptyTitle}</h3>
315
+ <p className="max-w-sm type-body text-fg-subtle">{copy.emptyNote}</p>
316
+ {suggestions.length ? (
317
+ <div className="mt-3 flex flex-wrap justify-center gap-2">
318
+ {suggestions.map((prompt) => (
319
+ <Button
320
+ key={prompt}
321
+ size="sm"
322
+ isDisabled={!snapshot.capabilities.send}
323
+ className="h-auto min-h-control-sm max-w-full py-2 whitespace-normal"
324
+ onPress={() => {
325
+ setDraft(prompt);
326
+ window.requestAnimationFrame(focusComposer);
327
+ }}
328
+ >
329
+ {prompt}
330
+ </Button>
331
+ ))}
332
+ </div>
333
+ ) : null}
334
+ </div>
335
+ )}
336
+ </div>
337
+ {scrollable && showLatest ? (
338
+ <Button
339
+ size="sm"
340
+ className="self-center shadow-control"
341
+ onPress={() => {
342
+ jump();
343
+ focusComposer();
344
+ }}
345
+ >
346
+ <IconArrowDown />
347
+ {words.latest}
348
+ </Button>
349
+ ) : null}
350
+ {/* The system's voice: mono words, with the done dot when the answer is ready. The thinking
351
+ mark shows where the answer will appear, not here. */}
352
+ <p
353
+ role="status"
354
+ aria-live="polite"
355
+ className={cn(
356
+ "flex items-center gap-2 font-mono type-caption text-fg-subtle",
357
+ recoverable && "sr-only",
358
+ )}
359
+ >
360
+ {snapshot.status === "complete" ? (
361
+ <span aria-hidden="true" className="size-1.5 shrink-0 rounded-pill bg-done" />
362
+ ) : null}
363
+ {copy[snapshot.status]}
364
+ </p>
365
+ {snapshot.capabilities.attachments ? attachmentPicker : null}
366
+ {recoverable ? (
367
+ <Callout
368
+ tone={snapshot.status === "cancelled" ? "neutral" : "attention"}
369
+ actions={
370
+ snapshot.capabilities.retry ? (
371
+ <Button
372
+ size="sm"
373
+ onPress={() => {
374
+ // The notice and its button go away; keep the visitor in the message box
375
+ if (actions.retry()) focusComposer();
376
+ }}
377
+ >
378
+ {copy.retry}
379
+ </Button>
380
+ ) : undefined
381
+ }
382
+ >
383
+ {copy[snapshot.status]}
384
+ </Callout>
385
+ ) : null}
386
+ <div
387
+ data-chat-composer
388
+ className="conversation-composer min-w-0 rounded-surface border border-line-input bg-surface shadow-control focus-within:border-line-emphasis has-data-focus-visible:border-focus has-data-focus-visible:ring-2 has-data-focus-visible:ring-focus/15"
389
+ >
390
+ <TextField
391
+ value={draft}
392
+ onChange={setDraft}
393
+ isDisabled={!snapshot.capabilities.send || Boolean(editing)}
394
+ className="min-w-0"
395
+ >
396
+ <Label className="sr-only">{copy.input}</Label>
397
+ <TextArea
398
+ rows={3}
399
+ maxLength={20_000}
400
+ aria-describedby={hintId}
401
+ placeholder={copy.placeholder}
402
+ className="max-h-48 min-h-24 w-full resize-none rounded-surface bg-transparent px-control-md-x pt-control-y pb-2 type-body text-fg-emphasis outline-none placeholder:text-fg-subtle"
403
+ onKeyDown={(event) => {
404
+ if (
405
+ event.key === "Enter" &&
406
+ !event.shiftKey &&
407
+ !event.nativeEvent.isComposing &&
408
+ !window.matchMedia("(pointer: coarse)").matches
409
+ ) {
410
+ event.preventDefault();
411
+ if (!running && !editing) send();
412
+ }
413
+ }}
414
+ />
415
+ </TextField>
416
+ <div className="flex flex-wrap items-center justify-between gap-3 px-3 pb-3">
417
+ <p id={hintId} className="conversation-key-hint type-caption text-fg-subtle">
418
+ <span className="conversation-desktop-hint">{copy.hint}</span>
419
+ <span className="conversation-touch-hint">{copy.touchHint}</span>
420
+ </p>
421
+ {running && snapshot.capabilities.cancel ? (
422
+ <Button
423
+ size="sm"
424
+ className="ml-auto"
425
+ onPress={() => {
426
+ actions.cancel();
427
+ focusComposer();
428
+ }}
429
+ >
430
+ <IconSquare />
431
+ {copy.stop}
432
+ </Button>
433
+ ) : (
434
+ <Button
435
+ variant="primary"
436
+ className="ml-auto"
437
+ size="sm"
438
+ isDisabled={
439
+ running ||
440
+ Boolean(editing) ||
441
+ isAttachmentBlocked ||
442
+ (!draft.trim() && !draftFiles.length) ||
443
+ !snapshot.capabilities.send
444
+ }
445
+ onPress={send}
446
+ >
447
+ <IconArrowUp />
448
+ {copy.send}
449
+ </Button>
450
+ )}
451
+ </div>
452
+ </div>
453
+ {footer}
454
+ </section>
455
+ );
456
+ }
457
+
458
+ export {
459
+ DraftReview,
460
+ ReviewChangeCard,
461
+ ReviewDecisionPanel,
462
+ useReview,
463
+ reviewCopy,
464
+ } from "./review/index.js";
465
+ export type {
466
+ DraftReviewProps,
467
+ ReviewChangeCardProps,
468
+ ReviewDecisionPanelProps,
469
+ ReviewCopy,
470
+ } from "./review/index.js";
@@ -0,0 +1,26 @@
1
+ export const messageCopy = {
2
+ copy: "Copy response",
3
+ copyCode: "Copy code",
4
+ copying: "Copying",
5
+ copied: "Copied",
6
+ copyFailed: "Couldn’t copy. Select the text and copy it instead.",
7
+ code: "Code",
8
+ table: "Response table",
9
+ footnotes: "Notes",
10
+ back: "Back to the reference",
11
+ previous: "Previous version",
12
+ next: "Next version",
13
+ versions: "Message versions",
14
+ version: "Version",
15
+ edit: "Edit your message",
16
+ editInput: "Edit message",
17
+ save: "Save and resend",
18
+ cancel: "Cancel edit",
19
+ editNote: "Resending creates a new version. The original conversation stays available.",
20
+ editFailed:
21
+ "This message couldn’t be resent. Your edit is still here. Try again when the response has stopped.",
22
+ regenerate: "Regenerate response",
23
+ latest: "Jump to latest message",
24
+ transcript: "Conversation messages",
25
+ } as const;
26
+ export type MessageCopy = { [Key in keyof typeof messageCopy]: string };