@surph_ai/sdk 0.0.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.
Files changed (70) hide show
  1. package/auth/index.js +147 -0
  2. package/base/vinely-base/.nvmrc +1 -0
  3. package/base/vinely-base/README.md +30 -0
  4. package/base/vinely-base/app.js +5 -0
  5. package/base/vinely-base/declarations.d.ts +1 -0
  6. package/base/vinely-base/package.json +103 -0
  7. package/base/vinely-base/public/css/globals-2.css +3699 -0
  8. package/base/vinely-base/public/css/globals.css +3695 -0
  9. package/base/vinely-base/public/js/app.js +1 -0
  10. package/base/vinely-base/public/js/jquery.js +2 -0
  11. package/base/vinely-base/scratch.js +21 -0
  12. package/base/vinely-base/src/client/components/app-sidebar.tsx +59 -0
  13. package/base/vinely-base/src/client/components/assistant-ui/markdown-text.tsx +153 -0
  14. package/base/vinely-base/src/client/components/assistant-ui/thread-list.tsx +67 -0
  15. package/base/vinely-base/src/client/components/assistant-ui/thread.tsx +666 -0
  16. package/base/vinely-base/src/client/components/assistant-ui/tool-fallback.tsx +44 -0
  17. package/base/vinely-base/src/client/components/assistant-ui/tooltip-icon-button.tsx +44 -0
  18. package/base/vinely-base/src/client/components/ui/breadcrumb.tsx +109 -0
  19. package/base/vinely-base/src/client/components/ui/button.tsx +59 -0
  20. package/base/vinely-base/src/client/components/ui/input.tsx +21 -0
  21. package/base/vinely-base/src/client/components/ui/separator.tsx +28 -0
  22. package/base/vinely-base/src/client/components/ui/sheet.tsx +139 -0
  23. package/base/vinely-base/src/client/components/ui/sidebar.tsx +726 -0
  24. package/base/vinely-base/src/client/components/ui/skeleton.tsx +14 -0
  25. package/base/vinely-base/src/client/components/ui/tooltip.tsx +61 -0
  26. package/base/vinely-base/src/client/components/vines-sidebar.tsx +182 -0
  27. package/base/vinely-base/src/client/contexts/SessionContext.tsx +28 -0
  28. package/base/vinely-base/src/client/contexts/SessionReducer.tsx +32 -0
  29. package/base/vinely-base/src/client/hooks/use-mobile.ts +19 -0
  30. package/base/vinely-base/src/client/index.tsx +154 -0
  31. package/base/vinely-base/src/client/lib/API.ts +155 -0
  32. package/base/vinely-base/src/client/lib/RPC.ts +49 -0
  33. package/base/vinely-base/src/client/lib/utils.ts +96 -0
  34. package/base/vinely-base/src/server/index.ts +63 -0
  35. package/base/vinely-base/src/server/routes/api.ts +38 -0
  36. package/base/vinely-base/src/server/routes/chat.ts +133 -0
  37. package/base/vinely-base/src/server/routes/main.ts +36 -0
  38. package/base/vinely-base/src/server/routes/mcp.ts +52 -0
  39. package/base/vinely-base/src/server/routes/rpc.ts +64 -0
  40. package/base/vinely-base/src/server/routes/thread.ts +44 -0
  41. package/base/vinely-base/src/server/utils/APIMgr.ts +156 -0
  42. package/base/vinely-base/src/server/utils/Auth.ts +235 -0
  43. package/base/vinely-base/src/server/utils/fetchManifest.ts +15 -0
  44. package/base/vinely-base/src/server/utils/mcp-auth.ts +251 -0
  45. package/base/vinely-base/tsconfig.json +16 -0
  46. package/base/vinely-base/views/landing.html +187 -0
  47. package/base/vinely-base/views/partials/imports.dev.html +3 -0
  48. package/base/vinely-base/webpack.config.js +112 -0
  49. package/deploy/Dockerfile +9 -0
  50. package/deploy/index.js +244 -0
  51. package/index.js +266 -0
  52. package/package.json +28 -0
  53. package/scaffold/app/gulpfile.js +12 -0
  54. package/scaffold/app/package.json +24 -0
  55. package/scaffold/app/src/index.js +14 -0
  56. package/scaffold/index.js +171 -0
  57. package/scaffold/project/README.md +28 -0
  58. package/scaffold/project/app.js +20 -0
  59. package/scaffold/project/apps/README.md +1 -0
  60. package/scaffold/project/env.txt +4 -0
  61. package/scaffold/project/package-lock.json +2459 -0
  62. package/scaffold/project/package.json +21 -0
  63. package/scaffold/project/public/css/bootstrap.css +5926 -0
  64. package/scaffold/project/public/js/app.js +1 -0
  65. package/scaffold/project/public/js/jquery.js +2 -0
  66. package/scaffold/project/routes/index.js +8 -0
  67. package/scaffold/project/templates/index.mustache +20 -0
  68. package/utils/Realm.js +60 -0
  69. package/utils/http.js +22 -0
  70. package/utils/index.js +276 -0
