@aomi-labs/widget-lib 1.0.0 → 1.1.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.
- package/dist/accordion.json +18 -0
- package/dist/alert.json +17 -0
- package/dist/aomi-frame.json +24 -0
- package/dist/assistant-thread-list.json +22 -0
- package/dist/assistant-thread.json +27 -0
- package/dist/assistant-threadlist-sidebar.json +20 -0
- package/dist/assistant-tool-fallback.json +20 -0
- package/dist/avatar.json +17 -0
- package/dist/badge.json +17 -0
- package/dist/breadcrumb.json +17 -0
- package/dist/button.json +18 -0
- package/dist/card.json +15 -0
- package/dist/collapsible.json +17 -0
- package/dist/command.json +21 -0
- package/dist/dialog.json +18 -0
- package/dist/drawer.json +17 -0
- package/dist/input.json +15 -0
- package/dist/label.json +15 -0
- package/dist/notification.json +20 -0
- package/dist/popover.json +17 -0
- package/dist/registry.json +429 -0
- package/dist/separator.json +17 -0
- package/dist/sheet.json +18 -0
- package/dist/sidebar.json +18 -0
- package/dist/skeleton.json +15 -0
- package/dist/sonner.json +17 -0
- package/dist/tooltip.json +17 -0
- package/package.json +27 -85
- package/src/components/aomi-frame.tsx +221 -0
- package/src/components/assistant-ui/attachment.tsx +235 -0
- package/src/components/assistant-ui/markdown-text.tsx +228 -0
- package/src/components/assistant-ui/thread-list.tsx +106 -0
- package/src/components/assistant-ui/thread.tsx +476 -0
- package/src/components/assistant-ui/threadlist-sidebar.tsx +66 -0
- package/src/components/assistant-ui/tool-fallback.tsx +48 -0
- package/src/components/assistant-ui/tooltip-icon-button.tsx +42 -0
- package/src/components/control-bar/api-key-input.tsx +122 -0
- package/src/components/control-bar/index.tsx +58 -0
- package/src/components/control-bar/model-select.tsx +120 -0
- package/src/components/control-bar/namespace-select.tsx +117 -0
- package/src/components/control-bar/wallet-connect.tsx +75 -0
- package/src/components/test/ThreadContextTest.tsx +204 -0
- package/src/components/tools/example-tool/ExampleTool.tsx +102 -0
- package/src/components/ui/accordion.tsx +58 -0
- package/src/components/ui/alert.tsx +62 -0
- package/src/components/ui/avatar.tsx +53 -0
- package/src/components/ui/badge.tsx +37 -0
- package/src/components/ui/breadcrumb.tsx +109 -0
- package/src/components/ui/button.tsx +59 -0
- package/src/components/ui/card.tsx +86 -0
- package/src/components/ui/collapsible.tsx +12 -0
- package/src/components/ui/command.tsx +156 -0
- package/src/components/ui/dialog.tsx +143 -0
- package/src/components/ui/drawer.tsx +118 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/label.tsx +20 -0
- package/src/components/ui/notification.tsx +57 -0
- package/src/components/ui/popover.tsx +33 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/sheet.tsx +139 -0
- package/src/components/ui/sidebar.tsx +827 -0
- package/src/components/ui/skeleton.tsx +15 -0
- package/src/components/ui/sonner.tsx +29 -0
- package/src/components/ui/tooltip.tsx +61 -0
- package/src/hooks/use-mobile.ts +21 -0
- package/src/index.ts +26 -0
- package/src/registry.ts +218 -0
- package/{dist/styles.css → src/themes/default.css} +21 -3
- package/src/themes/tokens.config.ts +39 -0
- package/README.md +0 -41
- package/dist/index.cjs +0 -3780
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -302
- package/dist/index.d.ts +0 -302
- package/dist/index.js +0 -3696
- package/dist/index.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,108 +1,50 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aomi-labs/widget-lib",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.1.1",
|
|
4
|
+
"description": "Shadcn registry for the Aomi widget.",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "./
|
|
7
|
-
"module": "./dist/index.js",
|
|
8
|
-
"types": "./dist/index.d.ts",
|
|
6
|
+
"main": "./src/index.ts",
|
|
9
7
|
"exports": {
|
|
10
|
-
".":
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"./styles.css": "./dist/styles.css"
|
|
8
|
+
".": "./src/index.ts",
|
|
9
|
+
"./aomi-frame": "./src/components/aomi-frame.tsx",
|
|
10
|
+
"./aomi-frame-collapsible": "./src/components/aomi-frame-collapsible.tsx",
|
|
11
|
+
"./control-bar": "./src/components/control-bar/index.tsx",
|
|
12
|
+
"./components/ui/*": "./src/components/ui/*.tsx",
|
|
13
|
+
"./components/assistant-ui/*": "./src/components/assistant-ui/*.tsx",
|
|
14
|
+
"./components/control-bar/*": "./src/components/control-bar/*.tsx",
|
|
15
|
+
"./hooks/*": "./src/hooks/*.ts",
|
|
16
|
+
"./styles.css": "./src/themes/default.css",
|
|
17
|
+
"./themes/*.css": "./src/themes/*.css"
|
|
21
18
|
},
|
|
22
19
|
"files": [
|
|
20
|
+
"src",
|
|
23
21
|
"dist"
|
|
24
22
|
],
|
|
25
|
-
"prettier": {
|
|
26
|
-
"plugins": [
|
|
27
|
-
"prettier-plugin-tailwindcss"
|
|
28
|
-
],
|
|
29
|
-
"tailwindStylesheet": "app/globals.css"
|
|
30
|
-
},
|
|
31
|
-
"peerDependencies": {
|
|
32
|
-
"@assistant-ui/react": "^0.11.0",
|
|
33
|
-
"@assistant-ui/react-markdown": "^0.11.0",
|
|
34
|
-
"@radix-ui/react-avatar": "^1.0.0",
|
|
35
|
-
"@radix-ui/react-dialog": "^1.0.0",
|
|
36
|
-
"@radix-ui/react-separator": "^1.0.0",
|
|
37
|
-
"@radix-ui/react-slot": "^1.0.0",
|
|
38
|
-
"@radix-ui/react-tooltip": "^1.0.0",
|
|
39
|
-
"@tanstack/react-query": "^5.0.0",
|
|
40
|
-
"framer-motion": "^12.0.0",
|
|
41
|
-
"lucide-react": "^0.500.0",
|
|
42
|
-
"motion": "^12.0.0",
|
|
43
|
-
"next": ">=14.0.0",
|
|
44
|
-
"react": "^18.0.0 || ^19.0.0",
|
|
45
|
-
"react-dom": "^18.0.0 || ^19.0.0",
|
|
46
|
-
"react-shiki": "^0.9.0",
|
|
47
|
-
"remark-gfm": "^4.0.0",
|
|
48
|
-
"tailwindcss": "^4.0.0",
|
|
49
|
-
"zustand": "^5.0.0"
|
|
50
|
-
},
|
|
51
23
|
"dependencies": {
|
|
52
|
-
"class-variance-authority": "^0.7.1",
|
|
53
|
-
"clsx": "^2.1.1",
|
|
54
|
-
"tailwind-merge": "^3.3.1"
|
|
55
|
-
},
|
|
56
|
-
"devDependencies": {
|
|
57
|
-
"@ai-sdk/openai": "^2.0.46",
|
|
58
24
|
"@assistant-ui/react": "^0.11.28",
|
|
59
|
-
"@assistant-ui/react-ai-sdk": "^1.1.5",
|
|
60
25
|
"@assistant-ui/react-markdown": "^0.11.1",
|
|
61
|
-
"@
|
|
26
|
+
"@radix-ui/react-accordion": "^1.2.3",
|
|
62
27
|
"@radix-ui/react-avatar": "^1.1.10",
|
|
28
|
+
"@radix-ui/react-collapsible": "^1.1.12",
|
|
63
29
|
"@radix-ui/react-dialog": "^1.1.15",
|
|
30
|
+
"@radix-ui/react-popover": "^1.1.4",
|
|
64
31
|
"@radix-ui/react-separator": "^1.1.7",
|
|
65
32
|
"@radix-ui/react-slot": "^1.2.3",
|
|
66
33
|
"@radix-ui/react-tooltip": "^1.2.8",
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
"@tailwindcss/postcss": "^4",
|
|
70
|
-
"@tanstack/react-query": "^5.90.10",
|
|
71
|
-
"@types/node": "^24",
|
|
72
|
-
"@types/react": "^19",
|
|
73
|
-
"@types/react-dom": "^19",
|
|
74
|
-
"ai": "^5.0.65",
|
|
75
|
-
"eslint": "^9",
|
|
76
|
-
"eslint-config-next": "15.5.4",
|
|
77
|
-
"framer-motion": "^12.23.22",
|
|
34
|
+
"class-variance-authority": "^0.7.1",
|
|
35
|
+
"cmdk": "^1.0.0",
|
|
78
36
|
"lucide-react": "^0.545.0",
|
|
79
37
|
"motion": "^12.23.22",
|
|
80
|
-
"next": "15.5.7",
|
|
81
|
-
"prettier": "^3.6.2",
|
|
82
|
-
"prettier-plugin-tailwindcss": "^0.6.14",
|
|
83
|
-
"@typescript-eslint/eslint-plugin": "^8.18.0",
|
|
84
|
-
"@typescript-eslint/parser": "^8.18.0",
|
|
85
|
-
"react": "^19.2.0",
|
|
86
|
-
"react-dom": "^19.2.0",
|
|
87
|
-
"react-shiki": "^0.9.0",
|
|
88
38
|
"remark-gfm": "^4.0.1",
|
|
89
|
-
"
|
|
90
|
-
"
|
|
91
|
-
"
|
|
92
|
-
"
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
"
|
|
39
|
+
"sonner": "^1.7.4",
|
|
40
|
+
"vaul": "^1.1.2",
|
|
41
|
+
"zustand": "^5.0.8",
|
|
42
|
+
"@aomi-labs/react": "0.2.1"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"tsx": "^4.21.0"
|
|
96
46
|
},
|
|
97
47
|
"scripts": {
|
|
98
|
-
"
|
|
99
|
-
"dev:example:live": "pnpm run build:lib -- --watch & pnpm --filter example dev",
|
|
100
|
-
"build": "next build",
|
|
101
|
-
"build:lib": "tsup",
|
|
102
|
-
"vercel-build": "pnpm run build:lib && pnpm --filter example build",
|
|
103
|
-
"start": "next start",
|
|
104
|
-
"lint": "eslint .",
|
|
105
|
-
"prettier": "prettier --check .",
|
|
106
|
-
"prettier:fix": "prettier --write ."
|
|
48
|
+
"build": "tsx scripts/build-registry.js"
|
|
107
49
|
}
|
|
108
50
|
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
type CSSProperties,
|
|
5
|
+
type ReactNode,
|
|
6
|
+
type FC,
|
|
7
|
+
createContext,
|
|
8
|
+
useContext,
|
|
9
|
+
} from "react";
|
|
10
|
+
import { AomiRuntimeProvider, cn, useAomiRuntime } from "@aomi-labs/react";
|
|
11
|
+
import { Thread } from "@/components/assistant-ui/thread";
|
|
12
|
+
import { ThreadListSidebar } from "@/components/assistant-ui/threadlist-sidebar";
|
|
13
|
+
import { NotificationToaster } from "@/components/ui/notification";
|
|
14
|
+
import {
|
|
15
|
+
SidebarInset,
|
|
16
|
+
SidebarProvider,
|
|
17
|
+
SidebarTrigger,
|
|
18
|
+
} from "@/components/ui/sidebar";
|
|
19
|
+
import { Separator } from "@/components/ui/separator";
|
|
20
|
+
import {
|
|
21
|
+
Breadcrumb,
|
|
22
|
+
BreadcrumbItem,
|
|
23
|
+
BreadcrumbList,
|
|
24
|
+
} from "@/components/ui/breadcrumb";
|
|
25
|
+
import { ControlBar, type ControlBarProps } from "@/components/control-bar";
|
|
26
|
+
|
|
27
|
+
// =============================================================================
|
|
28
|
+
// Composer Control Context - signals Thread to show inline controls
|
|
29
|
+
// =============================================================================
|
|
30
|
+
|
|
31
|
+
const ComposerControlContext = createContext<boolean>(false);
|
|
32
|
+
|
|
33
|
+
export const useComposerControl = () => useContext(ComposerControlContext);
|
|
34
|
+
|
|
35
|
+
// =============================================================================
|
|
36
|
+
// Types
|
|
37
|
+
// =============================================================================
|
|
38
|
+
|
|
39
|
+
type RootProps = {
|
|
40
|
+
children?: ReactNode;
|
|
41
|
+
width?: CSSProperties["width"];
|
|
42
|
+
height?: CSSProperties["height"];
|
|
43
|
+
className?: string;
|
|
44
|
+
style?: CSSProperties;
|
|
45
|
+
/** Position of the wallet button in the sidebar */
|
|
46
|
+
walletPosition?: "header" | "footer" | null;
|
|
47
|
+
/** Backend URL for the Aomi runtime */
|
|
48
|
+
backendUrl?: string;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
type HeaderProps = {
|
|
52
|
+
children?: ReactNode;
|
|
53
|
+
/** Show the control bar in the header */
|
|
54
|
+
withControl?: boolean;
|
|
55
|
+
/** Props to pass to the ControlBar when withControl is true */
|
|
56
|
+
controlBarProps?: Omit<ControlBarProps, "children">;
|
|
57
|
+
className?: string;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
type ComposerProps = {
|
|
61
|
+
children?: ReactNode;
|
|
62
|
+
/** Show inline controls in the composer input area */
|
|
63
|
+
withControl?: boolean;
|
|
64
|
+
className?: string;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
type FrameControlBarProps = ControlBarProps;
|
|
68
|
+
|
|
69
|
+
// =============================================================================
|
|
70
|
+
// Compound Components
|
|
71
|
+
// =============================================================================
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Root component - provides all context and layout container
|
|
75
|
+
*/
|
|
76
|
+
const Root: FC<RootProps> = ({
|
|
77
|
+
children,
|
|
78
|
+
width = "100%",
|
|
79
|
+
height = "80vh",
|
|
80
|
+
className,
|
|
81
|
+
style,
|
|
82
|
+
walletPosition = "footer",
|
|
83
|
+
backendUrl,
|
|
84
|
+
}) => {
|
|
85
|
+
const resolvedBackendUrl =
|
|
86
|
+
backendUrl ??
|
|
87
|
+
process.env.NEXT_PUBLIC_BACKEND_URL ??
|
|
88
|
+
"http://localhost:8080";
|
|
89
|
+
const frameStyle: CSSProperties = { width, height, ...style };
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<AomiRuntimeProvider backendUrl={resolvedBackendUrl}>
|
|
93
|
+
<SidebarProvider>
|
|
94
|
+
<div
|
|
95
|
+
className={cn(
|
|
96
|
+
"rounded-4xl flex h-full w-full overflow-hidden bg-white shadow-2xl dark:bg-neutral-950",
|
|
97
|
+
className,
|
|
98
|
+
)}
|
|
99
|
+
style={frameStyle}
|
|
100
|
+
>
|
|
101
|
+
<ThreadListSidebar walletPosition={walletPosition} />
|
|
102
|
+
<SidebarInset className="relative flex flex-col">
|
|
103
|
+
{children}
|
|
104
|
+
</SidebarInset>
|
|
105
|
+
<NotificationToaster />
|
|
106
|
+
</div>
|
|
107
|
+
</SidebarProvider>
|
|
108
|
+
</AomiRuntimeProvider>
|
|
109
|
+
);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Header component - renders the header with optional control bar
|
|
114
|
+
*/
|
|
115
|
+
const Header: FC<HeaderProps> = ({
|
|
116
|
+
children,
|
|
117
|
+
withControl,
|
|
118
|
+
controlBarProps,
|
|
119
|
+
className,
|
|
120
|
+
}) => {
|
|
121
|
+
const { currentThreadId, getThreadMetadata } = useAomiRuntime();
|
|
122
|
+
const currentTitle = getThreadMetadata(currentThreadId)?.title ?? "New Chat";
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<header
|
|
126
|
+
className={cn(
|
|
127
|
+
"mt-1 flex h-14 shrink-0 items-center gap-2 px-3",
|
|
128
|
+
className,
|
|
129
|
+
)}
|
|
130
|
+
>
|
|
131
|
+
<SidebarTrigger />
|
|
132
|
+
<Separator orientation="vertical" className="mr-2 h-4" />
|
|
133
|
+
<Breadcrumb>
|
|
134
|
+
<BreadcrumbList>
|
|
135
|
+
<BreadcrumbItem className="hidden md:block">
|
|
136
|
+
{currentTitle}
|
|
137
|
+
</BreadcrumbItem>
|
|
138
|
+
</BreadcrumbList>
|
|
139
|
+
</Breadcrumb>
|
|
140
|
+
<div className="ml-auto flex items-center gap-2">
|
|
141
|
+
{withControl && <ControlBar {...controlBarProps} />}
|
|
142
|
+
{children}
|
|
143
|
+
</div>
|
|
144
|
+
</header>
|
|
145
|
+
);
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Composer component - renders the thread with optional inline controls
|
|
150
|
+
* When withControl={true}, controls appear inline in the composer input area
|
|
151
|
+
*/
|
|
152
|
+
const Composer: FC<ComposerProps> = ({
|
|
153
|
+
children,
|
|
154
|
+
withControl = false,
|
|
155
|
+
className,
|
|
156
|
+
}) => {
|
|
157
|
+
const { currentThreadId, threadViewKey } = useAomiRuntime();
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
<ComposerControlContext.Provider value={withControl}>
|
|
161
|
+
<div className={cn("flex flex-1 flex-col overflow-hidden", className)}>
|
|
162
|
+
<Thread key={`${currentThreadId}-${threadViewKey}`} />
|
|
163
|
+
{children}
|
|
164
|
+
</div>
|
|
165
|
+
</ComposerControlContext.Provider>
|
|
166
|
+
);
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* ControlBar component - wrapper for the control bar with frame styling
|
|
171
|
+
*/
|
|
172
|
+
const FrameControlBar: FC<FrameControlBarProps> = (props) => {
|
|
173
|
+
return <ControlBar {...props} />;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// =============================================================================
|
|
177
|
+
// Default Layout Component (Simple API)
|
|
178
|
+
// =============================================================================
|
|
179
|
+
|
|
180
|
+
type DefaultLayoutProps = Omit<RootProps, "children">;
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Default layout - controls are inline in the composer input area
|
|
184
|
+
* Usage: <AomiFrame /> or <AomiFrame walletPosition="header" />
|
|
185
|
+
*/
|
|
186
|
+
const DefaultLayout: FC<DefaultLayoutProps> = ({
|
|
187
|
+
walletPosition = "footer",
|
|
188
|
+
...props
|
|
189
|
+
}) => {
|
|
190
|
+
// Hide wallet in ControlBar when it's shown in sidebar
|
|
191
|
+
const hideWalletInControlBar = walletPosition !== null;
|
|
192
|
+
|
|
193
|
+
return (
|
|
194
|
+
<Root walletPosition={walletPosition} {...props}>
|
|
195
|
+
<Header
|
|
196
|
+
withControl
|
|
197
|
+
controlBarProps={{ hideWallet: hideWalletInControlBar }}
|
|
198
|
+
/>
|
|
199
|
+
<Composer />
|
|
200
|
+
</Root>
|
|
201
|
+
);
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
// =============================================================================
|
|
205
|
+
// Export Compound Component
|
|
206
|
+
// =============================================================================
|
|
207
|
+
|
|
208
|
+
export const AomiFrame = Object.assign(DefaultLayout, {
|
|
209
|
+
Root,
|
|
210
|
+
Header,
|
|
211
|
+
Composer,
|
|
212
|
+
ControlBar: FrameControlBar,
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// Re-export types for consumers
|
|
216
|
+
export type {
|
|
217
|
+
RootProps as AomiFrameRootProps,
|
|
218
|
+
HeaderProps as AomiFrameHeaderProps,
|
|
219
|
+
ComposerProps as AomiFrameComposerProps,
|
|
220
|
+
FrameControlBarProps as AomiFrameControlBarProps,
|
|
221
|
+
};
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { PropsWithChildren, useEffect, useState, type FC } from "react";
|
|
4
|
+
import Image from "next/image";
|
|
5
|
+
import { XIcon, PlusIcon, FileText } from "lucide-react";
|
|
6
|
+
import {
|
|
7
|
+
AttachmentPrimitive,
|
|
8
|
+
ComposerPrimitive,
|
|
9
|
+
MessagePrimitive,
|
|
10
|
+
useAssistantState,
|
|
11
|
+
useAssistantApi,
|
|
12
|
+
} from "@assistant-ui/react";
|
|
13
|
+
import { useShallow } from "zustand/shallow";
|
|
14
|
+
import {
|
|
15
|
+
Tooltip,
|
|
16
|
+
TooltipContent,
|
|
17
|
+
TooltipTrigger,
|
|
18
|
+
} from "@/components/ui/tooltip";
|
|
19
|
+
import {
|
|
20
|
+
Dialog,
|
|
21
|
+
DialogTitle,
|
|
22
|
+
DialogContent,
|
|
23
|
+
DialogTrigger,
|
|
24
|
+
} from "@/components/ui/dialog";
|
|
25
|
+
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
|
|
26
|
+
import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
|
|
27
|
+
import { cn } from "@aomi-labs/react";
|
|
28
|
+
|
|
29
|
+
const useFileSrc = (file: File | undefined) => {
|
|
30
|
+
const [src, setSrc] = useState<string | undefined>(undefined);
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (!file) {
|
|
34
|
+
setSrc(undefined);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const objectUrl = URL.createObjectURL(file);
|
|
39
|
+
setSrc(objectUrl);
|
|
40
|
+
|
|
41
|
+
return () => {
|
|
42
|
+
URL.revokeObjectURL(objectUrl);
|
|
43
|
+
};
|
|
44
|
+
}, [file]);
|
|
45
|
+
|
|
46
|
+
return src;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const useAttachmentSrc = () => {
|
|
50
|
+
const { file, src } = useAssistantState(
|
|
51
|
+
useShallow(({ attachment }): { file?: File; src?: string } => {
|
|
52
|
+
if (attachment.type !== "image") return {};
|
|
53
|
+
if (attachment.file) return { file: attachment.file };
|
|
54
|
+
const src = attachment.content?.filter((c) => c.type === "image")[0]
|
|
55
|
+
?.image;
|
|
56
|
+
if (!src) return {};
|
|
57
|
+
return { src };
|
|
58
|
+
}),
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
return useFileSrc(file) ?? src;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
type AttachmentPreviewProps = {
|
|
65
|
+
src: string;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const AttachmentPreview: FC<AttachmentPreviewProps> = ({ src }) => {
|
|
69
|
+
const [isLoaded, setIsLoaded] = useState(false);
|
|
70
|
+
return (
|
|
71
|
+
<Image
|
|
72
|
+
src={src}
|
|
73
|
+
alt="Image Preview"
|
|
74
|
+
width={1}
|
|
75
|
+
height={1}
|
|
76
|
+
className={
|
|
77
|
+
isLoaded
|
|
78
|
+
? "aui-attachment-preview-image-loaded block h-auto max-h-[80vh] w-auto max-w-full object-contain"
|
|
79
|
+
: "aui-attachment-preview-image-loading hidden"
|
|
80
|
+
}
|
|
81
|
+
onLoadingComplete={() => setIsLoaded(true)}
|
|
82
|
+
priority={false}
|
|
83
|
+
/>
|
|
84
|
+
);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const AttachmentPreviewDialog: FC<PropsWithChildren> = ({ children }) => {
|
|
88
|
+
const src = useAttachmentSrc();
|
|
89
|
+
|
|
90
|
+
if (!src) return children;
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<Dialog>
|
|
94
|
+
<DialogTrigger
|
|
95
|
+
className="aui-attachment-preview-trigger hover:bg-accent/50 cursor-pointer transition-colors"
|
|
96
|
+
asChild
|
|
97
|
+
>
|
|
98
|
+
{children}
|
|
99
|
+
</DialogTrigger>
|
|
100
|
+
<DialogContent className="aui-attachment-preview-dialog-content [&_svg]:text-background [&>button]:bg-foreground/60 [&>button]:hover:[&_svg]:text-destructive p-2 sm:max-w-3xl [&>button]:rounded-full [&>button]:p-1 [&>button]:opacity-100 [&>button]:!ring-0">
|
|
101
|
+
<DialogTitle className="aui-sr-only sr-only">
|
|
102
|
+
Image Attachment Preview
|
|
103
|
+
</DialogTitle>
|
|
104
|
+
<div className="aui-attachment-preview bg-background relative mx-auto flex max-h-[80dvh] w-full items-center justify-center overflow-hidden">
|
|
105
|
+
<AttachmentPreview src={src} />
|
|
106
|
+
</div>
|
|
107
|
+
</DialogContent>
|
|
108
|
+
</Dialog>
|
|
109
|
+
);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const AttachmentThumb: FC = () => {
|
|
113
|
+
const isImage = useAssistantState(
|
|
114
|
+
({ attachment }) => attachment.type === "image",
|
|
115
|
+
);
|
|
116
|
+
const src = useAttachmentSrc();
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<Avatar className="aui-attachment-tile-avatar h-full w-full rounded-none">
|
|
120
|
+
<AvatarImage
|
|
121
|
+
src={src}
|
|
122
|
+
alt="Attachment preview"
|
|
123
|
+
className="aui-attachment-tile-image object-cover"
|
|
124
|
+
/>
|
|
125
|
+
<AvatarFallback delayMs={isImage ? 200 : 0}>
|
|
126
|
+
<FileText className="aui-attachment-tile-fallback-icon text-muted-foreground size-8" />
|
|
127
|
+
</AvatarFallback>
|
|
128
|
+
</Avatar>
|
|
129
|
+
);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const AttachmentUI: FC = () => {
|
|
133
|
+
const api = useAssistantApi();
|
|
134
|
+
const isComposer = api.attachment.source === "composer";
|
|
135
|
+
|
|
136
|
+
const isImage = useAssistantState(
|
|
137
|
+
({ attachment }) => attachment.type === "image",
|
|
138
|
+
);
|
|
139
|
+
const typeLabel = useAssistantState(({ attachment }) => {
|
|
140
|
+
const type = attachment.type;
|
|
141
|
+
switch (type) {
|
|
142
|
+
case "image":
|
|
143
|
+
return "Image";
|
|
144
|
+
case "document":
|
|
145
|
+
return "Document";
|
|
146
|
+
case "file":
|
|
147
|
+
return "File";
|
|
148
|
+
default:
|
|
149
|
+
const _exhaustiveCheck: never = type;
|
|
150
|
+
throw new Error(`Unknown attachment type: ${_exhaustiveCheck}`);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
<Tooltip>
|
|
156
|
+
<AttachmentPrimitive.Root
|
|
157
|
+
className={cn(
|
|
158
|
+
"aui-attachment-root relative",
|
|
159
|
+
isImage &&
|
|
160
|
+
"aui-attachment-root-composer only:[&>#attachment-tile]:size-24",
|
|
161
|
+
)}
|
|
162
|
+
>
|
|
163
|
+
<AttachmentPreviewDialog>
|
|
164
|
+
<TooltipTrigger asChild>
|
|
165
|
+
<div
|
|
166
|
+
className={cn(
|
|
167
|
+
"aui-attachment-tile bg-muted size-14 cursor-pointer overflow-hidden rounded-[14px] border transition-opacity hover:opacity-75",
|
|
168
|
+
isComposer &&
|
|
169
|
+
"aui-attachment-tile-composer border-foreground/20",
|
|
170
|
+
)}
|
|
171
|
+
role="button"
|
|
172
|
+
id="attachment-tile"
|
|
173
|
+
aria-label={`${typeLabel} attachment`}
|
|
174
|
+
>
|
|
175
|
+
<AttachmentThumb />
|
|
176
|
+
</div>
|
|
177
|
+
</TooltipTrigger>
|
|
178
|
+
</AttachmentPreviewDialog>
|
|
179
|
+
{isComposer && <AttachmentRemove />}
|
|
180
|
+
</AttachmentPrimitive.Root>
|
|
181
|
+
<TooltipContent side="top">
|
|
182
|
+
<AttachmentPrimitive.Name />
|
|
183
|
+
</TooltipContent>
|
|
184
|
+
</Tooltip>
|
|
185
|
+
);
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const AttachmentRemove: FC = () => {
|
|
189
|
+
return (
|
|
190
|
+
<AttachmentPrimitive.Remove asChild>
|
|
191
|
+
<TooltipIconButton
|
|
192
|
+
tooltip="Remove file"
|
|
193
|
+
className="aui-attachment-tile-remove text-muted-foreground hover:[&_svg]:text-destructive absolute right-1.5 top-1.5 size-3.5 rounded-full bg-white opacity-100 shadow-sm hover:!bg-white [&_svg]:text-black"
|
|
194
|
+
side="top"
|
|
195
|
+
>
|
|
196
|
+
<XIcon className="aui-attachment-remove-icon size-3 dark:stroke-[2.5px]" />
|
|
197
|
+
</TooltipIconButton>
|
|
198
|
+
</AttachmentPrimitive.Remove>
|
|
199
|
+
);
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
export const UserMessageAttachments: FC = () => {
|
|
203
|
+
return (
|
|
204
|
+
<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">
|
|
205
|
+
<MessagePrimitive.Attachments components={{ Attachment: AttachmentUI }} />
|
|
206
|
+
</div>
|
|
207
|
+
);
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
export const ComposerAttachments: FC = () => {
|
|
211
|
+
return (
|
|
212
|
+
<div className="aui-composer-attachments mb-2 flex w-full flex-row items-center gap-2 overflow-x-auto px-1.5 pb-1 pt-0.5 empty:hidden">
|
|
213
|
+
<ComposerPrimitive.Attachments
|
|
214
|
+
components={{ Attachment: AttachmentUI }}
|
|
215
|
+
/>
|
|
216
|
+
</div>
|
|
217
|
+
);
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
export const ComposerAddAttachment: FC = () => {
|
|
221
|
+
return (
|
|
222
|
+
<ComposerPrimitive.AddAttachment asChild>
|
|
223
|
+
<TooltipIconButton
|
|
224
|
+
tooltip="Add Attachment"
|
|
225
|
+
side="bottom"
|
|
226
|
+
variant="ghost"
|
|
227
|
+
size="icon"
|
|
228
|
+
className="aui-composer-add-attachment hover:bg-muted-foreground/15 dark:border-muted-foreground/15 dark:hover:bg-muted-foreground/30 size-[34px] rounded-full p-1 text-xs font-semibold"
|
|
229
|
+
aria-label="Add Attachment"
|
|
230
|
+
>
|
|
231
|
+
<PlusIcon className="aui-attachment-add-icon size-5 stroke-[1.5px]" />
|
|
232
|
+
</TooltipIconButton>
|
|
233
|
+
</ComposerPrimitive.AddAttachment>
|
|
234
|
+
);
|
|
235
|
+
};
|