@aomi-labs/widget-lib 1.1.0 → 1.1.2
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/aomi-frame.json +1 -1
- package/dist/assistant-thread-list.json +1 -1
- package/dist/assistant-thread.json +1 -1
- package/dist/assistant-threadlist-sidebar.json +1 -1
- package/dist/sidebar.json +1 -1
- package/package.json +9 -3
- package/src/components/aomi-frame.tsx +171 -78
- package/src/components/assistant-ui/thread-list.tsx +5 -5
- package/src/components/assistant-ui/thread.tsx +27 -8
- package/src/components/assistant-ui/threadlist-sidebar.tsx +24 -29
- 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/ui/sidebar.tsx +10 -3
- package/SHADCN-FETCH-GUIDE.md +0 -105
- package/components.json +0 -10
- package/dist/assistant-threadlist-collapsible.json +0 -23
- package/scripts/build-registry.js +0 -74
- package/tsconfig.json +0 -19
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { type CSSProperties, type ReactNode } from "react";
|
|
4
3
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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";
|
|
10
11
|
import { Thread } from "@/components/assistant-ui/thread";
|
|
11
12
|
import { ThreadListSidebar } from "@/components/assistant-ui/threadlist-sidebar";
|
|
13
|
+
import { NotificationToaster } from "@/components/ui/notification";
|
|
12
14
|
import {
|
|
13
15
|
SidebarInset,
|
|
14
16
|
SidebarProvider,
|
|
@@ -19,110 +21,201 @@ import {
|
|
|
19
21
|
Breadcrumb,
|
|
20
22
|
BreadcrumbItem,
|
|
21
23
|
BreadcrumbList,
|
|
22
|
-
BreadcrumbSeparator,
|
|
23
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);
|
|
24
34
|
|
|
25
35
|
// =============================================================================
|
|
26
36
|
// Types
|
|
27
37
|
// =============================================================================
|
|
28
38
|
|
|
29
|
-
type
|
|
39
|
+
type RootProps = {
|
|
40
|
+
children?: ReactNode;
|
|
30
41
|
width?: CSSProperties["width"];
|
|
31
42
|
height?: CSSProperties["height"];
|
|
32
43
|
className?: string;
|
|
33
44
|
style?: CSSProperties;
|
|
34
|
-
/**
|
|
35
|
-
|
|
36
|
-
/**
|
|
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 = {
|
|
37
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;
|
|
38
58
|
};
|
|
39
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
|
+
|
|
40
69
|
// =============================================================================
|
|
41
|
-
//
|
|
70
|
+
// Compound Components
|
|
42
71
|
// =============================================================================
|
|
43
72
|
|
|
44
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Root component - provides all context and layout container
|
|
75
|
+
*/
|
|
76
|
+
const Root: FC<RootProps> = ({
|
|
77
|
+
children,
|
|
45
78
|
width = "100%",
|
|
46
79
|
height = "80vh",
|
|
47
80
|
className,
|
|
48
81
|
style,
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
const
|
|
53
|
-
|
|
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 };
|
|
54
90
|
|
|
55
91
|
return (
|
|
56
|
-
<AomiRuntimeProvider backendUrl={
|
|
57
|
-
<
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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>
|
|
66
108
|
</AomiRuntimeProvider>
|
|
67
109
|
);
|
|
68
110
|
};
|
|
69
111
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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";
|
|
73
123
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
+
);
|
|
81
146
|
};
|
|
82
147
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
walletFooter,
|
|
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> = ({
|
|
89
153
|
children,
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
const frameStyle: CSSProperties = { width, height, ...style };
|
|
154
|
+
withControl = false,
|
|
155
|
+
className,
|
|
156
|
+
}) => {
|
|
157
|
+
const { currentThreadId, threadViewKey } = useAomiRuntime();
|
|
96
158
|
|
|
97
159
|
return (
|
|
98
|
-
<
|
|
99
|
-
{
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
"flex h-full w-full overflow-hidden rounded-2xl bg-white shadow-2xl dark:bg-neutral-950",
|
|
103
|
-
className,
|
|
104
|
-
)}
|
|
105
|
-
style={frameStyle}
|
|
106
|
-
>
|
|
107
|
-
<ThreadListSidebar footer={walletFooter?.({ user, setUser })} />
|
|
108
|
-
<SidebarInset className="relative">
|
|
109
|
-
<header className="mt-1 flex h-14 shrink-0 items-center gap-2 border-b px-3">
|
|
110
|
-
<SidebarTrigger />
|
|
111
|
-
<Separator orientation="vertical" className="mr-2 h-4" />
|
|
112
|
-
<Breadcrumb>
|
|
113
|
-
<BreadcrumbList>
|
|
114
|
-
<BreadcrumbItem className="hidden md:block">
|
|
115
|
-
{currentTitle}
|
|
116
|
-
</BreadcrumbItem>
|
|
117
|
-
<BreadcrumbSeparator className="hidden md:block" />
|
|
118
|
-
</BreadcrumbList>
|
|
119
|
-
</Breadcrumb>
|
|
120
|
-
</header>
|
|
121
|
-
<div className="flex-1 overflow-hidden">
|
|
122
|
-
<Thread key={`${currentThreadId}-${threadViewKey}`} />
|
|
123
|
-
</div>
|
|
124
|
-
</SidebarInset>
|
|
160
|
+
<ComposerControlContext.Provider value={withControl}>
|
|
161
|
+
<div className={cn("flex flex-1 flex-col overflow-hidden", className)}>
|
|
162
|
+
<Thread key={`${currentThreadId}-${threadViewKey}`} />
|
|
163
|
+
{children}
|
|
125
164
|
</div>
|
|
126
|
-
</
|
|
165
|
+
</ComposerControlContext.Provider>
|
|
127
166
|
);
|
|
128
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
|
+
};
|
|
@@ -14,7 +14,7 @@ import { Skeleton } from "@/components/ui/skeleton";
|
|
|
14
14
|
|
|
15
15
|
export const ThreadList: FC = () => {
|
|
16
16
|
return (
|
|
17
|
-
<ThreadListPrimitive.Root className="aui-root aui-thread-list-root flex flex-col items-stretch gap-1
|
|
17
|
+
<ThreadListPrimitive.Root className="aui-root aui-thread-list-root flex list-none flex-col items-stretch gap-1 pl-2">
|
|
18
18
|
<ThreadListNew />
|
|
19
19
|
<ThreadListItems />
|
|
20
20
|
</ThreadListPrimitive.Root>
|
|
@@ -25,10 +25,10 @@ const ThreadListNew: FC = () => {
|
|
|
25
25
|
return (
|
|
26
26
|
<ThreadListPrimitive.New asChild>
|
|
27
27
|
<Button
|
|
28
|
-
className="aui-thread-list-new hover:bg-
|
|
28
|
+
className="aui-thread-list-new hover:bg-accent data-active:bg-accent flex items-center justify-start gap-2 rounded-full px-4 py-2 text-start"
|
|
29
29
|
variant="ghost"
|
|
30
30
|
>
|
|
31
|
-
<PlusIcon />
|
|
31
|
+
<PlusIcon className="size-4" />
|
|
32
32
|
New Chat
|
|
33
33
|
</Button>
|
|
34
34
|
</ThreadListPrimitive.New>
|
|
@@ -65,8 +65,8 @@ const ThreadListSkeleton: FC = () => {
|
|
|
65
65
|
|
|
66
66
|
const ThreadListItem: FC = () => {
|
|
67
67
|
return (
|
|
68
|
-
<ThreadListItemPrimitive.Root className="aui-thread-list-item hover:bg-
|
|
69
|
-
<ThreadListItemPrimitive.Trigger className="aui-thread-list-item-trigger flex-grow
|
|
68
|
+
<ThreadListItemPrimitive.Root className="aui-thread-list-item hover:bg-accent focus-visible:bg-accent data-active:bg-accent flex items-center gap-2 rounded-full pl-4 transition-all focus-visible:outline-none">
|
|
69
|
+
<ThreadListItemPrimitive.Trigger className="aui-thread-list-item-trigger flex-grow py-2 text-start">
|
|
70
70
|
<ThreadListItemTitle />
|
|
71
71
|
</ThreadListItemPrimitive.Trigger>
|
|
72
72
|
<ThreadListItemDelete />
|
|
@@ -37,6 +37,10 @@ import {
|
|
|
37
37
|
} from "@/components/assistant-ui/attachment";
|
|
38
38
|
|
|
39
39
|
import { cn, useNotification, useThreadContext } from "@aomi-labs/react";
|
|
40
|
+
import { useComposerControl } from "@/components/aomi-frame";
|
|
41
|
+
import { ModelSelect } from "@/components/control-bar/model-select";
|
|
42
|
+
import { NamespaceSelect } from "@/components/control-bar/namespace-select";
|
|
43
|
+
import { ApiKeyInput } from "@/components/control-bar/api-key-input";
|
|
40
44
|
import { useAssistantApi, useMessage } from "@assistant-ui/react";
|
|
41
45
|
|
|
42
46
|
const seenSystemMessages = new Set<string>();
|
|
@@ -64,7 +68,7 @@ export const Thread: FC = () => {
|
|
|
64
68
|
["--thread-max-width" as string]: "44rem",
|
|
65
69
|
}}
|
|
66
70
|
>
|
|
67
|
-
<ThreadPrimitive.Viewport className="aui-thread-viewport relative flex flex-1 flex-col overflow-x-auto overflow-y-scroll px-
|
|
71
|
+
<ThreadPrimitive.Viewport className="aui-thread-viewport relative flex flex-1 flex-col overflow-x-auto overflow-y-scroll px-2">
|
|
68
72
|
<ThreadPrimitive.If empty>
|
|
69
73
|
<ThreadWelcome />
|
|
70
74
|
</ThreadPrimitive.If>
|
|
@@ -173,10 +177,10 @@ const ThreadSuggestions: FC = () => {
|
|
|
173
177
|
>
|
|
174
178
|
<Button
|
|
175
179
|
variant="ghost"
|
|
176
|
-
className="aui-thread-welcome-suggestion @md:flex-col dark:hover:bg-accent/60 h-auto w-full flex-1 flex-wrap items-start justify-start gap-1 rounded-3xl border px-5 py-4 text-left text-sm"
|
|
180
|
+
className="aui-thread-welcome-suggestion @md:flex-col dark:hover:bg-accent/60 h-auto w-full flex-1 flex-wrap items-start justify-start gap-1 rounded-3xl border px-5 py-4 text-left text-sm font-normal"
|
|
177
181
|
aria-label={suggestedAction.action}
|
|
178
182
|
>
|
|
179
|
-
<span className="aui-thread-welcome-suggestion-text-1
|
|
183
|
+
<span className="aui-thread-welcome-suggestion-text-1">
|
|
180
184
|
{suggestedAction.title}
|
|
181
185
|
</span>
|
|
182
186
|
<span className="aui-thread-welcome-suggestion-text-2 text-muted-foreground">
|
|
@@ -194,11 +198,11 @@ const Composer: FC = () => {
|
|
|
194
198
|
return (
|
|
195
199
|
<div className="aui-composer-wrapper bg-background sticky bottom-0 mx-auto flex w-full max-w-[var(--thread-max-width)] flex-col gap-4 overflow-visible rounded-t-3xl pb-4 md:pb-6">
|
|
196
200
|
<ThreadScrollToBottom />
|
|
197
|
-
<ComposerPrimitive.Root className="aui-composer-root rounded-4xl
|
|
201
|
+
<ComposerPrimitive.Root className="aui-composer-root rounded-4xl bg-sidebar text-card-foreground relative flex w-full flex-col px-1 pt-2">
|
|
198
202
|
<ComposerAttachments />
|
|
199
203
|
<ComposerPrimitive.Input
|
|
200
204
|
placeholder="Send a message..."
|
|
201
|
-
className="aui-composer-input placeholder:text-muted-foreground focus:outline-primary ml-3 mt-2 max-h-32 min-h-16 w-full resize-none bg-transparent px-3.5 pb-3 pt-1.5 text-sm
|
|
205
|
+
className="aui-composer-input text-foreground dark:text-white placeholder:text-muted-foreground focus:outline-primary ml-3 mt-2 max-h-32 min-h-16 w-full resize-none bg-transparent px-3.5 pb-3 pt-1.5 text-sm outline-none"
|
|
202
206
|
rows={1}
|
|
203
207
|
autoFocus
|
|
204
208
|
aria-label="Message input"
|
|
@@ -210,9 +214,24 @@ const Composer: FC = () => {
|
|
|
210
214
|
};
|
|
211
215
|
|
|
212
216
|
const ComposerAction: FC = () => {
|
|
217
|
+
const showInlineControls = useComposerControl();
|
|
218
|
+
|
|
213
219
|
return (
|
|
214
|
-
<div className="aui-composer-action-wrapper relative mx-1 mb-2 mt-2 flex items-center
|
|
215
|
-
|
|
220
|
+
<div className="aui-composer-action-wrapper relative mx-1 mb-2 mt-2 flex items-center">
|
|
221
|
+
{/* Show attachment button only when inline controls are hidden */}
|
|
222
|
+
{!showInlineControls && <ComposerAddAttachment />}
|
|
223
|
+
|
|
224
|
+
{/* Inline controls: [Model ▾] [Agent ▾] [🔑] */}
|
|
225
|
+
{showInlineControls && (
|
|
226
|
+
<div className="ml-2 flex items-center gap-2">
|
|
227
|
+
<ModelSelect />
|
|
228
|
+
<NamespaceSelect />
|
|
229
|
+
<ApiKeyInput />
|
|
230
|
+
</div>
|
|
231
|
+
)}
|
|
232
|
+
|
|
233
|
+
{/* Spacer */}
|
|
234
|
+
<div className="flex-1" />
|
|
216
235
|
|
|
217
236
|
<ThreadPrimitive.If running={false}>
|
|
218
237
|
<ComposerPrimitive.Send asChild>
|
|
@@ -355,7 +374,7 @@ const EditComposer: FC = () => {
|
|
|
355
374
|
<div className="aui-edit-composer-wrapper mx-auto flex w-full max-w-[var(--thread-max-width)] flex-col gap-4 px-2 first:mt-4">
|
|
356
375
|
<ComposerPrimitive.Root className="aui-edit-composer-root max-w-7/8 bg-muted ml-auto flex w-full flex-col rounded-xl">
|
|
357
376
|
<ComposerPrimitive.Input
|
|
358
|
-
className="aui-edit-composer-input text-foreground flex min-h-[60px] w-full resize-none bg-transparent p-4 outline-none"
|
|
377
|
+
className="aui-edit-composer-input text-foreground dark:text-white flex min-h-[60px] w-full resize-none bg-transparent p-4 outline-none"
|
|
359
378
|
autoFocus
|
|
360
379
|
/>
|
|
361
380
|
|
|
@@ -14,14 +14,15 @@ import {
|
|
|
14
14
|
SidebarRail,
|
|
15
15
|
} from "@/components/ui/sidebar";
|
|
16
16
|
import { ThreadList } from "@/components/assistant-ui/thread-list";
|
|
17
|
+
import { WalletConnect } from "@/components/control-bar/wallet-connect";
|
|
17
18
|
|
|
18
19
|
type ThreadListSidebarProps = React.ComponentProps<typeof Sidebar> & {
|
|
19
|
-
/**
|
|
20
|
-
|
|
20
|
+
/** Position of the wallet button: "header" (top), "footer" (bottom), or null (hidden) */
|
|
21
|
+
walletPosition?: "header" | "footer" | null;
|
|
21
22
|
};
|
|
22
23
|
|
|
23
24
|
export function ThreadListSidebar({
|
|
24
|
-
footer,
|
|
25
|
+
walletPosition = "footer",
|
|
25
26
|
...props
|
|
26
27
|
}: ThreadListSidebarProps) {
|
|
27
28
|
return (
|
|
@@ -32,38 +33,32 @@ export function ThreadListSidebar({
|
|
|
32
33
|
{...props}
|
|
33
34
|
>
|
|
34
35
|
<SidebarHeader className="aomi-sidebar-header">
|
|
35
|
-
<div className="aomi-sidebar-header-content flex items-center justify-between">
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
/>
|
|
53
|
-
</div>
|
|
54
|
-
</Link>
|
|
55
|
-
</SidebarMenuButton>
|
|
56
|
-
</SidebarMenuItem>
|
|
57
|
-
</SidebarMenu>
|
|
36
|
+
<div className="aomi-sidebar-header-content mt-5 mb-5 ml-5 flex items-center justify-between">
|
|
37
|
+
<Link
|
|
38
|
+
href="https://aomi.dev"
|
|
39
|
+
target="_blank"
|
|
40
|
+
rel="noopener noreferrer"
|
|
41
|
+
className="flex items-center justify-center"
|
|
42
|
+
>
|
|
43
|
+
<Image
|
|
44
|
+
src="/assets/images/bubble.svg"
|
|
45
|
+
alt="Logo"
|
|
46
|
+
width={25}
|
|
47
|
+
height={25}
|
|
48
|
+
className="aomi-sidebar-header-icon size-6"
|
|
49
|
+
priority
|
|
50
|
+
/>
|
|
51
|
+
</Link>
|
|
52
|
+
{walletPosition === "header" && <WalletConnect />}
|
|
58
53
|
</div>
|
|
59
54
|
</SidebarHeader>
|
|
60
55
|
<SidebarContent className="aomi-sidebar-content">
|
|
61
56
|
<ThreadList />
|
|
62
57
|
</SidebarContent>
|
|
63
58
|
<SidebarRail />
|
|
64
|
-
{footer && (
|
|
65
|
-
<SidebarFooter className="aomi-sidebar-footer border-
|
|
66
|
-
|
|
59
|
+
{walletPosition === "footer" && (
|
|
60
|
+
<SidebarFooter className="aomi-sidebar-footer border-0 mx-5 mb-5">
|
|
61
|
+
<WalletConnect className="w-full" />
|
|
67
62
|
</SidebarFooter>
|
|
68
63
|
)}
|
|
69
64
|
</Sidebar>
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState, type FC } from "react";
|
|
4
|
+
import { KeyIcon, CheckIcon, EyeIcon, EyeOffIcon } from "lucide-react";
|
|
5
|
+
import { useControl, cn } from "@aomi-labs/react";
|
|
6
|
+
import { Button } from "@/components/ui/button";
|
|
7
|
+
import { Input } from "@/components/ui/input";
|
|
8
|
+
import { Label } from "@/components/ui/label";
|
|
9
|
+
import {
|
|
10
|
+
Dialog,
|
|
11
|
+
DialogContent,
|
|
12
|
+
DialogDescription,
|
|
13
|
+
DialogHeader,
|
|
14
|
+
DialogTitle,
|
|
15
|
+
DialogTrigger,
|
|
16
|
+
DialogFooter,
|
|
17
|
+
} from "@/components/ui/dialog";
|
|
18
|
+
|
|
19
|
+
export type ApiKeyInputProps = {
|
|
20
|
+
className?: string;
|
|
21
|
+
title?: string;
|
|
22
|
+
description?: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const ApiKeyInput: FC<ApiKeyInputProps> = ({
|
|
26
|
+
className,
|
|
27
|
+
title = "Aomi API Key",
|
|
28
|
+
description = "Enter your API key to authenticate with Aomi services.",
|
|
29
|
+
}) => {
|
|
30
|
+
const { state, setState } = useControl();
|
|
31
|
+
const [open, setOpen] = useState(false);
|
|
32
|
+
const [inputValue, setInputValue] = useState("");
|
|
33
|
+
const [showKey, setShowKey] = useState(false);
|
|
34
|
+
|
|
35
|
+
const hasApiKey = Boolean(state.apiKey);
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<Dialog open={open} onOpenChange={setOpen}>
|
|
39
|
+
<DialogTrigger asChild>
|
|
40
|
+
<Button
|
|
41
|
+
variant="ghost"
|
|
42
|
+
size="icon"
|
|
43
|
+
className={cn("relative rounded-full", className)}
|
|
44
|
+
aria-label={hasApiKey ? "API key configured" : "Set API key"}
|
|
45
|
+
>
|
|
46
|
+
<KeyIcon className={cn("h-4 w-4", hasApiKey && "text-green-500")} />
|
|
47
|
+
{hasApiKey && (
|
|
48
|
+
<span className="absolute -right-0.5 -top-0.5 h-2 w-2 rounded-full bg-green-500" />
|
|
49
|
+
)}
|
|
50
|
+
</Button>
|
|
51
|
+
</DialogTrigger>
|
|
52
|
+
<DialogContent className="max-w-[280px] pl-4 rounded-3xl">
|
|
53
|
+
<DialogHeader className="border-0">
|
|
54
|
+
<DialogTitle>{title}</DialogTitle>
|
|
55
|
+
<DialogDescription>{description}</DialogDescription>
|
|
56
|
+
</DialogHeader>
|
|
57
|
+
<div className="grid gap-4 py-4">
|
|
58
|
+
<div className="grid gap-2">
|
|
59
|
+
<Label htmlFor="api-key" className="mb-2">
|
|
60
|
+
API Key
|
|
61
|
+
</Label>
|
|
62
|
+
<div className="relative">
|
|
63
|
+
<Input
|
|
64
|
+
id="api-key"
|
|
65
|
+
type={showKey ? "text" : "password"}
|
|
66
|
+
placeholder={hasApiKey ? "********" : "Enter your API key"}
|
|
67
|
+
value={inputValue}
|
|
68
|
+
onChange={(e) => setInputValue(e.target.value)}
|
|
69
|
+
className="rounded-full pr-10"
|
|
70
|
+
/>
|
|
71
|
+
<Button
|
|
72
|
+
type="button"
|
|
73
|
+
variant="ghost"
|
|
74
|
+
size="icon"
|
|
75
|
+
className="absolute right-0 top-0 h-full px-3 hover:bg-transparent"
|
|
76
|
+
onClick={() => setShowKey(!showKey)}
|
|
77
|
+
aria-label={showKey ? "Hide API key" : "Show API key"}
|
|
78
|
+
>
|
|
79
|
+
{showKey ? (
|
|
80
|
+
<EyeIcon className="h-4 w-4" />
|
|
81
|
+
) : (
|
|
82
|
+
<EyeOffIcon className="h-4 w-4" />
|
|
83
|
+
)}
|
|
84
|
+
</Button>
|
|
85
|
+
</div>
|
|
86
|
+
{hasApiKey && (
|
|
87
|
+
<p className="text-muted-foreground text-xs">
|
|
88
|
+
<CheckIcon className="mr-1 inline h-3 w-3 text-green-500" />
|
|
89
|
+
API key is configured
|
|
90
|
+
</p>
|
|
91
|
+
)}
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
<DialogFooter>
|
|
95
|
+
{hasApiKey && (
|
|
96
|
+
<Button
|
|
97
|
+
variant="outline"
|
|
98
|
+
onClick={() => {
|
|
99
|
+
setState({ apiKey: null });
|
|
100
|
+
setInputValue("");
|
|
101
|
+
}}
|
|
102
|
+
>
|
|
103
|
+
Clear
|
|
104
|
+
</Button>
|
|
105
|
+
)}
|
|
106
|
+
<Button
|
|
107
|
+
onClick={() => {
|
|
108
|
+
if (inputValue.trim()) {
|
|
109
|
+
setState({ apiKey: inputValue.trim() });
|
|
110
|
+
setOpen(false);
|
|
111
|
+
setInputValue("");
|
|
112
|
+
}
|
|
113
|
+
}}
|
|
114
|
+
disabled={!inputValue.trim()}
|
|
115
|
+
>
|
|
116
|
+
Save
|
|
117
|
+
</Button>
|
|
118
|
+
</DialogFooter>
|
|
119
|
+
</DialogContent>
|
|
120
|
+
</Dialog>
|
|
121
|
+
);
|
|
122
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ReactNode, FC } from "react";
|
|
4
|
+
import { cn } from "@aomi-labs/react";
|
|
5
|
+
import { ModelSelect } from "./model-select";
|
|
6
|
+
import { NamespaceSelect } from "./namespace-select";
|
|
7
|
+
import { ApiKeyInput } from "./api-key-input";
|
|
8
|
+
import { WalletConnect } from "./wallet-connect";
|
|
9
|
+
|
|
10
|
+
// =============================================================================
|
|
11
|
+
// Types
|
|
12
|
+
// =============================================================================
|
|
13
|
+
|
|
14
|
+
export type ControlBarProps = {
|
|
15
|
+
className?: string;
|
|
16
|
+
/** Custom controls to render alongside built-in ones */
|
|
17
|
+
children?: ReactNode;
|
|
18
|
+
/** Hide the model selector */
|
|
19
|
+
hideModel?: boolean;
|
|
20
|
+
/** Hide the namespace/agent selector */
|
|
21
|
+
hideNamespace?: boolean;
|
|
22
|
+
/** Hide the API key input */
|
|
23
|
+
hideApiKey?: boolean;
|
|
24
|
+
/** Hide the wallet connect button (default: true) */
|
|
25
|
+
hideWallet?: boolean;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// =============================================================================
|
|
29
|
+
// Main Component
|
|
30
|
+
// =============================================================================
|
|
31
|
+
|
|
32
|
+
export const ControlBar: FC<ControlBarProps> = ({
|
|
33
|
+
className,
|
|
34
|
+
children,
|
|
35
|
+
hideModel = false,
|
|
36
|
+
hideNamespace = false,
|
|
37
|
+
hideApiKey = false,
|
|
38
|
+
hideWallet = true,
|
|
39
|
+
}) => {
|
|
40
|
+
return (
|
|
41
|
+
<div className={cn("flex items-center gap-2", className)}>
|
|
42
|
+
{!hideModel && <ModelSelect />}
|
|
43
|
+
{!hideNamespace && <NamespaceSelect />}
|
|
44
|
+
{!hideWallet && <WalletConnect />}
|
|
45
|
+
{children}
|
|
46
|
+
{!hideApiKey && <ApiKeyInput />}
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// =============================================================================
|
|
52
|
+
// Re-exports for granular usage
|
|
53
|
+
// =============================================================================
|
|
54
|
+
|
|
55
|
+
export { ModelSelect, type ModelSelectProps } from "./model-select";
|
|
56
|
+
export { NamespaceSelect, type NamespaceSelectProps } from "./namespace-select";
|
|
57
|
+
export { ApiKeyInput, type ApiKeyInputProps } from "./api-key-input";
|
|
58
|
+
export { WalletConnect, type WalletConnectProps } from "./wallet-connect";
|