@@ -0,0 +1,666 @@
1
+ import React, { useMemo, useEffect } from "react"
2
+ import { useHistory } from 'react-router-dom'
3
+ import {
4
+ ThreadPrimitive,
5
+ ComposerPrimitive,
6
+ MessagePrimitive,
7
+ ActionBarPrimitive,
8
+ BranchPickerPrimitive,
9
+ ErrorPrimitive,
10
+ ToolCallMessagePartProps,
11
+ useMessage,
12
+ useThread,
13
+ useThreadListItem
14
+ } from "@assistant-ui/react";
15
+ import type { FC } from "react";
16
+ import {
17
+ ArrowDownIcon,
18
+ ArrowUpIcon,
19
+ PlusIcon,
20
+ CopyIcon,
21
+ CheckIcon,
22
+ PencilIcon,
23
+ RefreshCwIcon,
24
+ ChevronLeftIcon,
25
+ ChevronRightIcon,
26
+ Square,
27
+ } from "lucide-react";
28
+ import { motion } from "framer-motion";
29
+ import { TooltipIconButton } from "./tooltip-icon-button";
30
+ import { MarkdownText } from "./markdown-text";
31
+ import { Button } from "../ui/button";
32
+ import { cn, uploadFile, fetchSpace, updateSpace } from "../../lib/utils";
33
+ // import { ToolFallback } from "./tool-fallback";
34
+ // import { SessionContext } from '../../contexts/SessionContext'
35
+
36
+ const { vineyard } = window.__CONTEXT__
37
+
38
+ const removeClass = (selector: string, className: string) => {
39
+ Array.from(document.getElementsByClassName(selector)).forEach(el => {
40
+ el.classList.remove(className)
41
+ })
42
+ }
43
+
44
+ const addClass = (event: any, selector: string, className: string) => {
45
+ const els = Array.from(event.target.querySelectorAll(`.${selector}`))
46
+ els.forEach((el: any) => {
47
+ el.classList.add(className)
48
+ })
49
+ }
50
+
51
+ const parseDocname = (docPath: string): Record<string, string> => {
52
+ // console.log('DOC PATH: ' + docPath) // s3://vinely/sites/dans-test-12/storage/describe-econ-1012/econ1012.pdf
53
+ const parts = docPath.split('/')
54
+ const name = parts[parts.length-1]
55
+ const url = docPath.replace('s3://vinely/sites/', 'https://d24qxjs65cjbf1.cloudfront.net/')
56
+ return { name, url }
57
+ }
58
+
59
+ const parseVector = (vector: any, docs: any[]) => {
60
+ const { url, name } = parseDocname(vector.metadata['x-amz-bedrock-kb-source-uri'])
61
+ if (docs.find(doc => doc.url === url)) {
62
+ return
63
+ }
64
+
65
+ docs.push({ url, name })
66
+ }
67
+
68
+ export const Thread: FC = () => {
69
+ const thread = useThread()
70
+ const threadList = useThreadListItem()
71
+
72
+ useEffect(() => {
73
+ if (!thread) {
74
+ return
75
+ }
76
+
77
+ if (thread.isRunning) {
78
+ return
79
+ }
80
+
81
+ const msg = thread.messages.map(({ id, createdAt, role, content }: any) => {
82
+ const interactions = content.find((c: any) => c.type === 'text')
83
+ const text = interactions ? interactions.text : null
84
+
85
+ return ({
86
+ id,
87
+ createdAt,
88
+ role,
89
+ text
90
+ })
91
+ })
92
+
93
+ if (msg.length === 0) {
94
+ return
95
+ }
96
+
97
+ // console.log(JSON.stringify({ id: threadList.id, messages: msg }))
98
+ uploadFile({
99
+ folder: `sites/${vineyard.slug}`,
100
+ filedata: JSON.stringify({ id: threadList.id, messages: msg }),
101
+ filename: `threads/${threadList.id}.json`,
102
+ filetype: 'application/json'
103
+ })
104
+
105
+ const addThreadPreview = async (vyd: any) => {
106
+ // fetch space first because another session may be updating too:
107
+ const current = await fetchSpace(vyd)
108
+
109
+ const metadata = current.metadata || {}
110
+ const recentThreads = metadata.recentThreads || []
111
+ const found = recentThreads.find((t: any) => t.id === threadList.id)
112
+
113
+ // preview already found, this is a continuation of a thread:
114
+ if (found) {
115
+ return
116
+ }
117
+
118
+ const preview = msg.length > 1 ? msg[1].text : ''
119
+ recentThreads.unshift({
120
+ id: threadList.id,
121
+ initial: msg[0], // just send first message, not entire thread
122
+ preview: `${preview.substring(0, 275).trim()}...`
123
+ })
124
+
125
+ if (recentThreads.length === 7) { // max of 6 recent threads
126
+ recentThreads.pop()
127
+ }
128
+
129
+ await updateSpace(current, {
130
+ metadata: { ...metadata, recentThreads }
131
+ })
132
+ }
133
+
134
+ addThreadPreview(vineyard)
135
+ }, [thread])
136
+
137
+ return (
138
+ <ThreadPrimitive.Root
139
+ // aui-thread-root
140
+ className="bg-background flex h-full flex-col"
141
+ style={{
142
+ ["--thread-max-width" as string]: "54rem",
143
+ ["--thread-padding-x" as string]: "1rem",
144
+ }}
145
+ >
146
+ {/* aui-thread-viewport */}
147
+ <ThreadPrimitive.Viewport
148
+ className="relative flex min-w-0 flex-1 flex-col gap-6 overflow-y-scroll"
149
+ style={{ paddingTop: 80 }}
150
+ onScroll={(e: any) => {
151
+ const header = document.getElementById('nav-header')
152
+ if (!header) {
153
+ return
154
+ }
155
+
156
+ const offset = e.target.scrollTop
157
+ if (offset < 50) {
158
+ if (header.style.borderBottomColor !== '#191a1a') {
159
+ header.style.borderBottomColor = '#191a1a'
160
+ }
161
+
162
+ return
163
+ }
164
+
165
+ if (header.style.borderBottomColor !== '#ece6e70d') {
166
+ header.style.borderBottomColor = '#ece6e70d'
167
+ }
168
+ }}
169
+ >
170
+ <ThreadPrimitive.Messages
171
+ components={{
172
+ UserMessage,
173
+ EditComposer,
174
+ AssistantMessage,
175
+ }}
176
+ />
177
+
178
+ <ThreadPrimitive.If empty={false}>
179
+ {/* aui-thread-viewport-spacer */}
180
+ <motion.div className="min-h-6 min-w-6 shrink-0" />
181
+ </ThreadPrimitive.If>
182
+ </ThreadPrimitive.Viewport>
183
+
184
+ <Composer />
185
+ </ThreadPrimitive.Root>
186
+ );
187
+ };
188
+
189
+ const ThreadScrollToBottom: FC = () => {
190
+ return (
191
+ <ThreadPrimitive.ScrollToBottom asChild>
192
+ <TooltipIconButton
193
+ tooltip="Scroll to bottom"
194
+ variant="outline"
195
+ // aui-thread-scroll-to-bottom
196
+ className="dark:bg-background dark:hover:bg-accent absolute -top-12 z-10 self-center rounded-full p-4 disabled:invisible"
197
+ style={{ background: '#fff' }}
198
+ >
199
+ <ArrowDownIcon />
200
+ </TooltipIconButton>
201
+ </ThreadPrimitive.ScrollToBottom>
202
+ );
203
+ };
204
+
205
+ /*
206
+ const ThreadWelcomeSuggestions: FC = () => {
207
+ return (
208
+ // aui-thread-welcome-suggestions
209
+ <div className="grid w-full gap-2 sm:grid-cols-2">
210
+ {[
211
+ {
212
+ title: "What are the advantages",
213
+ label: "of using Assistant Cloud?",
214
+ action: "What are the advantages of using Assistant Cloud?",
215
+ },
216
+ {
217
+ title: "Write code to",
218
+ label: `demonstrate topological sorting`,
219
+ action: `Write code to demonstrate topological sorting`,
220
+ },
221
+ {
222
+ title: "Help me write an essay",
223
+ label: `about AI chat applications`,
224
+ action: `Help me write an essay about AI chat applications`,
225
+ },
226
+ {
227
+ title: "What is the weather",
228
+ label: "in San Francisco?",
229
+ action: "What is the weather in San Francisco?",
230
+ },
231
+ ].map((suggestedAction, index) => (
232
+ <motion.div
233
+ initial={{ opacity: 0, y: 20 }}
234
+ animate={{ opacity: 1, y: 0 }}
235
+ exit={{ opacity: 0, y: 20 }}
236
+ transition={{ delay: 0.05 * index }}
237
+ key={`suggested-action-${suggestedAction.title}-${index}`}
238
+ // aui-thread-welcome-suggestion-display
239
+ className="[&:nth-child(n+3)]:hidden sm:[&:nth-child(n+3)]:block"
240
+ >
241
+ <ThreadPrimitive.Suggestion
242
+ prompt={suggestedAction.action}
243
+ method="replace"
244
+ autoSend
245
+ asChild
246
+ >
247
+ <Button
248
+ variant="ghost"
249
+ className="dark:hover:bg-accent/60 h-auto w-full flex-1 flex-wrap items-start justify-start gap-1 rounded-xl border px-4 py-3.5 text-left text-sm sm:flex-col"
250
+ aria-label={suggestedAction.action}
251
+ >
252
+ <span className="font-medium">{suggestedAction.title}</span>
253
+ <p className="text-muted-foreground">{suggestedAction.label}</p>
254
+ </Button>
255
+ </ThreadPrimitive.Suggestion>
256
+ </motion.div>
257
+ ))}
258
+ </div>
259
+ );
260
+ };
261
+ */
262
+
263
+ const RecentThreads = () => {
264
+ const history = useHistory()
265
+
266
+ useEffect(() => {
267
+ const threadPreviews = Array.from(document.getElementsByClassName('thread-preview'))
268
+ threadPreviews.forEach((preview: any, index: number) => {
269
+ const delay = (index * 200)
270
+ setTimeout(() => {
271
+ preview.style.opacity = '1'
272
+ preview.style.marginTop = '0px'
273
+ }, delay)
274
+ })
275
+ }, [])
276
+
277
+ const metadata = vineyard.metadata || {}
278
+ const threads = metadata.recentThreads || []
279
+
280
+ return (
281
+ <>
282
+ <h4 className="color-accent m-auto">Recent</h4>
283
+ <div className="grid w-full gap-4 sm:grid-cols-3">
284
+ {threads.map((thread: any) => (
285
+ <div
286
+ key={thread.id}
287
+ data-thread={thread.id}
288
+ className="thread-preview"
289
+ style={{ opacity: 0, marginTop: 8 }}
290
+ onClick={() => history.push(`thread/${thread.id}`)}
291
+ >
292
+ <div
293
+ className="card-preview"
294
+ onMouseEnter={(e: any) => {
295
+ removeClass('shine', 'effect-shine')
296
+ addClass(e, 'shine', 'effect-shine')
297
+ }}
298
+ onMouseLeave={(e: any) => {
299
+ removeClass('shine', 'effect-shine')
300
+ }}
301
+ >
302
+ <small className="color-accent">job market</small>
303
+ &nbsp;
304
+ <small>{thread.initial.createdAt}</small>
305
+ <h5 className="shine">{thread.initial.text}</h5>
306
+ <hr />
307
+ <p className="shine">{thread.preview}</p>
308
+ </div>
309
+ </div>
310
+ ))}
311
+ </div>
312
+ </>
313
+ )
314
+ }
315
+
316
+ const Composer: FC = () => {
317
+ return (
318
+ // aui-composer-wrapper
319
+ <div className="bg-background relative mx-auto flex w-full max-w-[var(--thread-max-width)] flex-col gap-4 pb-4 md:pb-6">
320
+ <ThreadScrollToBottom />
321
+ <ThreadPrimitive.Empty>
322
+ {/* <ThreadWelcomeSuggestions /> */}
323
+ <RecentThreads />
324
+ </ThreadPrimitive.Empty>
325
+
326
+ {/* aui-composer-root */}
327
+ <ComposerPrimitive.Root className="focus-within::ring-offset-2 relative flex w-full flex-col rounded-2xl focus-within:ring-2 focus-within:ring-black dark:focus-within:ring-white">
328
+ {/* aui-composer-input */}
329
+ <ComposerPrimitive.Input
330
+ placeholder="Send a message..."
331
+ className={
332
+ "border-border dark:border-muted-foreground/15 focus:outline-primary placeholder:text-muted-foreground max-h-[calc(50dvh)] min-h-16 w-full resize-none rounded-t-2xl border-x border-t px-4 pt-2 pb-3 text-base outline-none"
333
+ }
334
+ rows={1}
335
+ autoFocus
336
+ aria-label="Message input"
337
+ />
338
+ <ComposerAction />
339
+ </ComposerPrimitive.Root>
340
+ </div>
341
+ );
342
+ };
343
+
344
+ const ComposerAction: FC = () => {
345
+ return (
346
+ // aui-composer-action-wrapper
347
+ <div className="border-border dark:border-muted-foreground/15 relative flex items-center justify-between rounded-b-2xl border-x border-b p-2" style={{ background: '#1f2121' }}>
348
+ <TooltipIconButton
349
+ tooltip="Attach file"
350
+ variant="ghost"
351
+ // aui-composer-attachment-button
352
+ className="hover:bg-foreground/15 dark:hover:bg-background/50 scale-115 p-3.5"
353
+ onClick={() => {
354
+ console.log("Attachment clicked - not implemented");
355
+ }}
356
+ >
357
+ <PlusIcon style={{ color:'#e8e8e3'}} />
358
+ </TooltipIconButton>
359
+
360
+ <ThreadPrimitive.If running={false}>
361
+ <ComposerPrimitive.Send asChild>
362
+ <Button
363
+ type="submit"
364
+ variant="default"
365
+ // aui-composer-send
366
+ className="dark:border-muted-foreground/90 border-muted-foreground/60 hover:bg-primary/75 size-8 rounded-full border"
367
+ aria-label="Send message"
368
+ >
369
+ {/* aui-composer-send-icon */}
370
+ <ArrowUpIcon className="size-5" />
371
+ </Button>
372
+ </ComposerPrimitive.Send>
373
+ </ThreadPrimitive.If>
374
+
375
+ <ThreadPrimitive.If running>
376
+ <ComposerPrimitive.Cancel asChild>
377
+ <Button
378
+ type="button"
379
+ variant="default"
380
+ // aui-composer-cancel
381
+ className="dark:border-muted-foreground/90 border-muted-foreground/60 hover:bg-primary/75 size-8 rounded-full border"
382
+ aria-label="Stop generating"
383
+ >
384
+ {/* aui-composer-cancel-icon */}
385
+ <Square className="size-3.5 fill-white dark:size-4 dark:fill-black" />
386
+ </Button>
387
+ </ComposerPrimitive.Cancel>
388
+ </ThreadPrimitive.If>
389
+ </div>
390
+ );
391
+ };
392
+
393
+ const MessageError: FC = () => {
394
+ return (
395
+ <MessagePrimitive.Error>
396
+ {/* aui-message-error-root */}
397
+ <ErrorPrimitive.Root className="border-destructive bg-destructive/10 dark:bg-destructive/5 text-destructive mt-2 rounded-md border p-3 text-sm dark:text-red-200">
398
+ {/* aui-message-error-message */}
399
+ <ErrorPrimitive.Message className="line-clamp-2" />
400
+ </ErrorPrimitive.Root>
401
+ </MessagePrimitive.Error>
402
+ );
403
+ };
404
+
405
+ const CustomToolFallback: FC<ToolCallMessagePartProps> = (toolInvocation: any) => {
406
+ const refs: any = useMemo(() => {
407
+ if (!toolInvocation.result) {
408
+ return null
409
+ }
410
+
411
+ if (!toolInvocation.result.content) {
412
+ return null
413
+ }
414
+
415
+ const { content } = toolInvocation.result
416
+ if (content.length === 0) {
417
+ return null
418
+ }
419
+
420
+ const payload = content[0]
421
+ if (!payload.text) {
422
+ return null
423
+ }
424
+
425
+ let parsed: any = {}
426
+ try {
427
+ parsed = JSON.parse(payload.text)
428
+ } catch (error) {
429
+ return null
430
+ }
431
+
432
+ if (parsed.vectors) {
433
+ const docs: any[] = []
434
+ parsed.vectors.forEach((vector: any) => parseVector(vector, docs))
435
+ return { vectors: parsed.vectors, docs }
436
+ }
437
+
438
+ if (!parsed.response) {
439
+ return null
440
+ }
441
+
442
+ const response = parsed.response
443
+ if (!response.vectors) {
444
+ return null
445
+ }
446
+
447
+ const { vectors } = response
448
+ const docs: any[] = []
449
+ vectors.forEach((vector: any) => parseVector(vector, docs))
450
+
451
+ return { vectors, docs }
452
+ }, [toolInvocation.result])
453
+
454
+ if (!refs) {
455
+ return null
456
+ }
457
+
458
+ return (
459
+ <>
460
+ {/* <ToolFallback { ...toolInvocation } /> */}
461
+ <div
462
+ className="space-y-2 mb-5 reference-container"
463
+ style={{ opacity: refs?.vectors === null ? 0 : 1 }}
464
+ >
465
+ {refs?.vectors === null
466
+ ? null
467
+ : <>
468
+ <small className="fw-600">Sources</small>
469
+ <ol style={{ marginTop: 6 }}>
470
+ {refs?.docs.map((doc: any) => (
471
+ <li key={doc.url}>
472
+ <a
473
+ target="_blank"
474
+ className="color-accent"
475
+ href={doc.url}
476
+ >
477
+ <small>{doc.name}</small>
478
+ </a>
479
+ </li>
480
+ ))}
481
+ </ol>
482
+ </>
483
+ }
484
+ </div>
485
+ </>
486
+ );
487
+ };
488
+
489
+ const AssistantMessage: FC = () => {
490
+ return (
491
+ <MessagePrimitive.Root asChild>
492
+ <motion.div
493
+ // aui-assistant-message-root
494
+ className="relative mx-auto grid w-full max-w-[var(--thread-max-width)] grid-cols-[auto_auto_1fr] grid-rows-[auto_1fr] px-[var(--thread-padding-x)] py-4"
495
+ initial={{ y: 5, opacity: 0 }}
496
+ animate={{ y: 0, opacity: 1 }}
497
+ data-role="assistant"
498
+ >
499
+ {/* aui-assistant-message-avatar */}
500
+ {/* <div className="ring-border bg-background col-start-1 row-start-1 flex size-8 shrink-0 items-center justify-center rounded-full ring-1">
501
+ <StarIcon size={14} />
502
+ </div> */}
503
+
504
+ {/* aui-assistant-message-content */}
505
+ <div className="text-foreground col-span-2 col-start-2 row-start-1 ml-4 leading-7 break-words" style={{ color: '#e8e8e3' }}>
506
+ <MessagePrimitive.Content
507
+ components={{
508
+ Text: MarkdownText,
509
+ tools: { Fallback: CustomToolFallback }
510
+ }}
511
+ />
512
+ <MessageError />
513
+ </div>
514
+
515
+ <AssistantActionBar />
516
+
517
+ {/* aui-assistant-branch-picker */}
518
+ <BranchPicker className="col-start-2 row-start-2 mr-2 -ml-2" />
519
+ </motion.div>
520
+ </MessagePrimitive.Root>
521
+ );
522
+ };
523
+
524
+ const AssistantActionBar: FC = () => {
525
+ return (
526
+ <ActionBarPrimitive.Root
527
+ hideWhenRunning
528
+ autohide="not-last"
529
+ autohideFloat="single-branch"
530
+ // aui-assistant-action-bar-root
531
+ className="text-muted-foreground data-floating:bg-background col-start-3 row-start-2 mt-3 ml-3 flex gap-1 data-floating:absolute data-floating:mt-2 data-floating:rounded-md data-floating:border data-floating:p-1 data-floating:shadow-sm"
532
+ >
533
+ <ActionBarPrimitive.Copy asChild>
534
+ <TooltipIconButton tooltip="Copy">
535
+ <MessagePrimitive.If copied>
536
+ <CheckIcon />
537
+ </MessagePrimitive.If>
538
+ <MessagePrimitive.If copied={false}>
539
+ <CopyIcon />
540
+ </MessagePrimitive.If>
541
+ </TooltipIconButton>
542
+ </ActionBarPrimitive.Copy>
543
+ <ActionBarPrimitive.Reload asChild>
544
+ <TooltipIconButton tooltip="Refresh">
545
+ <RefreshCwIcon />
546
+ </TooltipIconButton>
547
+ </ActionBarPrimitive.Reload>
548
+ </ActionBarPrimitive.Root>
549
+ );
550
+ };
551
+
552
+ const UserMessage: FC = () => {
553
+ const { content } = useMessage()
554
+ const message = content[0]
555
+
556
+ if (message.type === 'text' && message.text === 'Please describe in plain english.') {
557
+ return null
558
+ }
559
+
560
+ return (
561
+ <MessagePrimitive.Root asChild>
562
+ <motion.div
563
+ // aui-user-message-root
564
+ className="mx-auto grid w-full max-w-[var(--thread-max-width)] auto-rows-auto grid-cols-[minmax(72px,1fr)_auto] gap-y-1 px-[var(--thread-padding-x)] py-4 [&:where(>*)]:col-start-2"
565
+ initial={{ y: 5, opacity: 0 }}
566
+ animate={{ y: 0, opacity: 1 }}
567
+ data-role="user"
568
+ >
569
+ <UserActionBar />
570
+
571
+ {/* aui-user-message-content */}
572
+ <div className="text-default col-start-2 rounded-3xl px-5 py-1 break-words" style={{ color: '#e8e8e3' }}>
573
+ <MessagePrimitive.Content
574
+ components={{ Text: MarkdownText }}
575
+ />
576
+ <small style={{ float: 'right', color: '#8c8c8c', fontSize: 12, marginTop: 6 }}>{`${new Date().toLocaleString('en-US')}`}</small>
577
+ </div>
578
+
579
+ {/* aui-user-branch-picker */}
580
+ <BranchPicker className="col-span-full col-start-1 row-start-3 -mr-1 justify-end" />
581
+ </motion.div>
582
+ </MessagePrimitive.Root>
583
+ );
584
+ };
585
+
586
+ const UserActionBar: FC = () => {
587
+ return (
588
+ <ActionBarPrimitive.Root
589
+ hideWhenRunning
590
+ autohide="not-last"
591
+ // aui-user-action-bar-root
592
+ className="col-start-1 mt-2.5 mr-3 flex flex-col items-end"
593
+ >
594
+ <ActionBarPrimitive.Edit asChild>
595
+ <TooltipIconButton tooltip="Edit?">
596
+ <PencilIcon style={{ color: '#fff' }} />
597
+ </TooltipIconButton>
598
+ </ActionBarPrimitive.Edit>
599
+ </ActionBarPrimitive.Root>
600
+ );
601
+ };
602
+
603
+ const EditComposer: FC = () => {
604
+ return (
605
+ // aui-edit-composer-wrapper
606
+ <div className="mx-auto flex w-full max-w-[var(--thread-max-width)] flex-col gap-4 px-[var(--thread-padding-x)]">
607
+ {/* aui-edit-composer-root */}
608
+ <ComposerPrimitive.Root
609
+ className="bg-muted ml-auto flex w-full max-w-7/8 flex-col rounded-xl"
610
+ style={{ background: '#1f2121' }}
611
+ >
612
+ {/* aui-edit-composer-input */}
613
+ <ComposerPrimitive.Input
614
+ className="text-foreground flex min-h-[60px] w-full resize-none bg-transparent p-4 outline-none"
615
+ autoFocus
616
+ />
617
+
618
+ {/* aui-edit-composer-footer */}
619
+ <div className="mx-3 mb-3 flex items-center justify-center gap-2 self-end">
620
+ <ComposerPrimitive.Cancel asChild>
621
+ <Button variant="ghost" size="sm" aria-label="Cancel edit">
622
+ Cancel
623
+ </Button>
624
+ </ComposerPrimitive.Cancel>
625
+ <ComposerPrimitive.Send asChild>
626
+ <Button size="sm" aria-label="Update message">
627
+ Update
628
+ </Button>
629
+ </ComposerPrimitive.Send>
630
+ </div>
631
+ </ComposerPrimitive.Root>
632
+ </div>
633
+ );
634
+ };
635
+
636
+ const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({
637
+ className,
638
+ ...rest
639
+ }) => {
640
+ return (
641
+ <BranchPickerPrimitive.Root
642
+ hideWhenSingleBranch
643
+ // aui-branch-picker-root
644
+ className={cn(
645
+ "text-muted-foreground inline-flex items-center text-xs",
646
+ className,
647
+ )}
648
+ {...rest}
649
+ >
650
+ <BranchPickerPrimitive.Previous asChild>
651
+ <TooltipIconButton tooltip="Previous">
652
+ <ChevronLeftIcon />
653
+ </TooltipIconButton>
654
+ </BranchPickerPrimitive.Previous>
655
+ {/* aui-branch-picker-state */}
656
+ <span className="font-medium">
657
+ <BranchPickerPrimitive.Number /> / <BranchPickerPrimitive.Count />
658
+ </span>
659
+ <BranchPickerPrimitive.Next asChild>
660
+ <TooltipIconButton tooltip="Next">
661
+ <ChevronRightIcon />
662
+ </TooltipIconButton>
663
+ </BranchPickerPrimitive.Next>
664
+ </BranchPickerPrimitive.Root>
665
+ );
666
+ };
@@ -0,0 +1,44 @@
1
+ import React, { useMemo, useState, useEffect } from "react";
2
+ import { ToolCallContentPartComponent } from "@assistant-ui/react";
3
+ import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
4
+ import { Button } from "../ui/button";
5
+
6
+ export const ToolFallback: ToolCallContentPartComponent = ({
7
+ toolName,
8
+ argsText,
9
+ result,
10
+ }) => {
11
+ const [isCollapsed, setIsCollapsed] = useState(true)
12
+
13
+ return (
14
+ <div className="mb-0 flex w-full flex-col gap-3 rounded-lg border py-3" style={{ cursor: 'pointer' }} onClick={() => setIsCollapsed(!isCollapsed)}>
15
+ <div className="flex items-center gap-2 px-4">
16
+ <CheckIcon className="size-4" />
17
+ <p>
18
+ Using tool: <b>{toolName}</b>
19
+ </p>
20
+ <div className="flex-grow" />
21
+ <Button onClick={() => setIsCollapsed(!isCollapsed)}>
22
+ {isCollapsed ? <ChevronUpIcon /> : <ChevronDownIcon />}
23
+ </Button>
24
+ </div>
25
+ {!isCollapsed && (
26
+ <div className="flex flex-col gap-2 border-t pt-2">
27
+ <div className="px-4">
28
+ <pre className="whitespace-pre-wrap">{argsText}</pre>
29
+ </div>
30
+ {result !== undefined && (
31
+ <div className="border-t border-dashed px-4 pt-2">
32
+ <p className="font-semibold">Result:</p>
33
+ <pre className="whitespace-pre-wrap">
34
+ {typeof result === "string"
35
+ ? result
36
+ : JSON.stringify(result, null, 2)}
37
+ </pre>
38
+ </div>
39
+ )}
40
+ </div>
41
+ )}
42
+ </div>
43
+ );
44
+ };