@applica-software-guru/persona-chat-sdk 0.1.102
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/.eslintrc.cjs +11 -0
- package/.github/copilot-instructions.md +3 -0
- package/.nvmrc +1 -0
- package/.prettierignore +5 -0
- package/.prettierrc +8 -0
- package/CLAUDE.md +3 -0
- package/GEMINI.md +3 -0
- package/README.md +33 -0
- package/bitbucket-pipelines.yml +19 -0
- package/components.json +24 -0
- package/dist/bundle.cjs.js +28 -0
- package/dist/bundle.cjs.js.map +1 -0
- package/dist/bundle.es.js +5173 -0
- package/dist/bundle.es.js.map +1 -0
- package/dist/bundle.iife.js +28 -0
- package/dist/bundle.iife.js.map +1 -0
- package/dist/bundle.umd.js +28 -0
- package/dist/bundle.umd.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/logging.d.ts +18 -0
- package/dist/logging.d.ts.map +1 -0
- package/dist/messages.d.ts +9 -0
- package/dist/messages.d.ts.map +1 -0
- package/dist/projects.d.ts +17 -0
- package/dist/projects.d.ts.map +1 -0
- package/dist/protocol/base.d.ts +25 -0
- package/dist/protocol/base.d.ts.map +1 -0
- package/dist/protocol/index.d.ts +6 -0
- package/dist/protocol/index.d.ts.map +1 -0
- package/dist/protocol/rest.d.ts +25 -0
- package/dist/protocol/rest.d.ts.map +1 -0
- package/dist/protocol/transaction.d.ts +56 -0
- package/dist/protocol/transaction.d.ts.map +1 -0
- package/dist/protocol/webrtc.d.ts +60 -0
- package/dist/protocol/webrtc.d.ts.map +1 -0
- package/dist/protocol/websocket.d.ts +22 -0
- package/dist/protocol/websocket.d.ts.map +1 -0
- package/dist/runtime/context.d.ts +34 -0
- package/dist/runtime/context.d.ts.map +1 -0
- package/dist/runtime/file-attachment-adapter.d.ts +15 -0
- package/dist/runtime/file-attachment-adapter.d.ts.map +1 -0
- package/dist/runtime/handlers.d.ts +21 -0
- package/dist/runtime/handlers.d.ts.map +1 -0
- package/dist/runtime/listeners.d.ts +6 -0
- package/dist/runtime/listeners.d.ts.map +1 -0
- package/dist/runtime/protocols.d.ts +17 -0
- package/dist/runtime/protocols.d.ts.map +1 -0
- package/dist/runtime/threads.d.ts +34 -0
- package/dist/runtime/threads.d.ts.map +1 -0
- package/dist/runtime/utils.d.ts +10 -0
- package/dist/runtime/utils.d.ts.map +1 -0
- package/dist/runtime.d.ts +6 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/storage/base.d.ts +9 -0
- package/dist/storage/base.d.ts.map +1 -0
- package/dist/storage/index.d.ts +3 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/persona.d.ts +29 -0
- package/dist/storage/persona.d.ts.map +1 -0
- package/dist/tools.d.ts +72 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/types.d.ts +237 -0
- package/dist/types.d.ts.map +1 -0
- package/docs/README.md +13 -0
- package/docs/api-reference.md +16 -0
- package/docs/contributing.md +21 -0
- package/docs/customization.md +74 -0
- package/docs/features.md +7 -0
- package/docs/installation.md +9 -0
- package/docs/messages.md +214 -0
- package/docs/protocols.md +39 -0
- package/docs/transactions.md +121 -0
- package/docs/usage.md +40 -0
- package/jsconfig.node.json +10 -0
- package/package.json +82 -0
- package/playground/index.html +14 -0
- package/playground/src/app.tsx +10 -0
- package/playground/src/chat.tsx +117 -0
- package/playground/src/components/assistant-ui/assistant-modal.tsx +57 -0
- package/playground/src/components/assistant-ui/attachment.tsx +197 -0
- package/playground/src/components/assistant-ui/markdown-text.tsx +228 -0
- package/playground/src/components/assistant-ui/thread-list.tsx +91 -0
- package/playground/src/components/assistant-ui/thread.tsx +302 -0
- package/playground/src/components/assistant-ui/threadlist-sidebar.tsx +59 -0
- package/playground/src/components/assistant-ui/tool-fallback.tsx +93 -0
- package/playground/src/components/assistant-ui/tooltip-icon-button.tsx +42 -0
- package/playground/src/components/chat/logging.tsx +53 -0
- package/playground/src/components/ui/avatar.tsx +51 -0
- package/playground/src/components/ui/button.tsx +60 -0
- package/playground/src/components/ui/dialog.tsx +141 -0
- package/playground/src/components/ui/input.tsx +21 -0
- package/playground/src/components/ui/separator.tsx +26 -0
- package/playground/src/components/ui/sheet.tsx +139 -0
- package/playground/src/components/ui/sidebar.tsx +619 -0
- package/playground/src/components/ui/skeleton.tsx +13 -0
- package/playground/src/components/ui/tooltip.tsx +59 -0
- package/playground/src/hooks/theme.ts +70 -0
- package/playground/src/hooks/use-mobile.ts +19 -0
- package/playground/src/lib/utils.ts +6 -0
- package/playground/src/main.tsx +10 -0
- package/playground/src/styles.css +120 -0
- package/playground/src/tools.ts +149 -0
- package/playground/src/vite-env.d.ts +1 -0
- package/preview-build.sh +23 -0
- package/src/index.ts +7 -0
- package/src/logging.ts +34 -0
- package/src/messages.ts +202 -0
- package/src/projects.ts +57 -0
- package/src/protocol/base.ts +73 -0
- package/src/protocol/index.ts +5 -0
- package/src/protocol/rest.ts +107 -0
- package/src/protocol/transaction.ts +182 -0
- package/src/protocol/webrtc.ts +379 -0
- package/src/protocol/websocket.ts +111 -0
- package/src/runtime/context.ts +88 -0
- package/src/runtime/file-attachment-adapter.ts +48 -0
- package/src/runtime/handlers.ts +322 -0
- package/src/runtime/index.ts +6 -0
- package/src/runtime/listeners.ts +79 -0
- package/src/runtime/protocols.ts +169 -0
- package/src/runtime/threads.ts +105 -0
- package/src/runtime/utils.ts +46 -0
- package/src/runtime.tsx +334 -0
- package/src/storage/base.ts +13 -0
- package/src/storage/index.ts +2 -0
- package/src/storage/persona.ts +138 -0
- package/src/tools.ts +211 -0
- package/src/types.ts +284 -0
- package/tsconfig.json +36 -0
- package/tsconfig.node.json +15 -0
- package/vite.config.ts +74 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { BotIcon, ChevronDownIcon } from 'lucide-react';
|
|
4
|
+
|
|
5
|
+
import { type FC, forwardRef } from 'react';
|
|
6
|
+
import { AssistantModalPrimitive } from '@assistant-ui/react';
|
|
7
|
+
|
|
8
|
+
import { Thread } from '@/components/assistant-ui/thread';
|
|
9
|
+
import { TooltipIconButton } from '@/components/assistant-ui/tooltip-icon-button';
|
|
10
|
+
|
|
11
|
+
export const AssistantModal: FC = () => {
|
|
12
|
+
return (
|
|
13
|
+
<AssistantModalPrimitive.Root>
|
|
14
|
+
<AssistantModalPrimitive.Anchor className="fixed bottom-4 right-4 size-11">
|
|
15
|
+
<AssistantModalPrimitive.Trigger asChild>
|
|
16
|
+
<AssistantModalButton />
|
|
17
|
+
</AssistantModalPrimitive.Trigger>
|
|
18
|
+
</AssistantModalPrimitive.Anchor>
|
|
19
|
+
<AssistantModalPrimitive.Content
|
|
20
|
+
sideOffset={16}
|
|
21
|
+
className="bg-popover text-popover-foreground z-50 h-[500px] w-[400px] overflow-clip rounded-xl border p-0 shadow-md outline-none [&>.aui-thread-root]:bg-inherit data-[state=closed]:animate-out data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out data-[state=open]:zoom-in data-[state=open]:slide-in-from-bottom-1/2 data-[state=open]:slide-in-from-right-1/2 data-[state=closed]:slide-out-to-bottom-1/2 data-[state=closed]:slide-out-to-right-1/2"
|
|
22
|
+
>
|
|
23
|
+
<Thread />
|
|
24
|
+
</AssistantModalPrimitive.Content>
|
|
25
|
+
</AssistantModalPrimitive.Root>
|
|
26
|
+
);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
type AssistantModalButtonProps = { 'data-state'?: 'open' | 'closed' };
|
|
30
|
+
|
|
31
|
+
const AssistantModalButton = forwardRef<HTMLButtonElement, AssistantModalButtonProps>(({ 'data-state': state, ...rest }, ref) => {
|
|
32
|
+
const tooltip = state === 'open' ? 'Close Assistant' : 'Open Assistant';
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<TooltipIconButton
|
|
36
|
+
variant="default"
|
|
37
|
+
tooltip={tooltip}
|
|
38
|
+
side="left"
|
|
39
|
+
{...rest}
|
|
40
|
+
className="size-full rounded-full shadow transition-transform hover:scale-110 active:scale-90"
|
|
41
|
+
ref={ref}
|
|
42
|
+
>
|
|
43
|
+
<BotIcon
|
|
44
|
+
data-state={state}
|
|
45
|
+
className="absolute size-6 transition-all data-[state=closed]:rotate-0 data-[state=open]:rotate-90 data-[state=closed]:scale-100 data-[state=open]:scale-0"
|
|
46
|
+
/>
|
|
47
|
+
|
|
48
|
+
<ChevronDownIcon
|
|
49
|
+
data-state={state}
|
|
50
|
+
className="absolute size-6 transition-all data-[state=closed]:-rotate-90 data-[state=open]:rotate-0 data-[state=closed]:scale-0 data-[state=open]:scale-100"
|
|
51
|
+
/>
|
|
52
|
+
<span className="sr-only">{tooltip}</span>
|
|
53
|
+
</TooltipIconButton>
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
AssistantModalButton.displayName = 'AssistantModalButton';
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { PropsWithChildren, useEffect, useState, type FC } from 'react';
|
|
4
|
+
import { XIcon, PlusIcon, FileText } from 'lucide-react';
|
|
5
|
+
import { AttachmentPrimitive, ComposerPrimitive, MessagePrimitive, useAssistantState, useAssistantApi } from '@assistant-ui/react';
|
|
6
|
+
import { useShallow } from 'zustand/shallow';
|
|
7
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
|
8
|
+
import { Dialog, DialogTitle, DialogContent, DialogTrigger } from '@/components/ui/dialog';
|
|
9
|
+
import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar';
|
|
10
|
+
import { TooltipIconButton } from '@/components/assistant-ui/tooltip-icon-button';
|
|
11
|
+
import { cn } from '@/lib/utils';
|
|
12
|
+
|
|
13
|
+
const useFileSrc = (file: File | undefined) => {
|
|
14
|
+
const [src, setSrc] = useState<string | undefined>(undefined);
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (!file) {
|
|
18
|
+
setSrc(undefined);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const objectUrl = URL.createObjectURL(file);
|
|
23
|
+
setSrc(objectUrl);
|
|
24
|
+
|
|
25
|
+
return () => {
|
|
26
|
+
URL.revokeObjectURL(objectUrl);
|
|
27
|
+
};
|
|
28
|
+
}, [file]);
|
|
29
|
+
|
|
30
|
+
return src;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const useAttachmentSrc = () => {
|
|
34
|
+
const { file, src } = useAssistantState(
|
|
35
|
+
useShallow(({ attachment }): { file?: File; src?: string } => {
|
|
36
|
+
if (attachment.type !== 'image') return {};
|
|
37
|
+
if (attachment.file) return { file: attachment.file };
|
|
38
|
+
const src = attachment.content?.filter((c) => c.type === 'image')[0]?.image;
|
|
39
|
+
if (!src) return {};
|
|
40
|
+
return { src };
|
|
41
|
+
}),
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
return useFileSrc(file) ?? src;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
type AttachmentPreviewProps = {
|
|
48
|
+
src: string;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const AttachmentPreview: FC<AttachmentPreviewProps> = ({ src }) => {
|
|
52
|
+
const [isLoaded, setIsLoaded] = useState(false);
|
|
53
|
+
return (
|
|
54
|
+
<img
|
|
55
|
+
src={src}
|
|
56
|
+
alt="Image Preview"
|
|
57
|
+
width={1}
|
|
58
|
+
height={1}
|
|
59
|
+
className={
|
|
60
|
+
isLoaded
|
|
61
|
+
? 'aui-attachment-preview-image-loaded block h-auto max-h-[80vh] w-auto max-w-full object-contain'
|
|
62
|
+
: 'aui-attachment-preview-image-loading hidden'
|
|
63
|
+
}
|
|
64
|
+
onLoad={() => setIsLoaded(true)}
|
|
65
|
+
/>
|
|
66
|
+
);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const AttachmentPreviewDialog: FC<PropsWithChildren> = ({ children }) => {
|
|
70
|
+
const src = useAttachmentSrc();
|
|
71
|
+
|
|
72
|
+
if (!src) return children;
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<Dialog>
|
|
76
|
+
<DialogTrigger className="aui-attachment-preview-trigger cursor-pointer transition-colors hover:bg-accent/50" asChild>
|
|
77
|
+
{children}
|
|
78
|
+
</DialogTrigger>
|
|
79
|
+
<DialogContent className="aui-attachment-preview-dialog-content p-2 sm:max-w-3xl [&>button]:rounded-full [&>button]:bg-foreground/60 [&>button]:p-1 [&>button]:opacity-100 [&>button]:ring-0! [&_svg]:text-background [&>button]:hover:[&_svg]:text-destructive">
|
|
80
|
+
<DialogTitle className="aui-sr-only sr-only">Image Attachment Preview</DialogTitle>
|
|
81
|
+
<div className="aui-attachment-preview relative mx-auto flex max-h-[80dvh] w-full items-center justify-center overflow-hidden bg-background">
|
|
82
|
+
<AttachmentPreview src={src} />
|
|
83
|
+
</div>
|
|
84
|
+
</DialogContent>
|
|
85
|
+
</Dialog>
|
|
86
|
+
);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const AttachmentThumb: FC = () => {
|
|
90
|
+
const isImage = useAssistantState(({ attachment }) => attachment.type === 'image');
|
|
91
|
+
const src = useAttachmentSrc();
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<Avatar className="aui-attachment-tile-avatar h-full w-full rounded-none">
|
|
95
|
+
<AvatarImage src={src} alt="Attachment preview" className="aui-attachment-tile-image object-cover" />
|
|
96
|
+
<AvatarFallback delayMs={isImage ? 200 : 0}>
|
|
97
|
+
<FileText className="aui-attachment-tile-fallback-icon size-8 text-muted-foreground" />
|
|
98
|
+
</AvatarFallback>
|
|
99
|
+
</Avatar>
|
|
100
|
+
);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const AttachmentUI: FC = () => {
|
|
104
|
+
const api = useAssistantApi();
|
|
105
|
+
const isComposer = api.attachment.source === 'composer';
|
|
106
|
+
|
|
107
|
+
const isImage = useAssistantState(({ attachment }) => attachment.type === 'image');
|
|
108
|
+
const typeLabel = useAssistantState(({ attachment }) => {
|
|
109
|
+
const type = attachment.type;
|
|
110
|
+
switch (type) {
|
|
111
|
+
case 'image':
|
|
112
|
+
return 'Image';
|
|
113
|
+
case 'document':
|
|
114
|
+
return 'Document';
|
|
115
|
+
case 'file':
|
|
116
|
+
return 'File';
|
|
117
|
+
default:
|
|
118
|
+
const _exhaustiveCheck: never = type;
|
|
119
|
+
throw new Error(`Unknown attachment type: ${_exhaustiveCheck}`);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<Tooltip>
|
|
125
|
+
<AttachmentPrimitive.Root
|
|
126
|
+
className={cn('aui-attachment-root relative', isImage && 'aui-attachment-root-composer only:[&>#attachment-tile]:size-24')}
|
|
127
|
+
>
|
|
128
|
+
<AttachmentPreviewDialog>
|
|
129
|
+
<TooltipTrigger asChild>
|
|
130
|
+
<div
|
|
131
|
+
className={cn(
|
|
132
|
+
'aui-attachment-tile size-14 cursor-pointer overflow-hidden rounded-[14px] border bg-muted transition-opacity hover:opacity-75',
|
|
133
|
+
isComposer && 'aui-attachment-tile-composer border-foreground/20',
|
|
134
|
+
)}
|
|
135
|
+
role="button"
|
|
136
|
+
id="attachment-tile"
|
|
137
|
+
aria-label={`${typeLabel} attachment`}
|
|
138
|
+
>
|
|
139
|
+
<AttachmentThumb />
|
|
140
|
+
</div>
|
|
141
|
+
</TooltipTrigger>
|
|
142
|
+
</AttachmentPreviewDialog>
|
|
143
|
+
{isComposer && <AttachmentRemove />}
|
|
144
|
+
</AttachmentPrimitive.Root>
|
|
145
|
+
<TooltipContent side="top">
|
|
146
|
+
<AttachmentPrimitive.Name />
|
|
147
|
+
</TooltipContent>
|
|
148
|
+
</Tooltip>
|
|
149
|
+
);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const AttachmentRemove: FC = () => {
|
|
153
|
+
return (
|
|
154
|
+
<AttachmentPrimitive.Remove asChild>
|
|
155
|
+
<TooltipIconButton
|
|
156
|
+
tooltip="Remove file"
|
|
157
|
+
className="aui-attachment-tile-remove absolute top-1.5 right-1.5 size-3.5 rounded-full bg-white text-muted-foreground opacity-100 shadow-sm hover:bg-white! [&_svg]:text-black hover:[&_svg]:text-destructive"
|
|
158
|
+
side="top"
|
|
159
|
+
>
|
|
160
|
+
<XIcon className="aui-attachment-remove-icon size-3 dark:stroke-[2.5px]" />
|
|
161
|
+
</TooltipIconButton>
|
|
162
|
+
</AttachmentPrimitive.Remove>
|
|
163
|
+
);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
export const UserMessageAttachments: FC = () => {
|
|
167
|
+
return (
|
|
168
|
+
<div className="aui-user-message-attachments-end col-span-full col-start-1 row-start-1 flex w-full flex-row justify-end gap-2">
|
|
169
|
+
<MessagePrimitive.Attachments components={{ Attachment: AttachmentUI }} />
|
|
170
|
+
</div>
|
|
171
|
+
);
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
export const ComposerAttachments: FC = () => {
|
|
175
|
+
return (
|
|
176
|
+
<div className="aui-composer-attachments mb-2 flex w-full flex-row items-center gap-2 overflow-x-auto px-1.5 pt-0.5 pb-1 empty:hidden">
|
|
177
|
+
<ComposerPrimitive.Attachments components={{ Attachment: AttachmentUI }} />
|
|
178
|
+
</div>
|
|
179
|
+
);
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
export const ComposerAddAttachment: FC = () => {
|
|
183
|
+
return (
|
|
184
|
+
<ComposerPrimitive.AddAttachment asChild>
|
|
185
|
+
<TooltipIconButton
|
|
186
|
+
tooltip="Add Attachment"
|
|
187
|
+
side="bottom"
|
|
188
|
+
variant="ghost"
|
|
189
|
+
size="icon"
|
|
190
|
+
className="aui-composer-add-attachment size-[34px] rounded-full p-1 font-semibold text-xs hover:bg-muted-foreground/15 dark:border-muted-foreground/15 dark:hover:bg-muted-foreground/30"
|
|
191
|
+
aria-label="Add Attachment"
|
|
192
|
+
>
|
|
193
|
+
<PlusIcon className="aui-attachment-add-icon size-5 stroke-[1.5px]" />
|
|
194
|
+
</TooltipIconButton>
|
|
195
|
+
</ComposerPrimitive.AddAttachment>
|
|
196
|
+
);
|
|
197
|
+
};
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import "@assistant-ui/react-markdown/styles/dot.css";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
type CodeHeaderProps,
|
|
7
|
+
MarkdownTextPrimitive,
|
|
8
|
+
unstable_memoizeMarkdownComponents as memoizeMarkdownComponents,
|
|
9
|
+
useIsMarkdownCodeBlock,
|
|
10
|
+
} from "@assistant-ui/react-markdown";
|
|
11
|
+
import remarkGfm from "remark-gfm";
|
|
12
|
+
import { type FC, memo, useState } from "react";
|
|
13
|
+
import { CheckIcon, CopyIcon } from "lucide-react";
|
|
14
|
+
|
|
15
|
+
import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
|
|
16
|
+
import { cn } from "@/lib/utils";
|
|
17
|
+
|
|
18
|
+
const MarkdownTextImpl = () => {
|
|
19
|
+
return (
|
|
20
|
+
<MarkdownTextPrimitive
|
|
21
|
+
remarkPlugins={[remarkGfm]}
|
|
22
|
+
className="aui-md"
|
|
23
|
+
components={defaultComponents}
|
|
24
|
+
/>
|
|
25
|
+
);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const MarkdownText = memo(MarkdownTextImpl);
|
|
29
|
+
|
|
30
|
+
const CodeHeader: FC<CodeHeaderProps> = ({ language, code }) => {
|
|
31
|
+
const { isCopied, copyToClipboard } = useCopyToClipboard();
|
|
32
|
+
const onCopy = () => {
|
|
33
|
+
if (!code || isCopied) return;
|
|
34
|
+
copyToClipboard(code);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div className="aui-code-header-root mt-4 flex items-center justify-between gap-4 rounded-t-lg bg-muted-foreground/15 px-4 py-2 font-semibold text-foreground text-sm dark:bg-muted-foreground/20">
|
|
39
|
+
<span className="aui-code-header-language lowercase [&>span]:text-xs">
|
|
40
|
+
{language}
|
|
41
|
+
</span>
|
|
42
|
+
<TooltipIconButton tooltip="Copy" onClick={onCopy}>
|
|
43
|
+
{!isCopied && <CopyIcon />}
|
|
44
|
+
{isCopied && <CheckIcon />}
|
|
45
|
+
</TooltipIconButton>
|
|
46
|
+
</div>
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const useCopyToClipboard = ({
|
|
51
|
+
copiedDuration = 3000,
|
|
52
|
+
}: {
|
|
53
|
+
copiedDuration?: number;
|
|
54
|
+
} = {}) => {
|
|
55
|
+
const [isCopied, setIsCopied] = useState<boolean>(false);
|
|
56
|
+
|
|
57
|
+
const copyToClipboard = (value: string) => {
|
|
58
|
+
if (!value) return;
|
|
59
|
+
|
|
60
|
+
navigator.clipboard.writeText(value).then(() => {
|
|
61
|
+
setIsCopied(true);
|
|
62
|
+
setTimeout(() => setIsCopied(false), copiedDuration);
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
return { isCopied, copyToClipboard };
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const defaultComponents = memoizeMarkdownComponents({
|
|
70
|
+
h1: ({ className, ...props }) => (
|
|
71
|
+
<h1
|
|
72
|
+
className={cn(
|
|
73
|
+
"aui-md-h1 mb-8 scroll-m-20 font-extrabold text-4xl tracking-tight last:mb-0",
|
|
74
|
+
className,
|
|
75
|
+
)}
|
|
76
|
+
{...props}
|
|
77
|
+
/>
|
|
78
|
+
),
|
|
79
|
+
h2: ({ className, ...props }) => (
|
|
80
|
+
<h2
|
|
81
|
+
className={cn(
|
|
82
|
+
"aui-md-h2 mt-8 mb-4 scroll-m-20 font-semibold text-3xl tracking-tight first:mt-0 last:mb-0",
|
|
83
|
+
className,
|
|
84
|
+
)}
|
|
85
|
+
{...props}
|
|
86
|
+
/>
|
|
87
|
+
),
|
|
88
|
+
h3: ({ className, ...props }) => (
|
|
89
|
+
<h3
|
|
90
|
+
className={cn(
|
|
91
|
+
"aui-md-h3 mt-6 mb-4 scroll-m-20 font-semibold text-2xl tracking-tight first:mt-0 last:mb-0",
|
|
92
|
+
className,
|
|
93
|
+
)}
|
|
94
|
+
{...props}
|
|
95
|
+
/>
|
|
96
|
+
),
|
|
97
|
+
h4: ({ className, ...props }) => (
|
|
98
|
+
<h4
|
|
99
|
+
className={cn(
|
|
100
|
+
"aui-md-h4 mt-6 mb-4 scroll-m-20 font-semibold text-xl tracking-tight first:mt-0 last:mb-0",
|
|
101
|
+
className,
|
|
102
|
+
)}
|
|
103
|
+
{...props}
|
|
104
|
+
/>
|
|
105
|
+
),
|
|
106
|
+
h5: ({ className, ...props }) => (
|
|
107
|
+
<h5
|
|
108
|
+
className={cn(
|
|
109
|
+
"aui-md-h5 my-4 font-semibold text-lg first:mt-0 last:mb-0",
|
|
110
|
+
className,
|
|
111
|
+
)}
|
|
112
|
+
{...props}
|
|
113
|
+
/>
|
|
114
|
+
),
|
|
115
|
+
h6: ({ className, ...props }) => (
|
|
116
|
+
<h6
|
|
117
|
+
className={cn(
|
|
118
|
+
"aui-md-h6 my-4 font-semibold first:mt-0 last:mb-0",
|
|
119
|
+
className,
|
|
120
|
+
)}
|
|
121
|
+
{...props}
|
|
122
|
+
/>
|
|
123
|
+
),
|
|
124
|
+
p: ({ className, ...props }) => (
|
|
125
|
+
<p
|
|
126
|
+
className={cn(
|
|
127
|
+
"aui-md-p mt-5 mb-5 leading-7 first:mt-0 last:mb-0",
|
|
128
|
+
className,
|
|
129
|
+
)}
|
|
130
|
+
{...props}
|
|
131
|
+
/>
|
|
132
|
+
),
|
|
133
|
+
a: ({ className, ...props }) => (
|
|
134
|
+
<a
|
|
135
|
+
className={cn(
|
|
136
|
+
"aui-md-a font-medium text-primary underline underline-offset-4",
|
|
137
|
+
className,
|
|
138
|
+
)}
|
|
139
|
+
{...props}
|
|
140
|
+
/>
|
|
141
|
+
),
|
|
142
|
+
blockquote: ({ className, ...props }) => (
|
|
143
|
+
<blockquote
|
|
144
|
+
className={cn("aui-md-blockquote border-l-2 pl-6 italic", className)}
|
|
145
|
+
{...props}
|
|
146
|
+
/>
|
|
147
|
+
),
|
|
148
|
+
ul: ({ className, ...props }) => (
|
|
149
|
+
<ul
|
|
150
|
+
className={cn("aui-md-ul my-5 ml-6 list-disc [&>li]:mt-2", className)}
|
|
151
|
+
{...props}
|
|
152
|
+
/>
|
|
153
|
+
),
|
|
154
|
+
ol: ({ className, ...props }) => (
|
|
155
|
+
<ol
|
|
156
|
+
className={cn("aui-md-ol my-5 ml-6 list-decimal [&>li]:mt-2", className)}
|
|
157
|
+
{...props}
|
|
158
|
+
/>
|
|
159
|
+
),
|
|
160
|
+
hr: ({ className, ...props }) => (
|
|
161
|
+
<hr className={cn("aui-md-hr my-5 border-b", className)} {...props} />
|
|
162
|
+
),
|
|
163
|
+
table: ({ className, ...props }) => (
|
|
164
|
+
<table
|
|
165
|
+
className={cn(
|
|
166
|
+
"aui-md-table my-5 w-full border-separate border-spacing-0 overflow-y-auto",
|
|
167
|
+
className,
|
|
168
|
+
)}
|
|
169
|
+
{...props}
|
|
170
|
+
/>
|
|
171
|
+
),
|
|
172
|
+
th: ({ className, ...props }) => (
|
|
173
|
+
<th
|
|
174
|
+
className={cn(
|
|
175
|
+
"aui-md-th bg-muted px-4 py-2 text-left font-bold first:rounded-tl-lg last:rounded-tr-lg [[align=center]]:text-center [[align=right]]:text-right",
|
|
176
|
+
className,
|
|
177
|
+
)}
|
|
178
|
+
{...props}
|
|
179
|
+
/>
|
|
180
|
+
),
|
|
181
|
+
td: ({ className, ...props }) => (
|
|
182
|
+
<td
|
|
183
|
+
className={cn(
|
|
184
|
+
"aui-md-td border-b border-l px-4 py-2 text-left last:border-r [[align=center]]:text-center [[align=right]]:text-right",
|
|
185
|
+
className,
|
|
186
|
+
)}
|
|
187
|
+
{...props}
|
|
188
|
+
/>
|
|
189
|
+
),
|
|
190
|
+
tr: ({ className, ...props }) => (
|
|
191
|
+
<tr
|
|
192
|
+
className={cn(
|
|
193
|
+
"aui-md-tr m-0 border-b p-0 first:border-t [&:last-child>td:first-child]:rounded-bl-lg [&:last-child>td:last-child]:rounded-br-lg",
|
|
194
|
+
className,
|
|
195
|
+
)}
|
|
196
|
+
{...props}
|
|
197
|
+
/>
|
|
198
|
+
),
|
|
199
|
+
sup: ({ className, ...props }) => (
|
|
200
|
+
<sup
|
|
201
|
+
className={cn("aui-md-sup [&>a]:text-xs [&>a]:no-underline", className)}
|
|
202
|
+
{...props}
|
|
203
|
+
/>
|
|
204
|
+
),
|
|
205
|
+
pre: ({ className, ...props }) => (
|
|
206
|
+
<pre
|
|
207
|
+
className={cn(
|
|
208
|
+
"aui-md-pre overflow-x-auto rounded-t-none! rounded-b-lg bg-black p-4 text-white",
|
|
209
|
+
className,
|
|
210
|
+
)}
|
|
211
|
+
{...props}
|
|
212
|
+
/>
|
|
213
|
+
),
|
|
214
|
+
code: function Code({ className, ...props }) {
|
|
215
|
+
const isCodeBlock = useIsMarkdownCodeBlock();
|
|
216
|
+
return (
|
|
217
|
+
<code
|
|
218
|
+
className={cn(
|
|
219
|
+
!isCodeBlock &&
|
|
220
|
+
"aui-md-inline-code rounded border bg-muted font-semibold",
|
|
221
|
+
className,
|
|
222
|
+
)}
|
|
223
|
+
{...props}
|
|
224
|
+
/>
|
|
225
|
+
);
|
|
226
|
+
},
|
|
227
|
+
CodeHeader,
|
|
228
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { FC } from 'react';
|
|
2
|
+
import { ThreadListItemPrimitive, ThreadListPrimitive, useAssistantState } from '@assistant-ui/react';
|
|
3
|
+
import { ArchiveIcon, PlusIcon } from 'lucide-react';
|
|
4
|
+
|
|
5
|
+
import { Button } from '@/components/ui/button';
|
|
6
|
+
import { TooltipIconButton } from '@/components/assistant-ui/tooltip-icon-button';
|
|
7
|
+
import { Skeleton } from '@/components/ui/skeleton';
|
|
8
|
+
|
|
9
|
+
export const ThreadList: FC = () => {
|
|
10
|
+
return (
|
|
11
|
+
<ThreadListPrimitive.Root className="aui-root aui-thread-list-root flex flex-col items-stretch gap-1.5">
|
|
12
|
+
<ThreadListNew />
|
|
13
|
+
<ThreadListItems />
|
|
14
|
+
</ThreadListPrimitive.Root>
|
|
15
|
+
);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const ThreadListNew: FC = () => {
|
|
19
|
+
return (
|
|
20
|
+
<ThreadListPrimitive.New asChild>
|
|
21
|
+
<Button
|
|
22
|
+
className="aui-thread-list-new flex items-center justify-start gap-1 rounded-lg px-2.5 py-2 text-start hover:bg-muted data-active:bg-muted"
|
|
23
|
+
variant="ghost"
|
|
24
|
+
>
|
|
25
|
+
<PlusIcon />
|
|
26
|
+
New Thread
|
|
27
|
+
</Button>
|
|
28
|
+
</ThreadListPrimitive.New>
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const ThreadListItems: FC = () => {
|
|
33
|
+
const isLoading = useAssistantState(({ threads }) => threads.isLoading);
|
|
34
|
+
|
|
35
|
+
if (isLoading) {
|
|
36
|
+
return <ThreadListSkeleton />;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return <ThreadListPrimitive.Items components={{ ThreadListItem }} />;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const ThreadListSkeleton: FC = () => {
|
|
43
|
+
return (
|
|
44
|
+
<>
|
|
45
|
+
{Array.from({ length: 5 }, (_, i) => (
|
|
46
|
+
<div
|
|
47
|
+
key={i}
|
|
48
|
+
role="status"
|
|
49
|
+
aria-label="Loading threads"
|
|
50
|
+
aria-live="polite"
|
|
51
|
+
className="aui-thread-list-skeleton-wrapper flex items-center gap-2 rounded-md px-3 py-2"
|
|
52
|
+
>
|
|
53
|
+
<Skeleton className="aui-thread-list-skeleton h-[22px] grow" />
|
|
54
|
+
</div>
|
|
55
|
+
))}
|
|
56
|
+
</>
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const ThreadListItem: FC = () => {
|
|
61
|
+
return (
|
|
62
|
+
<ThreadListItemPrimitive.Root className="aui-thread-list-item flex items-center gap-2 rounded-lg transition-all hover:bg-muted focus-visible:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring data-active:bg-muted">
|
|
63
|
+
<ThreadListItemPrimitive.Trigger className="aui-thread-list-item-trigger grow px-3 py-2 text-start">
|
|
64
|
+
<ThreadListItemTitle />
|
|
65
|
+
</ThreadListItemPrimitive.Trigger>
|
|
66
|
+
<ThreadListItemArchive />
|
|
67
|
+
</ThreadListItemPrimitive.Root>
|
|
68
|
+
);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const ThreadListItemTitle: FC = () => {
|
|
72
|
+
return (
|
|
73
|
+
<span className="aui-thread-list-item-title text-sm">
|
|
74
|
+
<ThreadListItemPrimitive.Title fallback="New Chat" />
|
|
75
|
+
</span>
|
|
76
|
+
);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const ThreadListItemArchive: FC = () => {
|
|
80
|
+
return (
|
|
81
|
+
<ThreadListItemPrimitive.Archive asChild>
|
|
82
|
+
<TooltipIconButton
|
|
83
|
+
className="aui-thread-list-item-archive mr-3 ml-auto size-4 p-0 text-foreground hover:text-primary cursor-pointer"
|
|
84
|
+
variant="ghost"
|
|
85
|
+
tooltip="Archive thread"
|
|
86
|
+
>
|
|
87
|
+
<ArchiveIcon />
|
|
88
|
+
</TooltipIconButton>
|
|
89
|
+
</ThreadListItemPrimitive.Archive>
|
|
90
|
+
);
|
|
91
|
+
};
|