@applica-software-guru/persona-sdk 0.1.79 → 0.1.82

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.
@@ -1,121 +1,66 @@
1
- /* eslint-disable @typescript-eslint/no-unused-vars */
2
- import {
3
- ActionBarPrimitive,
4
- BranchPickerPrimitive,
5
- ComposerPrimitive,
6
- FileContentPart,
7
- ImageContentPart,
8
- MessagePrimitive,
9
- ReasoningContentPartProps,
10
- ThreadPrimitive,
11
- } from '@assistant-ui/react';
12
- import { JSX, useCallback, useMemo, useState, type FC } from 'react';
13
1
  import {
14
2
  ArrowDownIcon,
3
+ ArrowUpIcon,
15
4
  CheckIcon,
16
5
  ChevronLeftIcon,
17
6
  ChevronRightIcon,
18
7
  CopyIcon,
19
- SendHorizontalIcon,
20
- MicIcon,
21
- FileIcon,
22
- ImageIcon,
23
- ArchiveIcon,
24
- ChevronUpIcon,
25
- LightbulbIcon,
26
- ChevronDownIcon,
27
- } from 'lucide-react';
28
- import { cn } from '@/lib/utils';
29
-
30
- import { Button } from '@/components/ui/button';
31
- import { MarkdownText } from '@/components/assistant-ui/markdown-text';
32
- import { TooltipIconButton } from '@/components/assistant-ui/tooltip-icon-button';
33
- import { ToolFallback } from '@/components/assistant-ui/tool-fallback';
34
-
35
- import { usePersonaRuntimeEndpoint, usePersonaRuntimeWebRTCProtocol } from '@applica-software-guru/persona-sdk';
36
- import { ComposerAddAttachment, ComposerAttachments, UserMessageAttachments } from './attachment';
37
- import Markdown from 'react-markdown';
38
- import remarkGfm from 'remark-gfm';
8
+ PencilIcon,
9
+ RefreshCwIcon,
10
+ Square,
11
+ } from "lucide-react";
39
12
 
