@hanzo/ui 4.5.4 → 4.6.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.
- package/README-MCP.md +3 -3
- package/README.md +229 -0
- package/assets/ai-icons.tsx +207 -0
- package/assets/crypto.tsx +33 -0
- package/assets/file-type-icon.tsx +66 -0
- package/assets/file.tsx +45 -0
- package/assets/general.tsx +2318 -0
- package/assets/hanzo-logo.svg +9 -0
- package/assets/hanzo-logo.tsx +15 -0
- package/assets/index.ts +8 -0
- package/assets/index.tsx +4 -0
- package/assets/llm-provider.tsx +1094 -0
- package/bin/create-registry.js +1 -1
- package/bin/test-mcp.sh +1 -1
- package/bin/update-registry.js +2 -2
- package/blocks/components/content.tsx +1 -1
- package/blocks/components/grid-block/index.tsx +1 -1
- package/blocks/components/screenful-block/content.tsx +1 -1
- package/blocks/components/screenful-block/poster-background.tsx +1 -1
- package/components/index.ts +56 -0
- package/dist/button.d.ts +1 -0
- package/dist/button.js +1 -0
- package/dist/hooks/index.d.ts +7 -0
- package/dist/hooks/index.js +7 -0
- package/dist/hooks/use-click-away.d.ts +2 -0
- package/dist/hooks/use-click-away.js +23 -0
- package/dist/hooks/use-combined-refs.d.ts +3 -0
- package/dist/hooks/use-combined-refs.js +18 -0
- package/dist/hooks/use-copy-clipboard.d.ts +9 -0
- package/dist/hooks/use-copy-clipboard.js +21 -0
- package/dist/hooks/use-debounce.d.ts +1 -0
- package/dist/hooks/use-debounce.js +13 -0
- package/dist/hooks/use-fill-ids.d.ts +8 -0
- package/dist/hooks/use-fill-ids.js +20 -0
- package/dist/hooks/use-map.d.ts +1 -0
- package/dist/hooks/use-map.js +20 -0
- package/dist/hooks/use-measure.d.ts +8 -0
- package/dist/hooks/use-measure.js +25 -0
- package/dist/hooks/use-reverse-video-playback.d.ts +1 -0
- package/dist/hooks/use-reverse-video-playback.js +41 -0
- package/dist/hooks/use-scroll-restoration.d.ts +8 -0
- package/dist/hooks/use-scroll-restoration.js +36 -0
- package/dist/mcp/enhanced-server.js +2 -2
- package/dist/registry/api.d.ts +1 -1
- package/dist/registry/api.js +3 -3
- package/dist/registry/index.d.ts +48 -48
- package/dist/registry/index.js +3 -3
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +1 -0
- package/helpers/file.ts +33 -0
- package/helpers/memoization.ts +40 -0
- package/package.json +27 -6
- package/primitives/accordion.tsx +53 -45
- package/primitives/alert-dialog.tsx +185 -0
- package/primitives/alert.tsx +74 -0
- package/primitives/apply-typography.tsx +1 -1
- package/primitives/avatar.tsx +37 -29
- package/primitives/background-beams.tsx +142 -0
- package/primitives/badge.tsx +27 -19
- package/primitives/breadcrumb.tsx +77 -62
- package/primitives/button.tsx +69 -72
- package/primitives/card.tsx +73 -59
- package/primitives/chat/chat-input-area.tsx +87 -0
- package/primitives/chat/chat-input.tsx +71 -0
- package/primitives/chat/files-preview.tsx +330 -0
- package/primitives/chat/index.ts +6 -0
- package/primitives/chat/json-form.tsx +8 -0
- package/primitives/chat/message-list.tsx +307 -0
- package/primitives/chat/message.tsx +569 -0
- package/primitives/chat/sqlite-preview.tsx +215 -0
- package/primitives/checkbox.tsx +18 -19
- package/primitives/collapsible.tsx +9 -0
- package/primitives/command.tsx +75 -83
- package/primitives/context-menu.tsx +115 -109
- package/primitives/copy-to-clipboard-icon.tsx +60 -0
- package/primitives/dialog-video-controller.tsx +1 -1
- package/primitives/dialog.tsx +111 -145
- package/primitives/dot-pattern.tsx +57 -0
- package/primitives/dots-loader.tsx +13 -0
- package/primitives/drawer.tsx +59 -87
- package/primitives/dropdown-menu.tsx +199 -0
- package/primitives/error-message.tsx +19 -0
- package/primitives/file-uploader.tsx +200 -0
- package/primitives/form.tsx +92 -87
- package/primitives/hover-card.tsx +28 -0
- package/primitives/icons/github.tsx +1 -1
- package/primitives/icons/youtube-logo.tsx +1 -1
- package/primitives/index-common.ts +121 -42
- package/primitives/index-next.ts +3 -1
- package/primitives/input.tsx +115 -20
- package/primitives/label.tsx +15 -23
- package/primitives/loading-spinner.tsx +1 -1
- package/primitives/markdown-preview.tsx +609 -0
- package/primitives/mermaid.tsx +196 -0
- package/primitives/next/link-element.tsx +1 -1
- package/primitives/next/mdx-link.tsx +1 -1
- package/primitives/pagination.tsx +117 -0
- package/primitives/popover.tsx +20 -25
- package/primitives/pretty-json-print.tsx +28 -0
- package/primitives/progress.tsx +14 -15
- package/primitives/prompt-textarea.tsx +72 -0
- package/primitives/qr-code.tsx +112 -0
- package/primitives/radio-group.tsx +25 -39
- package/primitives/resizable.tsx +47 -0
- package/primitives/scroll-area.tsx +35 -25
- package/primitives/search-input.tsx +66 -0
- package/primitives/select.tsx +62 -109
- package/primitives/separator.tsx +22 -26
- package/primitives/sheet.tsx +78 -117
- package/primitives/skeleton.tsx +13 -16
- package/primitives/slider.tsx +50 -60
- package/primitives/stepper.tsx +272 -0
- package/primitives/switch.tsx +14 -23
- package/primitives/table.tsx +65 -77
- package/primitives/tabs.tsx +29 -39
- package/primitives/text-link.tsx +25 -0
- package/primitives/textarea.tsx +61 -0
- package/primitives/textfield.tsx +75 -0
- package/primitives/toast.tsx +30 -0
- package/primitives/toggle-group.tsx +33 -33
- package/primitives/toggle.tsx +22 -51
- package/primitives/tooltip.tsx +37 -38
- package/registry.json +1 -1
- package/src/button.ts +1 -0
- package/src/hooks/index.ts +7 -0
- package/src/hooks/use-click-away.ts +31 -0
- package/src/hooks/use-combined-refs.ts +22 -0
- package/src/hooks/use-copy-clipboard.ts +30 -0
- package/src/hooks/use-debounce.ts +17 -0
- package/src/hooks/use-fill-ids.ts +25 -0
- package/src/hooks/use-map.ts +26 -0
- package/src/hooks/use-measure.ts +42 -0
- package/src/hooks/use-reverse-video-playback.ts +43 -0
- package/src/hooks/use-scroll-restoration.ts +50 -0
- package/src/mcp/README.md +1 -1
- package/src/mcp/enhanced-server.ts +2 -2
- package/src/registry/api.ts +3 -3
- package/src/registry/index.ts +3 -3
- package/src/utils.ts +1 -0
- package/style/theme-provider.tsx +1 -1
- package/test-imports.mjs +19 -0
- package/types/animation-def.ts +1 -1
- package/types/button-def.ts +1 -1
- package/types/index.ts +1 -1
- package/util/blob.ts +28 -0
- package/util/copy-to-clipboard.ts +17 -0
- package/util/create-shadow-root.ts +22 -0
- package/util/date.ts +83 -0
- package/util/debounce.ts +11 -0
- package/util/file.ts +15 -0
- package/util/format-and-abbreviate-as-currency.ts +1 -1
- package/util/format-text.ts +33 -0
- package/util/index.ts +9 -78
- package/util/timing.ts +3 -0
- package/util/toasts.tsx +17 -0
- package/utils.ts +9 -0
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
2
|
+
import { useTranslation } from '@hanzo_network/hanzo-i18n';
|
|
3
|
+
import {
|
|
4
|
+
type ToolArgs,
|
|
5
|
+
ToolStatusType,
|
|
6
|
+
} from '@hanzo_network/hanzo-message-ts/api/general/types';
|
|
7
|
+
import {
|
|
8
|
+
type Artifact,
|
|
9
|
+
type FormattedMessage,
|
|
10
|
+
} from '@hanzo_network/hanzo-node-state/v2/queries/getChatConversation/types';
|
|
11
|
+
import { format } from 'date-fns';
|
|
12
|
+
import { AnimatePresence, motion } from 'framer-motion';
|
|
13
|
+
import { Edit3, Loader2, RotateCcw, XCircle } from 'lucide-react';
|
|
14
|
+
import { InfoCircleIcon } from 'primereact/icons/infocircle';
|
|
15
|
+
import React, { Fragment, memo, useEffect, useMemo, useState } from 'react';
|
|
16
|
+
import { useForm } from 'react-hook-form';
|
|
17
|
+
import { Link } from 'react-router';
|
|
18
|
+
import { z } from 'zod';
|
|
19
|
+
|
|
20
|
+
import { appIcon, ReactJsIcon, ToolsIcon } from '../../assets';
|
|
21
|
+
import { formatText } from '../../helpers/format-text';
|
|
22
|
+
import { cn } from '../src/utils';
|
|
23
|
+
import {
|
|
24
|
+
Accordion,
|
|
25
|
+
AccordionContent,
|
|
26
|
+
AccordionItem,
|
|
27
|
+
AccordionTrigger,
|
|
28
|
+
} from '../accordion';
|
|
29
|
+
import { Avatar, AvatarFallback } from '../avatar';
|
|
30
|
+
import { Button } from '../button';
|
|
31
|
+
import { Card, CardContent } from '../card';
|
|
32
|
+
import { CopyToClipboardIcon } from '../copy-to-clipboard-icon';
|
|
33
|
+
import { DotsLoader } from '../dots-loader';
|
|
34
|
+
import { Form, FormField } from '../form';
|
|
35
|
+
import { MarkdownText } from '../markdown-preview';
|
|
36
|
+
import { PrettyJsonPrint } from '../pretty-json-print';
|
|
37
|
+
import {
|
|
38
|
+
Tooltip,
|
|
39
|
+
TooltipContent,
|
|
40
|
+
TooltipPortal,
|
|
41
|
+
TooltipProvider,
|
|
42
|
+
TooltipTrigger,
|
|
43
|
+
} from '../tooltip';
|
|
44
|
+
import { ChatInputArea } from './chat-input-area';
|
|
45
|
+
import { FileList } from './files-preview';
|
|
46
|
+
// import { PythonCodeRunner } from './python-code-runner/python-code-runner';
|
|
47
|
+
|
|
48
|
+
export const extractErrorPropertyOrContent = (
|
|
49
|
+
content: string,
|
|
50
|
+
property: 'error' | 'error_message',
|
|
51
|
+
) => {
|
|
52
|
+
try {
|
|
53
|
+
const parsedContent = JSON.parse(content);
|
|
54
|
+
if (property in parsedContent) {
|
|
55
|
+
return parsedContent[property];
|
|
56
|
+
}
|
|
57
|
+
} catch {
|
|
58
|
+
/* ignore */
|
|
59
|
+
}
|
|
60
|
+
return String(content);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const containsPythonCode = (content: string): boolean => {
|
|
64
|
+
const pythonCodeRegex = /```python\s[\s\S]*?```/;
|
|
65
|
+
return pythonCodeRegex.test(content);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
type MessageProps = {
|
|
69
|
+
messageId: string;
|
|
70
|
+
isPending?: boolean;
|
|
71
|
+
message: FormattedMessage;
|
|
72
|
+
handleRetryMessage?: () => void;
|
|
73
|
+
disabledRetry?: boolean;
|
|
74
|
+
disabledEdit?: boolean;
|
|
75
|
+
handleEditMessage?: (message: string) => void;
|
|
76
|
+
messageExtra?: React.ReactNode;
|
|
77
|
+
|
|
78
|
+
artifacts?: Artifact[];
|
|
79
|
+
artifact?: Artifact;
|
|
80
|
+
hidePythonExecution?: boolean;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const actionBar = {
|
|
84
|
+
rest: {
|
|
85
|
+
opacity: 0,
|
|
86
|
+
scale: 0.8,
|
|
87
|
+
transition: {
|
|
88
|
+
type: 'spring',
|
|
89
|
+
bounce: 0,
|
|
90
|
+
duration: 0.3,
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
hover: {
|
|
94
|
+
opacity: 1,
|
|
95
|
+
scale: 1,
|
|
96
|
+
transition: {
|
|
97
|
+
type: 'spring',
|
|
98
|
+
duration: 0.3,
|
|
99
|
+
bounce: 0,
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
type ArtifactProps = {
|
|
105
|
+
title: string;
|
|
106
|
+
type: string;
|
|
107
|
+
identifier: string;
|
|
108
|
+
loading?: boolean;
|
|
109
|
+
isSelected?: boolean;
|
|
110
|
+
onArtifactClick?: () => void;
|
|
111
|
+
};
|
|
112
|
+
const ArtifactCard = ({
|
|
113
|
+
title,
|
|
114
|
+
loading = true,
|
|
115
|
+
onArtifactClick,
|
|
116
|
+
isSelected,
|
|
117
|
+
identifier,
|
|
118
|
+
}: ArtifactProps) => (
|
|
119
|
+
<Card
|
|
120
|
+
className={cn(
|
|
121
|
+
'w-full max-w-sm border border-gray-100',
|
|
122
|
+
isSelected && 'border-gray-50 bg-gray-300',
|
|
123
|
+
)}
|
|
124
|
+
onClick={() => {
|
|
125
|
+
onArtifactClick?.();
|
|
126
|
+
}}
|
|
127
|
+
role="button"
|
|
128
|
+
>
|
|
129
|
+
<CardContent className="flex items-center gap-1 p-1 py-1.5">
|
|
130
|
+
<div className="rounded-md p-2">
|
|
131
|
+
{loading ? (
|
|
132
|
+
<Loader2 className="text-text-secondary h-5 w-5 animate-spin" />
|
|
133
|
+
) : (
|
|
134
|
+
<ReactJsIcon
|
|
135
|
+
className={cn(isSelected ? 'text-gray-50' : 'text-text-secondary')}
|
|
136
|
+
/>
|
|
137
|
+
)}
|
|
138
|
+
</div>
|
|
139
|
+
<div className="flex flex-1 flex-col gap-0.5">
|
|
140
|
+
<p className="!mb-0 line-clamp-1 text-sm font-medium text-gray-50">
|
|
141
|
+
{title}
|
|
142
|
+
</p>
|
|
143
|
+
<p className="text-text-secondary !mb-0 text-xs">
|
|
144
|
+
{loading ? 'Generating...' : 'Click to preview'}
|
|
145
|
+
</p>
|
|
146
|
+
</div>
|
|
147
|
+
</CardContent>
|
|
148
|
+
</Card>
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
export const editMessageFormSchema = z.object({
|
|
152
|
+
message: z.string().min(1),
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
type EditMessageFormSchema = z.infer<typeof editMessageFormSchema>;
|
|
156
|
+
|
|
157
|
+
const MessageBase = ({
|
|
158
|
+
message,
|
|
159
|
+
messageId,
|
|
160
|
+
isPending,
|
|
161
|
+
handleRetryMessage,
|
|
162
|
+
disabledRetry,
|
|
163
|
+
disabledEdit,
|
|
164
|
+
handleEditMessage,
|
|
165
|
+
hidePythonExecution,
|
|
166
|
+
}: MessageProps) => {
|
|
167
|
+
const { t } = useTranslation();
|
|
168
|
+
|
|
169
|
+
const [editing, setEditing] = useState(false);
|
|
170
|
+
|
|
171
|
+
const editMessageForm = useForm<EditMessageFormSchema>({
|
|
172
|
+
resolver: zodResolver(editMessageFormSchema),
|
|
173
|
+
defaultValues: {
|
|
174
|
+
message: message.content,
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const { message: currentMessage } = editMessageForm.watch();
|
|
179
|
+
|
|
180
|
+
const onSubmit = async (data: z.infer<typeof editMessageFormSchema>) => {
|
|
181
|
+
if (message.role === 'user') {
|
|
182
|
+
handleEditMessage?.(data.message);
|
|
183
|
+
setEditing(false);
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
useEffect(() => {
|
|
188
|
+
if (message.role === 'user') {
|
|
189
|
+
editMessageForm.reset({ message: message.content });
|
|
190
|
+
}
|
|
191
|
+
}, [editMessageForm, message.content]);
|
|
192
|
+
|
|
193
|
+
const pythonCode = useMemo(() => {
|
|
194
|
+
if (containsPythonCode(message.content)) {
|
|
195
|
+
const match = message.content.match(/```python\s([\s\S]*?)```/);
|
|
196
|
+
return match ? match[1] : null;
|
|
197
|
+
}
|
|
198
|
+
return null;
|
|
199
|
+
}, [message.content]);
|
|
200
|
+
|
|
201
|
+
return (
|
|
202
|
+
<motion.div
|
|
203
|
+
animate="rest"
|
|
204
|
+
className="pb-10"
|
|
205
|
+
data-testid={`message-${
|
|
206
|
+
message.role === 'user' ? 'local' : 'remote'
|
|
207
|
+
}-${message.messageId}`}
|
|
208
|
+
id={message.messageId}
|
|
209
|
+
initial="rest"
|
|
210
|
+
whileHover="hover"
|
|
211
|
+
>
|
|
212
|
+
<div
|
|
213
|
+
className={cn(
|
|
214
|
+
'relative flex flex-row space-x-2',
|
|
215
|
+
message.role === 'user' &&
|
|
216
|
+
'mr-0 ml-auto flex-row-reverse space-x-reverse',
|
|
217
|
+
message.role === 'assistant' && 'mr-auto ml-0 flex-row items-end',
|
|
218
|
+
)}
|
|
219
|
+
>
|
|
220
|
+
<a href={`#${messageId}`}>
|
|
221
|
+
<Avatar className={cn('mt-1 h-8 w-8')}>
|
|
222
|
+
{message.role === 'assistant' ? (
|
|
223
|
+
<img alt="Hanzo AI" src={appIcon} />
|
|
224
|
+
) : null}
|
|
225
|
+
</Avatar>
|
|
226
|
+
</a>
|
|
227
|
+
<div
|
|
228
|
+
className={cn(
|
|
229
|
+
'container flex flex-col overflow-hidden bg-transparent text-sm text-white',
|
|
230
|
+
editing && 'w-full py-1',
|
|
231
|
+
)}
|
|
232
|
+
>
|
|
233
|
+
{editing ? (
|
|
234
|
+
<Form {...editMessageForm}>
|
|
235
|
+
<form
|
|
236
|
+
className="relative flex items-center"
|
|
237
|
+
onSubmit={editMessageForm.handleSubmit(onSubmit)}
|
|
238
|
+
>
|
|
239
|
+
<div className="w-full">
|
|
240
|
+
<FormField
|
|
241
|
+
control={editMessageForm.control}
|
|
242
|
+
name="message"
|
|
243
|
+
render={({ field }) => (
|
|
244
|
+
<ChatInputArea
|
|
245
|
+
bottomAddons={
|
|
246
|
+
<div className="flex w-full items-center justify-between px-1">
|
|
247
|
+
<div className="text-text-secondary flex items-center gap-1 text-xs">
|
|
248
|
+
<InfoCircleIcon className="text-text-secondary h-3 w-3" />
|
|
249
|
+
<span>{t('chat.editMessage.warning')}</span>
|
|
250
|
+
</div>
|
|
251
|
+
<div className="flex items-center gap-2">
|
|
252
|
+
<Button
|
|
253
|
+
className="min-w-[90px] rounded-lg"
|
|
254
|
+
onClick={() => setEditing(false)}
|
|
255
|
+
size="sm"
|
|
256
|
+
variant="outline"
|
|
257
|
+
>
|
|
258
|
+
{t('common.cancel')}
|
|
259
|
+
</Button>
|
|
260
|
+
<Button
|
|
261
|
+
className="min-w-[90px] rounded-lg"
|
|
262
|
+
disabled={!currentMessage}
|
|
263
|
+
onClick={editMessageForm.handleSubmit(onSubmit)}
|
|
264
|
+
size="sm"
|
|
265
|
+
>
|
|
266
|
+
{t('common.send')}
|
|
267
|
+
</Button>
|
|
268
|
+
</div>
|
|
269
|
+
</div>
|
|
270
|
+
}
|
|
271
|
+
onChange={field.onChange}
|
|
272
|
+
onSubmit={editMessageForm.handleSubmit(onSubmit)}
|
|
273
|
+
value={field.value}
|
|
274
|
+
/>
|
|
275
|
+
)}
|
|
276
|
+
/>
|
|
277
|
+
</div>
|
|
278
|
+
</form>
|
|
279
|
+
</Form>
|
|
280
|
+
) : (
|
|
281
|
+
<Fragment>
|
|
282
|
+
<div
|
|
283
|
+
className={cn(
|
|
284
|
+
'relative mt-1 flex flex-col rounded-lg bg-black/40 px-3.5 pt-3 text-sm text-white',
|
|
285
|
+
message.role === 'user'
|
|
286
|
+
? 'rounded-tr-none bg-gray-300'
|
|
287
|
+
: 'rounded-bl-none border-none bg-gray-200',
|
|
288
|
+
!message.content ? 'pb-3' : 'pb-4',
|
|
289
|
+
editing && 'w-full py-1',
|
|
290
|
+
message.role === 'assistant' &&
|
|
291
|
+
isPending &&
|
|
292
|
+
'relative overflow-hidden pb-4 before:absolute before:right-0 before:bottom-0 before:left-0 before:h-10 before:animate-pulse before:bg-gradient-to-l before:from-gray-200 before:to-gray-200/10',
|
|
293
|
+
)}
|
|
294
|
+
>
|
|
295
|
+
{message.role === 'assistant' &&
|
|
296
|
+
message.toolCalls &&
|
|
297
|
+
message.toolCalls.length > 0 && (
|
|
298
|
+
<Accordion
|
|
299
|
+
className="space-y-1.5 self-baseline pb-3"
|
|
300
|
+
type="multiple"
|
|
301
|
+
>
|
|
302
|
+
{message.toolCalls.map((tool, index) => {
|
|
303
|
+
return (
|
|
304
|
+
<AccordionItem
|
|
305
|
+
className="bg-bg-secondary overflow-hidden rounded-lg"
|
|
306
|
+
disabled={tool.status !== ToolStatusType.Complete}
|
|
307
|
+
key={`${tool.name}-${index}`}
|
|
308
|
+
value={tool.name}
|
|
309
|
+
>
|
|
310
|
+
<AccordionTrigger
|
|
311
|
+
className={cn(
|
|
312
|
+
'min-w-[10rem] py-0 pr-2 no-underline hover:no-underline',
|
|
313
|
+
'hover:bg-bg-default [&[data-state=open]]:bg-bg-default transition-colors',
|
|
314
|
+
tool.status !== ToolStatusType.Complete &&
|
|
315
|
+
'[&>svg]:hidden',
|
|
316
|
+
)}
|
|
317
|
+
>
|
|
318
|
+
<ToolCard
|
|
319
|
+
args={tool.args}
|
|
320
|
+
name={tool.name}
|
|
321
|
+
status={tool.status ?? ToolStatusType.Complete}
|
|
322
|
+
toolRouterKey={tool.toolRouterKey}
|
|
323
|
+
/>
|
|
324
|
+
</AccordionTrigger>
|
|
325
|
+
<AccordionContent className="bg-gray-450 flex flex-col gap-1 overflow-x-scroll rounded-b-lg px-3 pt-2 pb-3 text-xs">
|
|
326
|
+
{Object.keys(tool.args).length > 0 && (
|
|
327
|
+
<span className="font-medium text-white">
|
|
328
|
+
{tool.name}(
|
|
329
|
+
{Object.keys(tool.args).length > 0 && (
|
|
330
|
+
<span className="text-text-secondary font-mono font-medium">
|
|
331
|
+
{JSON.stringify(tool.args)}
|
|
332
|
+
</span>
|
|
333
|
+
)}
|
|
334
|
+
)
|
|
335
|
+
</span>
|
|
336
|
+
)}
|
|
337
|
+
{tool.result && (
|
|
338
|
+
<div>
|
|
339
|
+
<span>Response:</span>
|
|
340
|
+
<span className="text-text-secondary font-mono break-all">
|
|
341
|
+
<PrettyJsonPrint json={tool.result} />
|
|
342
|
+
</span>
|
|
343
|
+
</div>
|
|
344
|
+
)}
|
|
345
|
+
</AccordionContent>
|
|
346
|
+
</AccordionItem>
|
|
347
|
+
);
|
|
348
|
+
})}
|
|
349
|
+
</Accordion>
|
|
350
|
+
)}
|
|
351
|
+
{message.role === 'assistant' && (
|
|
352
|
+
<MarkdownText
|
|
353
|
+
content={extractErrorPropertyOrContent(
|
|
354
|
+
message.content,
|
|
355
|
+
'error_message',
|
|
356
|
+
)}
|
|
357
|
+
isRunning={
|
|
358
|
+
!!message.content && message.status.type === 'running'
|
|
359
|
+
}
|
|
360
|
+
/>
|
|
361
|
+
)}
|
|
362
|
+
|
|
363
|
+
{message.role === 'user' && (
|
|
364
|
+
<div className="whitespace-pre-line">{message.content}</div>
|
|
365
|
+
)}
|
|
366
|
+
|
|
367
|
+
{message.role === 'assistant' &&
|
|
368
|
+
message.status.type === 'running' &&
|
|
369
|
+
message.content === '' && (
|
|
370
|
+
<div className="pt-1.5 whitespace-pre-line">
|
|
371
|
+
<DotsLoader />
|
|
372
|
+
</div>
|
|
373
|
+
)}
|
|
374
|
+
{/*{pythonCode && !hidePythonExecution && (*/}
|
|
375
|
+
{/* <PythonCodeRunner code={pythonCode} />*/}
|
|
376
|
+
{/*)}*/}
|
|
377
|
+
{message.role === 'user' && !!message.attachments.length && (
|
|
378
|
+
<FileList
|
|
379
|
+
className="mt-2 max-w-[70vw] min-w-[200px]"
|
|
380
|
+
files={message.attachments}
|
|
381
|
+
/>
|
|
382
|
+
)}
|
|
383
|
+
</div>
|
|
384
|
+
{!isPending && (
|
|
385
|
+
<motion.div
|
|
386
|
+
className={cn(
|
|
387
|
+
'absolute -bottom-[34px] flex items-center justify-end gap-3',
|
|
388
|
+
message.role === 'user'
|
|
389
|
+
? 'right-10 flex-row-reverse'
|
|
390
|
+
: 'left-10 flex-row',
|
|
391
|
+
)}
|
|
392
|
+
variants={actionBar}
|
|
393
|
+
>
|
|
394
|
+
<div className="flex items-center gap-1.5">
|
|
395
|
+
{message.role === 'user' && !disabledEdit && (
|
|
396
|
+
<TooltipProvider delayDuration={0}>
|
|
397
|
+
<Tooltip>
|
|
398
|
+
<TooltipTrigger asChild>
|
|
399
|
+
<button
|
|
400
|
+
className={cn(
|
|
401
|
+
'text-text-secondary flex h-7 w-7 items-center justify-center rounded-lg border border-gray-200 bg-transparent transition-colors hover:bg-gray-300 hover:text-white [&>svg]:h-3 [&>svg]:w-3',
|
|
402
|
+
)}
|
|
403
|
+
onClick={() => {
|
|
404
|
+
setEditing(true);
|
|
405
|
+
}}
|
|
406
|
+
>
|
|
407
|
+
<Edit3 />
|
|
408
|
+
</button>
|
|
409
|
+
</TooltipTrigger>
|
|
410
|
+
<TooltipPortal>
|
|
411
|
+
<TooltipContent>
|
|
412
|
+
<p>{t('common.editMessage')}</p>
|
|
413
|
+
</TooltipContent>
|
|
414
|
+
</TooltipPortal>
|
|
415
|
+
</Tooltip>
|
|
416
|
+
</TooltipProvider>
|
|
417
|
+
)}
|
|
418
|
+
{message.role === 'assistant' && !disabledRetry && (
|
|
419
|
+
<TooltipProvider delayDuration={0}>
|
|
420
|
+
<Tooltip>
|
|
421
|
+
<TooltipTrigger asChild>
|
|
422
|
+
<button
|
|
423
|
+
className={cn(
|
|
424
|
+
'text-text-secondary flex h-7 w-7 items-center justify-center rounded-lg border border-gray-200 bg-transparent transition-colors hover:bg-gray-300 hover:text-white [&>svg]:h-3 [&>svg]:w-3',
|
|
425
|
+
)}
|
|
426
|
+
onClick={handleRetryMessage}
|
|
427
|
+
>
|
|
428
|
+
<RotateCcw />
|
|
429
|
+
</button>
|
|
430
|
+
</TooltipTrigger>
|
|
431
|
+
<TooltipPortal>
|
|
432
|
+
<TooltipContent>
|
|
433
|
+
<p>{t('common.retry')}</p>
|
|
434
|
+
</TooltipContent>
|
|
435
|
+
</TooltipPortal>
|
|
436
|
+
</Tooltip>
|
|
437
|
+
</TooltipProvider>
|
|
438
|
+
)}
|
|
439
|
+
<TooltipProvider delayDuration={0}>
|
|
440
|
+
<Tooltip>
|
|
441
|
+
<TooltipTrigger asChild>
|
|
442
|
+
<div>
|
|
443
|
+
<CopyToClipboardIcon
|
|
444
|
+
className={cn(
|
|
445
|
+
'text-text-secondary h-7 w-7 border border-gray-200 bg-transparent hover:bg-gray-300 [&>svg]:h-3 [&>svg]:w-3',
|
|
446
|
+
)}
|
|
447
|
+
string={extractErrorPropertyOrContent(
|
|
448
|
+
message.content,
|
|
449
|
+
'error_message',
|
|
450
|
+
)}
|
|
451
|
+
/>
|
|
452
|
+
</div>
|
|
453
|
+
</TooltipTrigger>
|
|
454
|
+
<TooltipPortal>
|
|
455
|
+
<TooltipContent>
|
|
456
|
+
<p>{t('common.copy')}</p>
|
|
457
|
+
</TooltipContent>
|
|
458
|
+
</TooltipPortal>
|
|
459
|
+
</Tooltip>
|
|
460
|
+
</TooltipProvider>
|
|
461
|
+
</div>
|
|
462
|
+
<div
|
|
463
|
+
className={cn(
|
|
464
|
+
'text-text-secondary flex items-center gap-1.5 text-xs',
|
|
465
|
+
)}
|
|
466
|
+
>
|
|
467
|
+
<span>
|
|
468
|
+
{format(new Date(message?.createdAt ?? ''), 'p')}
|
|
469
|
+
</span>
|
|
470
|
+
{message.role === 'assistant' && message?.metadata?.tps && (
|
|
471
|
+
<>
|
|
472
|
+
{' '}
|
|
473
|
+
⋅
|
|
474
|
+
<span>
|
|
475
|
+
{Math.round(Number(message?.metadata?.tps) * 10) / 10}{' '}
|
|
476
|
+
tokens/s
|
|
477
|
+
</span>
|
|
478
|
+
</>
|
|
479
|
+
)}
|
|
480
|
+
</div>
|
|
481
|
+
</motion.div>
|
|
482
|
+
)}
|
|
483
|
+
</Fragment>
|
|
484
|
+
)}
|
|
485
|
+
</div>
|
|
486
|
+
</div>
|
|
487
|
+
</motion.div>
|
|
488
|
+
);
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
export const Message = memo(
|
|
492
|
+
MessageBase,
|
|
493
|
+
(prev, next) =>
|
|
494
|
+
prev.messageId === next.messageId &&
|
|
495
|
+
prev.message.content === next.message.content &&
|
|
496
|
+
prev.artifacts?.length === next.artifacts?.length &&
|
|
497
|
+
prev.artifact === next.artifact,
|
|
498
|
+
);
|
|
499
|
+
|
|
500
|
+
const variants = {
|
|
501
|
+
initial: { opacity: 0, y: -25 },
|
|
502
|
+
visible: { opacity: 1, y: 0 },
|
|
503
|
+
exit: { opacity: 0, y: 25 },
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
export function ToolCard({
|
|
507
|
+
name,
|
|
508
|
+
// args,
|
|
509
|
+
status,
|
|
510
|
+
toolRouterKey,
|
|
511
|
+
}: {
|
|
512
|
+
args: ToolArgs;
|
|
513
|
+
status: ToolStatusType;
|
|
514
|
+
name: string;
|
|
515
|
+
toolRouterKey: string;
|
|
516
|
+
}) {
|
|
517
|
+
const renderStatus = () => {
|
|
518
|
+
if (status === ToolStatusType.Complete) {
|
|
519
|
+
return <ToolsIcon className="text-brand size-full" />;
|
|
520
|
+
}
|
|
521
|
+
if (status === ToolStatusType.Incomplete) {
|
|
522
|
+
return <XCircle className="text-text-secondary size-full" />;
|
|
523
|
+
}
|
|
524
|
+
if (status === ToolStatusType.RequiresAction) {
|
|
525
|
+
return <InfoCircleIcon className="text-text-secondary size-full" />;
|
|
526
|
+
}
|
|
527
|
+
return <Loader2 className="text-brand size-full animate-spin" />;
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
const renderLabelText = () => {
|
|
531
|
+
if (status === ToolStatusType.Complete) {
|
|
532
|
+
return 'Tool Used';
|
|
533
|
+
}
|
|
534
|
+
if (status === ToolStatusType.Incomplete) {
|
|
535
|
+
return 'Incomplete';
|
|
536
|
+
}
|
|
537
|
+
if (status === ToolStatusType.RequiresAction) {
|
|
538
|
+
return 'Requires Action';
|
|
539
|
+
}
|
|
540
|
+
return 'Processing Tool';
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
return (
|
|
544
|
+
<AnimatePresence initial={false} mode="popLayout">
|
|
545
|
+
<motion.div
|
|
546
|
+
animate="visible"
|
|
547
|
+
exit="exit"
|
|
548
|
+
initial="initial"
|
|
549
|
+
transition={{ type: 'spring', duration: 0.3, bounce: 0 }}
|
|
550
|
+
variants={variants}
|
|
551
|
+
>
|
|
552
|
+
<div className="flex items-center gap-1 p-[5px]">
|
|
553
|
+
<div className="size-7 shrink-0 px-1.5">{renderStatus()}</div>
|
|
554
|
+
<div className="flex items-center gap-1">
|
|
555
|
+
<span className="text-text-secondary text-em-sm">
|
|
556
|
+
{renderLabelText()}
|
|
557
|
+
</span>
|
|
558
|
+
<Link
|
|
559
|
+
className="text-gray-white text-em-sm font-semibold hover:underline"
|
|
560
|
+
to={`/tools/${toolRouterKey}`}
|
|
561
|
+
>
|
|
562
|
+
{formatText(name)}
|
|
563
|
+
</Link>
|
|
564
|
+
</div>
|
|
565
|
+
</div>
|
|
566
|
+
</motion.div>
|
|
567
|
+
</AnimatePresence>
|
|
568
|
+
);
|
|
569
|
+
}
|