40
- const Reasoning: FC<ReasoningContentPartProps> = ({ type, text, status }: ReasoningContentPartProps) => {
41
- const [open, setOpen] = useState(false);
42
- let isThinking = status.type === 'running' && type === 'reasoning';
13
+ import {
14
+ ActionBarPrimitive,
15
+ BranchPickerPrimitive,
16
+ ComposerPrimitive,
17
+ ErrorPrimitive,
18
+ MessagePrimitive,
19
+ ThreadPrimitive,
20
+ } from "@assistant-ui/react";
43
21
 
44
- const regex = /\*\*(.*?)\*\*/gs; // g = globale, s = multilinea ( . include \n )
45
- const matches = [...text.matchAll(regex)];
46
- let title = matches.length > 0 ? matches[matches.length - 1][1] : 'Thinking';
47
- if (isThinking) {
48
- title = `${title}...`;
49
- } else {
50
- title = `Done thinking!`;
51
- }
52
- const rest = text;
22
+ import type { FC } from "react";
53
23
 
54
- return (
55
- <div
56
- className={`flex flex-col gap-2 border border-gray-200 bg-gray-50 rounded-lg p-2 shadow-sm transition-opacity duration-500 opacity-100`}
57
- >
58
- <div className="flex items-start gap-2">
59
- <LightbulbIcon className="text-orange-400" />
24
+ import { Button } from "@/components/ui/button";
25
+ import { MarkdownText } from "@/components/assistant-ui/markdown-text";
26
+ import { ToolFallback } from "@/components/assistant-ui/tool-fallback";
27
+ import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
28
+ import {
29
+ ComposerAddAttachment,
30
+ ComposerAttachments,
31
+ UserMessageAttachments,
32
+ } from "@/components/assistant-ui/attachment";
60
33
 
61
- <span className="font-normal flex-1 min-w-0 text-sm self-center">
62
- <span className="align-bottom">
63
- <span className={`text-gray-700 ${isThinking ? 'animate-pulse' : ''}`}>
64
- <b>{title}</b>
65
- </span>
66
- {open && (
67
- <div className="mt-4 border-l-2 rounded border-gray-200 pl-4 text-sm text-gray-900 flex flex-col gap-2">
68
- <div className="bg-gray-100 rounded px-3 py-2">
69
- <div className="whitespace-pre-wrap text-gray-900">
70
- <Markdown remarkPlugins={[remarkGfm]}>{rest}</Markdown>
71
- </div>
72
- </div>
73
- </div>
74
- )}
75
- </span>
76
- </span>
34
+ import { cn } from "@/lib/utils";
77
35
 
78
- <div className="flex-0 ml-2 flex justify-end w-auto">
79
- <button
80
- className="p-1 rounded hover:bg-gray-100 transition-colors"
81
- aria-label={open ? 'Show reasoning' : 'Hide reasoning'}
82
- onClick={() => setOpen((v) => !v)}
83
- type="button"
84
- >
85
- {open ? <ChevronUpIcon className="size-4" /> : <ChevronDownIcon className="size-4" />}
86
- </button>
87
- </div>
88
- </div>
89
- </div>
90
- );
91
- };
92
36
  export const Thread: FC = () => {
93
37
  return (
94
38
  <ThreadPrimitive.Root
95
- className="bg-background box-border flex h-full flex-col overflow-hidden"
39
+ className="aui-root aui-thread-root @container flex h-full flex-col bg-background"
96
40
  style={{
97
- ['--thread-max-width' as string]: '42rem',
41
+ ["--thread-max-width" as string]: "44rem",
98
42
  }}
99
43
  >
100
- <ThreadPrimitive.Viewport className="flex h-full flex-col items-center overflow-y-scroll scroll-smooth bg-inherit px-4 pt-8">
101
- <ThreadWelcome />
44
+ <ThreadPrimitive.Viewport
45
+ turnAnchor="top"
46
+ className="aui-thread-viewport relative flex flex-1 flex-col overflow-x-auto overflow-y-scroll scroll-smooth px-4 pt-4"
47
+ >
48
+ <ThreadPrimitive.If empty>
49
+ <ThreadWelcome />
50
+ </ThreadPrimitive.If>
102
51
 
103
52
  <ThreadPrimitive.Messages
104
53
  components={{
105
- UserMessage: UserMessage,
106
- EditComposer: EditComposer,
107
- AssistantMessage: AssistantMessage,
54
+ UserMessage,
55
+ EditComposer,
56
+ AssistantMessage,
108
57
  }}
109
58
  />
110
59
 
111
- <ThreadPrimitive.If empty={false}>
112
- <div className="min-h-8 flex-grow" />
113
- </ThreadPrimitive.If>
114
-
115
- <div className="sticky bottom-0 mt-3 flex w-full max-w-[var(--thread-max-width)] flex-col items-center justify-end rounded-t-lg bg-inherit pb-4">
60
+ <ThreadPrimitive.ViewportFooter className="aui-thread-viewport-footer sticky bottom-0 mx-auto mt-4 flex w-full max-w-(--thread-max-width) flex-col gap-4 overflow-visible rounded-t-3xl bg-background pb-4 md:pb-6">
116
61
  <ThreadScrollToBottom />
117
62
  <Composer />
118
- </div>
63
+ </ThreadPrimitive.ViewportFooter>
119
64
  </ThreadPrimitive.Viewport>
120
65
  </ThreadPrimitive.Root>
121
66
  );
@@ -124,7 +69,11 @@ export const Thread: FC = () => {
124
69
  const ThreadScrollToBottom: FC = () => {
125
70
  return (
126
71
  <ThreadPrimitive.ScrollToBottom asChild>
127
- <TooltipIconButton tooltip="Scroll to bottom" variant="outline" className="absolute -top-8 rounded-full disabled:invisible">
72
+ <TooltipIconButton
73
+ tooltip="Scroll to bottom"
74
+ variant="outline"
75
+ className="aui-thread-scroll-to-bottom -top-12 absolute z-10 self-center rounded-full p-4 disabled:invisible dark:bg-background dark:hover:bg-accent"
76
+ >
128
77
  <ArrowDownIcon />
129
78
  </TooltipIconButton>
130
79
  </ThreadPrimitive.ScrollToBottom>
@@ -133,175 +82,162 @@ const ThreadScrollToBottom: FC = () => {
133
82
 
134
83
  const ThreadWelcome: FC = () => {
135
84
  return (
136
- <ThreadPrimitive.Empty>
137
- <div className="flex w-full max-w-[var(--thread-max-width)] flex-grow flex-col">
138
- <div className="flex w-full flex-grow flex-col items-center justify-center">
139
- <p className="mt-4 font-medium">Come posso aiutarti oggi?</p>
85
+ <div className="aui-thread-welcome-root mx-auto my-auto flex w-full max-w-(--thread-max-width) grow flex-col">
86
+ <div className="aui-thread-welcome-center flex w-full grow flex-col items-center justify-center">
87
+ <div className="aui-thread-welcome-message flex size-full flex-col justify-center px-8">
88
+ <div className="aui-thread-welcome-message-inner fade-in slide-in-from-bottom-2 animate-in font-semibold text-2xl duration-300 ease-out">
89
+ Hello there!
90
+ </div>
91
+ <div className="aui-thread-welcome-message-inner fade-in slide-in-from-bottom-2 animate-in text-2xl text-muted-foreground/65 delay-100 duration-300 ease-out">
92
+ How can I help you today?
93
+ </div>
140
94
  </div>
141
- {/* <ThreadWelcomeSuggestions /> */}
142
95
  </div>
143
- </ThreadPrimitive.Empty>
96
+ <ThreadSuggestions />
97
+ </div>
144
98
  );
145
99
  };
146
100
 
147
- const Composer: FC = () => {
101
+ const ThreadSuggestions: FC = () => {
148
102
  return (
149
- <ComposerPrimitive.Root className="focus-within:border-ring/20 flex w-full flex-wrap items-end rounded-lg border bg-inherit px-2.5 shadow-sm transition-colors ease-in">
150
- <ComposerAttachments />
151
- <ComposerAddAttachment />
152
- <ComposerPrimitive.Input
153
- rows={1}
154
- autoFocus
155
- placeholder="Write a message..."
156
- className="placeholder:text-muted-foreground max-h-40 flex-grow resize-none border-none bg-transparent px-2 py-4 text-base outline-none focus:ring-0 disabled:cursor-not-allowed"
157
- />
158
- <ComposerAction />
159
- </ComposerPrimitive.Root>
103
+ <div className="aui-thread-welcome-suggestions grid w-full @md:grid-cols-2 gap-2 pb-4">
104
+ {[
105
+ {
106
+ title: "What's the weather",
107
+ label: "in San Francisco?",
108
+ action: "What's the weather in San Francisco?",
109
+ },
110
+ {
111
+ title: "Explain React hooks",
112
+ label: "like useState and useEffect",
113
+ action: "Explain React hooks like useState and useEffect",
114
+ },
115
+ {
116
+ title: "Write a SQL query",
117
+ label: "to find top customers",
118
+ action: "Write a SQL query to find top customers",
119
+ },
120
+ {
121
+ title: "Create a meal plan",
122
+ label: "for healthy weight loss",
123
+ action: "Create a meal plan for healthy weight loss",
124
+ },
125
+ ].map((suggestedAction, index) => (
126
+ <div
127
+ key={`suggested-action-${suggestedAction.title}-${index}`}
128
+ className="aui-thread-welcome-suggestion-display fade-in slide-in-from-bottom-4 @md:nth-[n+3]:block nth-[n+3]:hidden animate-in fill-mode-both duration-300 ease-out"
129
+ style={{ animationDelay: `${index * 50}ms` }}
130
+ >
131
+ <ThreadPrimitive.Suggestion
132
+ prompt={suggestedAction.action}
133
+ send
134
+ asChild
135
+ >
136
+ <Button
137
+ variant="ghost"
138
+ className="aui-thread-welcome-suggestion h-auto w-full flex-1 @md:flex-col flex-wrap items-start justify-start gap-1 rounded-3xl border px-5 py-4 text-left text-sm dark:hover:bg-accent/60"
139
+ aria-label={suggestedAction.action}
140
+ >
141
+ <span className="aui-thread-welcome-suggestion-text-1 font-medium">
142
+ {suggestedAction.title}
143
+ </span>
144
+ <span className="aui-thread-welcome-suggestion-text-2 text-muted-foreground">
145
+ {suggestedAction.label}
146
+ </span>
147
+ </Button>
148
+ </ThreadPrimitive.Suggestion>
149
+ </div>
150
+ ))}
151
+ </div>
160
152
  );
161
153
  };
162
154
 
163
- const SpeakButton: FC = () => {
164
- const webrtcProtocol = usePersonaRuntimeWebRTCProtocol()!;
165
-
166
- const isConnected = useMemo(() => webrtcProtocol?.status === 'connected', [webrtcProtocol?.status]);
167
- const handleConnection = useCallback(() => {
168
- if (webrtcProtocol.status === 'connected') {
169
- webrtcProtocol.disconnect();
170
- } else {
171
- webrtcProtocol.connect();
172
- }
173
- }, [webrtcProtocol]);
174
-
175
- if (!webrtcProtocol) {
176
- return null;
177
- }
155
+ const Composer: FC = () => {
178
156
  return (
179
- <TooltipIconButton
180
- className={cn(
181
- 'hover:text-primary my-2.5 size-8 p-2 ml-2 transition-opacity ease-in cursor-pointer',
182
- isConnected && 'animate-pulse text-blue-500',
183
- )}
184
- variant="default"
185
- tooltip={isConnected ? 'Stop speaking' : 'Speak'}
186
- onClick={handleConnection}
187
- >
188
- <MicIcon className={cn(isConnected && 'animate-pulse')} />
189
- </TooltipIconButton>
157
+ <ComposerPrimitive.Root className="aui-composer-root relative flex w-full flex-col">
158
+ <ComposerPrimitive.AttachmentDropzone className="aui-composer-attachment-dropzone flex w-full flex-col rounded-3xl border border-input bg-background px-1 pt-2 shadow-xs outline-none transition-[color,box-shadow] has-[textarea:focus-visible]:border-ring has-[textarea:focus-visible]:ring-[3px] has-[textarea:focus-visible]:ring-ring/50 data-[dragging=true]:border-ring data-[dragging=true]:border-dashed data-[dragging=true]:bg-accent/50 dark:bg-background">
159
+ <ComposerAttachments />
160
+ <ComposerPrimitive.Input
161
+ placeholder="Send a message..."
162
+ className="aui-composer-input mb-1 max-h-32 min-h-16 w-full resize-none bg-transparent px-3.5 pt-1.5 pb-3 text-base outline-none placeholder:text-muted-foreground focus-visible:ring-0"
163
+ rows={1}
164
+ autoFocus
165
+ aria-label="Message input"
166
+ />
167
+ <ComposerAction />
168
+ </ComposerPrimitive.AttachmentDropzone>
169
+ </ComposerPrimitive.Root>
190
170
  );
191
171
  };
192
172
 
193
173
  const ComposerAction: FC = () => {
194
174
  return (
195
- <>
175
+ <div className="aui-composer-action-wrapper relative mx-1 mt-2 mb-2 flex items-center justify-between">
176
+ <ComposerAddAttachment />
177
+
196
178
  <ThreadPrimitive.If running={false}>
197
179
  <ComposerPrimitive.Send asChild>
198
- <TooltipIconButton tooltip="Send" variant="default" className="my-2.5 size-8 p-2 transition-opacity ease-in">
199
- <SendHorizontalIcon />
180
+ <TooltipIconButton
181
+ tooltip="Send message"
182
+ side="bottom"
183
+ type="submit"
184
+ variant="default"
185
+ size="icon"
186
+ className="aui-composer-send size-[34px] rounded-full p-1"
187
+ aria-label="Send message"
188
+ >
189
+ <ArrowUpIcon className="aui-composer-send-icon size-5" />
200
190
  </TooltipIconButton>
201
191
  </ComposerPrimitive.Send>
202
192
  </ThreadPrimitive.If>
193
+
203
194
  <ThreadPrimitive.If running>
204
195
  <ComposerPrimitive.Cancel asChild>
205
- <TooltipIconButton tooltip="Cancel" variant="default" className="my-2.5 size-8 p-2 transition-opacity ease-in">
206
- <CircleStopIcon />
207
- </TooltipIconButton>
196
+ <Button
197
+ type="button"
198
+ variant="default"
199
+ size="icon"
200
+ className="aui-composer-cancel size-[34px] rounded-full border border-muted-foreground/60 hover:bg-primary/75 dark:border-muted-foreground/90"
201
+ aria-label="Stop generating"
202
+ >
203
+ <Square className="aui-composer-cancel-icon size-3.5 fill-white dark:fill-black" />
204
+ </Button>
208
205
  </ComposerPrimitive.Cancel>
209
206
  </ThreadPrimitive.If>
210
- <SpeakButton />
211
- </>
212
- );
213
- };
214
-
215
- const UserMessage: FC = () => {
216
- return (
217
- <MessagePrimitive.Root className="grid auto-rows-auto grid-cols-[minmax(72px,1fr)_auto] gap-y-2 [&:where(>*)]:col-start-2 w-full max-w-[var(--thread-max-width)] py-4">
218
- <div className="bg-muted text-foreground max-w-[calc(var(--thread-max-width)*0.8)] break-words rounded-3xl px-5 py-2.5 col-start-2 row-start-2">
219
- <UserMessageAttachments />
220
- <MessagePrimitive.Content />
221
- </div>
222
-
223
- <BranchPicker className="col-span-full col-start-1 row-start-3 -mr-1 justify-end" />
224
- </MessagePrimitive.Root>
225
- );
226
- };
227
-
228
- const EditComposer: FC = () => {
229
- return (
230
- <ComposerPrimitive.Root className="bg-muted my-4 flex w-full max-w-[var(--thread-max-width)] flex-col gap-2 rounded-xl">
231
- <ComposerPrimitive.Input className="text-foreground flex h-8 w-full resize-none bg-transparent p-4 pb-0 outline-none" />
232
-
233
- <div className="mx-3 mb-3 flex items-center justify-center gap-2 self-end">
234
- <ComposerPrimitive.Cancel asChild>
235
- <Button variant="ghost">Cancel</Button>
236
- </ComposerPrimitive.Cancel>
237
- <ComposerPrimitive.Send asChild>
238
- <Button>Send</Button>
239
- </ComposerPrimitive.Send>
240
- </div>
241
- </ComposerPrimitive.Root>
242
- );
243
- };
244
-
245
- const Image: FC<ImageContentPart> = ({ image }: ImageContentPart) => {
246
- const baseEndpoint = usePersonaRuntimeEndpoint();
247
- return (
248
- <div className="flex items-center gap-2 border rounded-lg border-muted bg-muted p-4 my-2">
249
- <img src={`${baseEndpoint}/files/${image}`} alt="Image" className="max-h-96 max-w-full rounded-lg object-cover" />
250
207
  </div>
251
208
  );
252
209
  };
253
210
 
254
- const File: FC<FileContentPart> = ({ data, mimeType }) => {
255
- const isImage = useMemo(() => mimeType.startsWith('image/'), [mimeType]);
256
- if (isImage) {
257
- return <Image image={data} type="image" />;
258
- }
259
- const fileName = useMemo(() => {
260
- const parts = data.split('/');
261
- return parts[parts.length - 1];
262
- }, [data]);
263
- const baseEndpoint = usePersonaRuntimeEndpoint();
264
- const getIconByMimeType = (mimeType: string) => {
265
- const iconMap: Record<string, JSX.Element> = {
266
- 'application/pdf': <FileIcon />,
267
- 'image/png': <ImageIcon />,
268
- 'image/jpeg': <ImageIcon />,
269
- 'application/zip': <ArchiveIcon />,
270
- };
271
-
272
- return iconMap[mimeType] || <FileIcon />;
273
- };
274
- const icon = getIconByMimeType(mimeType);
275
-
211
+ const MessageError: FC = () => {
276
212
  return (
277
- <div className="flex items-center gap-2 border rounded-lg border-muted bg-muted p-4 my-2">
278
- <div className="text-muted-foreground">{icon}</div>
279
- <a target="_blank" href={`${baseEndpoint}/files/${data}`} download={fileName} className="text-md text-muted-foreground">
280
- {fileName}
281
- </a>
282
- </div>
213
+ <MessagePrimitive.Error>
214
+ <ErrorPrimitive.Root className="aui-message-error-root mt-2 rounded-md border border-destructive bg-destructive/10 p-3 text-destructive text-sm dark:bg-destructive/5 dark:text-red-200">
215
+ <ErrorPrimitive.Message className="aui-message-error-message line-clamp-2" />
216
+ </ErrorPrimitive.Root>
217
+ </MessagePrimitive.Error>
283
218
  );
284
219
  };
285
220
 
286
221
  const AssistantMessage: FC = () => {
287
222
  return (
288
- <MessagePrimitive.Root className="grid grid-cols-[auto_auto_1fr] grid-rows-[auto_1fr] relative w-full max-w-[var(--thread-max-width)] py-4">
289
- <div className="text-foreground max-w-[calc(var(--thread-max-width)*0.8)] break-words leading-7 col-span-2 col-start-2 row-start-1 my-1.5">
290
- <MessagePrimitive.Content
223
+ <MessagePrimitive.Root
224
+ className="aui-assistant-message-root fade-in slide-in-from-bottom-1 relative mx-auto w-full max-w-(--thread-max-width) animate-in py-4 duration-150 ease-out"
225
+ data-role="assistant"
226
+ >
227
+ <div className="aui-assistant-message-content wrap-break-word mx-2 text-foreground leading-7">
228
+ <MessagePrimitive.Parts
291
229
  components={{
292
230
  Text: MarkdownText,
293
- File,
294
- Reasoning,
295
- tools: {
296
- Fallback: ToolFallback,
297
- },
231
+ tools: { Fallback: ToolFallback },
298
232
  }}
299
233
  />
234
+ <MessageError />
300
235
  </div>
301
236
 
302
- <AssistantActionBar />
303
-
304
- <BranchPicker className="col-start-2 row-start-2 -ml-2 mr-2" />
237
+ <div className="aui-assistant-message-footer mt-2 ml-2 flex">
238
+ <BranchPicker />
239
+ <AssistantActionBar />
240
+ </div>
305
241
  </MessagePrimitive.Root>
306
242
  );
307
243
  };
@@ -312,7 +248,7 @@ const AssistantActionBar: FC = () => {
312
248
  hideWhenRunning
313
249
  autohide="not-last"
314
250
  autohideFloat="single-branch"
315
- className="text-muted-foreground flex gap-1 col-start-3 row-start-2 -ml-1 data-[floating]:bg-background data-[floating]:absolute data-[floating]:rounded-md data-[floating]:border data-[floating]:p-1 data-[floating]:shadow-sm"
251
+ className="aui-assistant-action-bar-root -ml-1 col-start-3 row-start-2 flex gap-1 text-muted-foreground data-floating:absolute data-floating:rounded-md data-floating:border data-floating:bg-background data-floating:p-1 data-floating:shadow-sm"
316
252
  >
317
253
  <ActionBarPrimitive.Copy asChild>
318
254
  <TooltipIconButton tooltip="Copy">
@@ -324,15 +260,90 @@ const AssistantActionBar: FC = () => {
324
260
  </MessagePrimitive.If>
325
261
  </TooltipIconButton>
326
262
  </ActionBarPrimitive.Copy>
263
+ <ActionBarPrimitive.Reload asChild>
264
+ <TooltipIconButton tooltip="Refresh">
265
+ <RefreshCwIcon />
266
+ </TooltipIconButton>
267
+ </ActionBarPrimitive.Reload>
327
268
  </ActionBarPrimitive.Root>
328
269
  );
329
270
  };
330
271
 
331
- const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({ className, ...rest }) => {
272
+ const UserMessage: FC = () => {
273
+ return (
274
+ <MessagePrimitive.Root
275
+ className="aui-user-message-root fade-in slide-in-from-bottom-1 mx-auto grid w-full max-w-(--thread-max-width) animate-in auto-rows-auto grid-cols-[minmax(72px,1fr)_auto] content-start gap-y-2 px-2 py-4 duration-150 ease-out [&:where(>*)]:col-start-2"
276
+ data-role="user"
277
+ >
278
+ <UserMessageAttachments />
279
+
280
+ <div className="aui-user-message-content-wrapper relative col-start-2 min-w-0">
281
+ <div className="aui-user-message-content wrap-break-word rounded-3xl bg-muted px-5 py-2.5 text-foreground">
282
+ <MessagePrimitive.Parts />
283
+ </div>
284
+ <div className="aui-user-action-bar-wrapper -translate-x-full -translate-y-1/2 absolute top-1/2 left-0 pr-2">
285
+ <UserActionBar />
286
+ </div>
287
+ </div>
288
+
289
+ <BranchPicker className="aui-user-branch-picker -mr-1 col-span-full col-start-1 row-start-3 justify-end" />
290
+ </MessagePrimitive.Root>
291
+ );
292
+ };
293
+
294
+ const UserActionBar: FC = () => {
295
+ return (
296
+ <ActionBarPrimitive.Root
297
+ hideWhenRunning
298
+ autohide="not-last"
299
+ className="aui-user-action-bar-root flex flex-col items-end"
300
+ >
301
+ <ActionBarPrimitive.Edit asChild>
302
+ <TooltipIconButton tooltip="Edit" className="aui-user-action-edit p-4">
303
+ <PencilIcon />
304
+ </TooltipIconButton>
305
+ </ActionBarPrimitive.Edit>
306
+ </ActionBarPrimitive.Root>
307
+ );
308
+ };
309
+
310
+ const EditComposer: FC = () => {
311
+ return (
312
+ <MessagePrimitive.Root className="aui-edit-composer-wrapper mx-auto flex w-full max-w-(--thread-max-width) flex-col gap-4 px-2">
313
+ <ComposerPrimitive.Root className="aui-edit-composer-root ml-auto flex w-full max-w-7/8 flex-col rounded-xl bg-muted">
314
+ <ComposerPrimitive.Input
315
+ className="aui-edit-composer-input flex min-h-[60px] w-full resize-none bg-transparent p-4 text-foreground outline-none"
316
+ autoFocus
317
+ />
318
+
319
+ <div className="aui-edit-composer-footer mx-3 mb-3 flex items-center justify-center gap-2 self-end">
320
+ <ComposerPrimitive.Cancel asChild>
321
+ <Button variant="ghost" size="sm" aria-label="Cancel edit">
322
+ Cancel
323
+ </Button>
324
+ </ComposerPrimitive.Cancel>
325
+ <ComposerPrimitive.Send asChild>
326
+ <Button size="sm" aria-label="Update message">
327
+ Update
328
+ </Button>
329
+ </ComposerPrimitive.Send>
330
+ </div>
331
+ </ComposerPrimitive.Root>
332
+ </MessagePrimitive.Root>
333
+ );
334
+ };
335
+
336
+ const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({
337
+ className,
338
+ ...rest
339
+ }) => {
332
340
  return (
333
341
  <BranchPickerPrimitive.Root
334
342
  hideWhenSingleBranch
335
- className={cn('text-muted-foreground inline-flex items-center text-xs', className)}
343
+ className={cn(
344
+ "aui-branch-picker-root -ml-2 mr-2 inline-flex items-center text-muted-foreground text-xs",
345
+ className,
346
+ )}
336
347
  {...rest}
337
348
  >
338
349
  <BranchPickerPrimitive.Previous asChild>
@@ -340,7 +351,7 @@ const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({ className, ...rest
340
351
  <ChevronLeftIcon />
341
352
  </TooltipIconButton>
342
353
  </BranchPickerPrimitive.Previous>
343
- <span className="font-medium">
354
+ <span className="aui-branch-picker-state font-medium">
344
355
  <BranchPickerPrimitive.Number /> / <BranchPickerPrimitive.Count />
345
356
  </span>
346
357
  <BranchPickerPrimitive.Next asChild>
@@ -351,11 +362,3 @@ const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({ className, ...rest
351
362
  </BranchPickerPrimitive.Root>
352
363
  );
353
364
  };
354
-
355
- const CircleStopIcon = () => {
356
- return (
357
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" width="16" height="16">
358
- <rect width="10" height="10" x="3" y="3" rx="2" />
359
- </svg>
360
- );
361
- };