@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
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState, type FC } from "react";
|
|
4
|
+
import { ChevronDownIcon, CheckIcon } from "lucide-react";
|
|
5
|
+
import { useControl, cn } from "@aomi-labs/react";
|
|
6
|
+
import { Button } from "@/components/ui/button";
|
|
7
|
+
import {
|
|
8
|
+
Popover,
|
|
9
|
+
PopoverContent,
|
|
10
|
+
PopoverTrigger,
|
|
11
|
+
} from "@/components/ui/popover";
|
|
12
|
+
|
|
13
|
+
export type ModelSelectProps = {
|
|
14
|
+
className?: string;
|
|
15
|
+
placeholder?: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const ModelSelect: FC<ModelSelectProps> = ({
|
|
19
|
+
className,
|
|
20
|
+
placeholder = "Select model",
|
|
21
|
+
}) => {
|
|
22
|
+
const {
|
|
23
|
+
state,
|
|
24
|
+
getAvailableModels,
|
|
25
|
+
getCurrentThreadControl,
|
|
26
|
+
onModelSelect,
|
|
27
|
+
isProcessing,
|
|
28
|
+
} = useControl();
|
|
29
|
+
const [open, setOpen] = useState(false);
|
|
30
|
+
|
|
31
|
+
// Fetch available models on mount
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
void getAvailableModels();
|
|
34
|
+
}, [getAvailableModels]);
|
|
35
|
+
|
|
36
|
+
// Get current thread's selected model (or fall back to default)
|
|
37
|
+
const threadControl = getCurrentThreadControl();
|
|
38
|
+
const selectedModel =
|
|
39
|
+
threadControl.model ?? state.defaultModel ?? state.availableModels[0];
|
|
40
|
+
|
|
41
|
+
const models = state.availableModels.length > 0 ? state.availableModels : [];
|
|
42
|
+
|
|
43
|
+
// Don't render if no models available
|
|
44
|
+
if (models.length === 0) {
|
|
45
|
+
return (
|
|
46
|
+
<Button
|
|
47
|
+
variant="ghost"
|
|
48
|
+
disabled
|
|
49
|
+
className={cn(
|
|
50
|
+
"h-8 w-auto min-w-[100px] rounded-full px-2 text-xs",
|
|
51
|
+
"text-muted-foreground",
|
|
52
|
+
className,
|
|
53
|
+
)}
|
|
54
|
+
>
|
|
55
|
+
<span className="truncate">Loading...</span>
|
|
56
|
+
</Button>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<Popover open={open} onOpenChange={setOpen}>
|
|
62
|
+
<PopoverTrigger asChild>
|
|
63
|
+
<Button
|
|
64
|
+
variant="ghost"
|
|
65
|
+
role="combobox"
|
|
66
|
+
aria-expanded={open}
|
|
67
|
+
disabled={isProcessing}
|
|
68
|
+
className={cn(
|
|
69
|
+
"h-8 w-auto min-w-[100px] justify-between rounded-full px-2 text-xs",
|
|
70
|
+
"text-muted-foreground hover:bg-accent hover:text-accent-foreground",
|
|
71
|
+
isProcessing && "cursor-not-allowed opacity-50",
|
|
72
|
+
className,
|
|
73
|
+
)}
|
|
74
|
+
>
|
|
75
|
+
<span className="truncate">{selectedModel || placeholder}</span>
|
|
76
|
+
<ChevronDownIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
|
77
|
+
</Button>
|
|
78
|
+
</PopoverTrigger>
|
|
79
|
+
<PopoverContent
|
|
80
|
+
align="center"
|
|
81
|
+
sideOffset={-40}
|
|
82
|
+
className="w-[220px] rounded-3xl p-1 shadow-none"
|
|
83
|
+
>
|
|
84
|
+
<div className="flex flex-col gap-0.5">
|
|
85
|
+
{models.map((model) => (
|
|
86
|
+
<button
|
|
87
|
+
key={model}
|
|
88
|
+
disabled={isProcessing}
|
|
89
|
+
onClick={() => {
|
|
90
|
+
console.log("[ModelSelect] clicked", { model, isProcessing });
|
|
91
|
+
if (isProcessing) return;
|
|
92
|
+
setOpen(false);
|
|
93
|
+
console.log("[ModelSelect] calling onModelSelect", { model });
|
|
94
|
+
void onModelSelect(model)
|
|
95
|
+
.then(() => {
|
|
96
|
+
console.log("[ModelSelect] onModelSelect completed", {
|
|
97
|
+
model,
|
|
98
|
+
});
|
|
99
|
+
})
|
|
100
|
+
.catch((err) => {
|
|
101
|
+
console.error("[ModelSelect] onModelSelect failed:", err);
|
|
102
|
+
});
|
|
103
|
+
}}
|
|
104
|
+
className={cn(
|
|
105
|
+
"flex w-full items-center justify-between gap-2 rounded-full px-3 py-2 text-sm outline-none",
|
|
106
|
+
"hover:bg-accent hover:text-accent-foreground",
|
|
107
|
+
"focus:bg-accent focus:text-accent-foreground",
|
|
108
|
+
selectedModel === model && "bg-accent",
|
|
109
|
+
isProcessing && "cursor-not-allowed opacity-50",
|
|
110
|
+
)}
|
|
111
|
+
>
|
|
112
|
+
<span>{model}</span>
|
|
113
|
+
{selectedModel === model && <CheckIcon className="h-4 w-4" />}
|
|
114
|
+
</button>
|
|
115
|
+
))}
|
|
116
|
+
</div>
|
|
117
|
+
</PopoverContent>
|
|
118
|
+
</Popover>
|
|
119
|
+
);
|
|
120
|
+
};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect, type FC } from "react";
|
|
4
|
+
import { ChevronDownIcon, CheckIcon } from "lucide-react";
|
|
5
|
+
import { useControl, cn } from "@aomi-labs/react";
|
|
6
|
+
import { Button } from "@/components/ui/button";
|
|
7
|
+
import {
|
|
8
|
+
Popover,
|
|
9
|
+
PopoverContent,
|
|
10
|
+
PopoverTrigger,
|
|
11
|
+
} from "@/components/ui/popover";
|
|
12
|
+
|
|
13
|
+
export type NamespaceSelectProps = {
|
|
14
|
+
className?: string;
|
|
15
|
+
placeholder?: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const NamespaceSelect: FC<NamespaceSelectProps> = ({
|
|
19
|
+
className,
|
|
20
|
+
placeholder = "Select agent",
|
|
21
|
+
}) => {
|
|
22
|
+
const {
|
|
23
|
+
state,
|
|
24
|
+
getAuthorizedNamespaces,
|
|
25
|
+
getCurrentThreadControl,
|
|
26
|
+
onNamespaceSelect,
|
|
27
|
+
isProcessing,
|
|
28
|
+
} = useControl();
|
|
29
|
+
const [open, setOpen] = useState(false);
|
|
30
|
+
|
|
31
|
+
// Fetch authorized namespaces on mount
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
void getAuthorizedNamespaces();
|
|
34
|
+
}, [getAuthorizedNamespaces]);
|
|
35
|
+
|
|
36
|
+
// Get current thread's selected namespace (or fall back to default)
|
|
37
|
+
const threadControl = getCurrentThreadControl();
|
|
38
|
+
const selectedNamespace =
|
|
39
|
+
threadControl.namespace ?? state.defaultNamespace ?? "default";
|
|
40
|
+
|
|
41
|
+
const namespaces = state.authorizedNamespaces;
|
|
42
|
+
|
|
43
|
+
// Show loading state if no namespaces yet
|
|
44
|
+
if (namespaces.length === 0) {
|
|
45
|
+
return (
|
|
46
|
+
<Button
|
|
47
|
+
variant="ghost"
|
|
48
|
+
disabled
|
|
49
|
+
className={cn(
|
|
50
|
+
"h-8 w-auto min-w-[100px] rounded-full px-2 text-xs",
|
|
51
|
+
"text-muted-foreground",
|
|
52
|
+
className,
|
|
53
|
+
)}
|
|
54
|
+
>
|
|
55
|
+
<span className="truncate">{selectedNamespace}</span>
|
|
56
|
+
</Button>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<Popover open={open} onOpenChange={setOpen}>
|
|
62
|
+
<PopoverTrigger asChild>
|
|
63
|
+
<Button
|
|
64
|
+
variant="ghost"
|
|
65
|
+
role="combobox"
|
|
66
|
+
aria-expanded={open}
|
|
67
|
+
disabled={isProcessing}
|
|
68
|
+
className={cn(
|
|
69
|
+
"h-8 w-auto min-w-[100px] justify-between rounded-full px-3 text-xs",
|
|
70
|
+
"text-muted-foreground hover:bg-accent hover:text-accent-foreground",
|
|
71
|
+
isProcessing && "cursor-not-allowed opacity-50",
|
|
72
|
+
className,
|
|
73
|
+
)}
|
|
74
|
+
>
|
|
75
|
+
<span className="truncate">{selectedNamespace ?? placeholder}</span>
|
|
76
|
+
<ChevronDownIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
|
77
|
+
</Button>
|
|
78
|
+
</PopoverTrigger>
|
|
79
|
+
<PopoverContent
|
|
80
|
+
align="center"
|
|
81
|
+
sideOffset={-40}
|
|
82
|
+
className="w-[180px] rounded-3xl p-1 shadow-none"
|
|
83
|
+
>
|
|
84
|
+
<div className="flex flex-col gap-0.5">
|
|
85
|
+
{namespaces.map((ns: string) => (
|
|
86
|
+
<button
|
|
87
|
+
key={ns}
|
|
88
|
+
disabled={isProcessing}
|
|
89
|
+
onClick={() => {
|
|
90
|
+
console.log("[NamespaceSelect] clicked", { ns, isProcessing });
|
|
91
|
+
if (isProcessing) return;
|
|
92
|
+
console.log("[NamespaceSelect] calling onNamespaceSelect", {
|
|
93
|
+
ns,
|
|
94
|
+
});
|
|
95
|
+
onNamespaceSelect(ns);
|
|
96
|
+
setOpen(false);
|
|
97
|
+
console.log("[NamespaceSelect] onNamespaceSelect completed", {
|
|
98
|
+
ns,
|
|
99
|
+
});
|
|
100
|
+
}}
|
|
101
|
+
className={cn(
|
|
102
|
+
"flex w-full items-center justify-between gap-2 rounded-full px-3 py-2 text-sm outline-none",
|
|
103
|
+
"hover:bg-accent hover:text-accent-foreground",
|
|
104
|
+
"focus:bg-accent focus:text-accent-foreground",
|
|
105
|
+
selectedNamespace === ns && "bg-accent",
|
|
106
|
+
isProcessing && "cursor-not-allowed opacity-50",
|
|
107
|
+
)}
|
|
108
|
+
>
|
|
109
|
+
<span>{ns}</span>
|
|
110
|
+
{selectedNamespace === ns && <CheckIcon className="h-4 w-4" />}
|
|
111
|
+
</button>
|
|
112
|
+
))}
|
|
113
|
+
</div>
|
|
114
|
+
</PopoverContent>
|
|
115
|
+
</Popover>
|
|
116
|
+
);
|
|
117
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, type FC } from "react";
|
|
4
|
+
import { useAccount, useConnect, useDisconnect } from "wagmi";
|
|
5
|
+
import { cn, formatAddress, getNetworkName, useUser } from "@aomi-labs/react";
|
|
6
|
+
|
|
7
|
+
export type WalletConnectProps = {
|
|
8
|
+
className?: string;
|
|
9
|
+
connectLabel?: string;
|
|
10
|
+
onConnectionChange?: (connected: boolean) => void;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const WalletConnect: FC<WalletConnectProps> = ({
|
|
14
|
+
className,
|
|
15
|
+
connectLabel = "Connect Wallet",
|
|
16
|
+
onConnectionChange,
|
|
17
|
+
}) => {
|
|
18
|
+
const { address, isConnected, chainId } = useAccount();
|
|
19
|
+
const { connect, connectors } = useConnect();
|
|
20
|
+
const { disconnect } = useDisconnect();
|
|
21
|
+
const { setUser } = useUser();
|
|
22
|
+
|
|
23
|
+
// Sync wallet state to UserContext
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
setUser({
|
|
26
|
+
address: address ?? undefined,
|
|
27
|
+
chainId: chainId ?? undefined,
|
|
28
|
+
isConnected,
|
|
29
|
+
});
|
|
30
|
+
onConnectionChange?.(isConnected);
|
|
31
|
+
}, [address, chainId, isConnected, setUser, onConnectionChange]);
|
|
32
|
+
|
|
33
|
+
const handleClick = () => {
|
|
34
|
+
if (isConnected) {
|
|
35
|
+
disconnect();
|
|
36
|
+
} else {
|
|
37
|
+
const connector = connectors[0];
|
|
38
|
+
if (connector) {
|
|
39
|
+
connect({ connector });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const networkName = getNetworkName(chainId);
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<button
|
|
48
|
+
type="button"
|
|
49
|
+
onClick={handleClick}
|
|
50
|
+
className={cn(
|
|
51
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm font-medium",
|
|
52
|
+
"rounded-full px-5 py-2.5",
|
|
53
|
+
"bg-neutral-900 text-white",
|
|
54
|
+
"hover:bg-neutral-800",
|
|
55
|
+
"dark:bg-neutral-900 dark:text-white",
|
|
56
|
+
"dark:hover:bg-neutral-800",
|
|
57
|
+
"transition-colors",
|
|
58
|
+
"focus-visible:ring-ring focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2",
|
|
59
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
60
|
+
className,
|
|
61
|
+
)}
|
|
62
|
+
aria-label={isConnected ? "Disconnect wallet" : "Connect wallet"}
|
|
63
|
+
>
|
|
64
|
+
<span>
|
|
65
|
+
{isConnected && address ? formatAddress(address) : connectLabel}
|
|
66
|
+
</span>
|
|
67
|
+
{isConnected && networkName && (
|
|
68
|
+
<>
|
|
69
|
+
<span className="opacity-50">•</span>
|
|
70
|
+
<span className="opacity-50">{networkName}</span>
|
|
71
|
+
</>
|
|
72
|
+
)}
|
|
73
|
+
</button>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
@@ -261,7 +261,7 @@ function Sidebar({
|
|
|
261
261
|
? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]"
|
|
262
262
|
: variant === "inset"
|
|
263
263
|
? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]"
|
|
264
|
-
: "group-data-[collapsible=icon]:w-(--sidebar-width-icon)
|
|
264
|
+
: "group-data-[collapsible=icon]:w-(--sidebar-width-icon)",
|
|
265
265
|
className,
|
|
266
266
|
)}
|
|
267
267
|
{...props}
|
|
@@ -269,7 +269,14 @@ function Sidebar({
|
|
|
269
269
|
<div
|
|
270
270
|
data-sidebar="sidebar"
|
|
271
271
|
data-slot="sidebar-inner"
|
|
272
|
-
className="
|
|
272
|
+
className="
|
|
273
|
+
bg-sidebar
|
|
274
|
+
dark:bg-sidebar
|
|
275
|
+
group-data-[variant=floating]:border-sidebar-border
|
|
276
|
+
flex h-full w-full flex-col
|
|
277
|
+
group-data-[variant=floating]:rounded-lg
|
|
278
|
+
group-data-[variant=floating]:border
|
|
279
|
+
group-data-[variant=floating]:shadow-sm"
|
|
273
280
|
>
|
|
274
281
|
{children}
|
|
275
282
|
</div>
|
|
@@ -550,7 +557,7 @@ function SidebarMenu({ className, ...props }: React.ComponentProps<"ul">) {
|
|
|
550
557
|
<ul
|
|
551
558
|
data-slot="sidebar-menu"
|
|
552
559
|
data-sidebar="menu"
|
|
553
|
-
className={cn("flex w-full min-w-0 flex-col gap-1", className)}
|
|
560
|
+
className={cn("flex w-full min-w-0 list-none flex-col gap-1", className)}
|
|
554
561
|
{...props}
|
|
555
562
|
/>
|
|
556
563
|
);
|
package/SHADCN-FETCH-GUIDE.md
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
⏺ After running npx shadcn add https://r.aomi.dev/aomi-frame.json:
|
|
2
|
-
|
|
3
|
-
File structure:
|
|
4
|
-
their-project/
|
|
5
|
-
├── components.json ← their config (already exists)
|
|
6
|
-
├── package.json ← npm deps added
|
|
7
|
-
├── components/
|
|
8
|
-
│ ├── aomi-frame.tsx ← from r.aomi.dev
|
|
9
|
-
│ ├── assistant-ui/
|
|
10
|
-
│ │ ├── thread.tsx ← from r.aomi.dev
|
|
11
|
-
│ │ ├── thread-list.tsx ← from r.aomi.dev
|
|
12
|
-
│ │ ├── threadlist-sidebar.tsx ← from r.aomi.dev
|
|
13
|
-
│ │ ├── tool-fallback.tsx ← from r.aomi.dev
|
|
14
|
-
│ │ ├── markdown-text.tsx ← from r.assistant-ui.com
|
|
15
|
-
│ │ ├── tooltip-icon-button.tsx ← from r.assistant-ui.com
|
|
16
|
-
│ │ └── attachment.tsx ← from r.assistant-ui.com
|
|
17
|
-
│ └── ui/
|
|
18
|
-
│ ├── button.tsx ← from shadcn
|
|
19
|
-
│ ├── sidebar.tsx ← from shadcn
|
|
20
|
-
│ ├── separator.tsx ← from shadcn
|
|
21
|
-
│ ├── breadcrumb.tsx ← from shadcn
|
|
22
|
-
│ ├── skeleton.tsx ← from shadcn
|
|
23
|
-
│ ├── tooltip.tsx ← from shadcn
|
|
24
|
-
│ ├── dialog.tsx ← from shadcn
|
|
25
|
-
│ ├── avatar.tsx ← from shadcn
|
|
26
|
-
│ └── ...
|
|
27
|
-
└── lib/
|
|
28
|
-
└── utils.ts ← cn() helper (from shadcn init)
|
|
29
|
-
|
|
30
|
-
Their components.json (pre-existing, configured during npx shadcn init):
|
|
31
|
-
{
|
|
32
|
-
"$schema": "https://ui.shadcn.com/schema.json",
|
|
33
|
-
"style": "new-york",
|
|
34
|
-
"tailwind": {
|
|
35
|
-
"baseColor": "neutral",
|
|
36
|
-
"cssVariables": true
|
|
37
|
-
},
|
|
38
|
-
"aliases": {
|
|
39
|
-
"components": "@/components",
|
|
40
|
-
"utils": "@/lib/utils"
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
Their package.json gets these added:
|
|
45
|
-
{
|
|
46
|
-
"dependencies": {
|
|
47
|
-
"@aomi-labs/react": "^x.x.x",
|
|
48
|
-
"@assistant-ui/react": "^0.11.x",
|
|
49
|
-
"@assistant-ui/react-markdown": "^0.11.x",
|
|
50
|
-
"lucide-react": "^0.x.x",
|
|
51
|
-
"remark-gfm": "^4.x.x",
|
|
52
|
-
"@radix-ui/react-tooltip": "...",
|
|
53
|
-
"@radix-ui/react-dialog": "...",
|
|
54
|
-
...
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
Usage in their code:
|
|
59
|
-
import { AomiFrame } from "@/components/aomi-frame";
|
|
60
|
-
|
|
61
|
-
export default function Page() {
|
|
62
|
-
return <AomiFrame />;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
> when apps/registry/src/components/assistant-ui/thread-list.tsx says this:
|
|
66
|
-
> import {
|
|
67
|
-
> ThreadListItemPrimitive,
|
|
68
|
-
> ThreadListPrimitive,
|
|
69
|
-
> useAssistantState,
|
|
70
|
-
> } from "@assistant-ui/react";
|
|
71
|
-
> now my repo can resolve these dependencies thanks to correct pnpm configuration, but can the user
|
|
72
|
-
> application resolve it?
|
|
73
|
-
|
|
74
|
-
⏺ Yes, because we list it in dependencies in the registry.
|
|
75
|
-
|
|
76
|
-
Looking at your registry.ts:
|
|
77
|
-
|
|
78
|
-
{
|
|
79
|
-
name: "assistant-thread-list",
|
|
80
|
-
file: "components/assistant-ui/thread-list.tsx",
|
|
81
|
-
dependencies: ["@assistant-ui/react", "lucide-react"], // ← This!
|
|
82
|
-
registryDependencies: [...],
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
What happens when user runs shadcn add:
|
|
86
|
-
|
|
87
|
-
1. shadcn reads dependencies: ["@assistant-ui/react", "lucide-react"]
|
|
88
|
-
2. Runs npm install @assistant-ui/react lucide-react
|
|
89
|
-
3. Package is now in user's node_modules/
|
|
90
|
-
4. Import resolves ✓
|
|
91
|
-
|
|
92
|
-
User's project:
|
|
93
|
-
├── node_modules/
|
|
94
|
-
│ └── @assistant-ui/react/ ← installed by shadcn
|
|
95
|
-
├── components/
|
|
96
|
-
│ └── assistant-ui/
|
|
97
|
-
│ └── thread-list.tsx ← import { ... } from "@assistant-ui/react" ✓
|
|
98
|
-
|
|
99
|
-
| Import type | Resolved via |
|
|
100
|
-
| ---------------------- | --------------------------------- |
|
|
101
|
-
| @assistant-ui/react | npm (installed from dependencies) |
|
|
102
|
-
| @/components/ui/button | Their components.json aliases |
|
|
103
|
-
| @aomi-labs/react | npm (installed from dependencies) |
|
|
104
|
-
|
|
105
|
-
So yes — as long as it's in the dependencies array, shadcn will install it for them.
|
package/components.json
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
-
"name": "assistant-threadlist-collapsible",
|
|
4
|
-
"type": "registry:component",
|
|
5
|
-
"description": "Collapsible shell for thread navigation and wallet footer slot.",
|
|
6
|
-
"files": [
|
|
7
|
-
{
|
|
8
|
-
"type": "registry:component",
|
|
9
|
-
"path": "components/assistant-ui/threadlist-collapsible.tsx",
|
|
10
|
-
"content": "\"use client\";\n\nimport * as React from \"react\";\nimport Link from \"next/link\";\nimport Image from \"next/image\";\nimport { MenuIcon } from \"lucide-react\";\nimport {\n Collapsible,\n CollapsibleContent,\n CollapsibleTrigger,\n} from \"@/components/ui/collapsible\";\nimport { Button } from \"@/components/ui/button\";\nimport { ThreadList } from \"@/components/assistant-ui/thread-list\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { cn } from \"@aomi-labs/react\";\n\ntype ThreadListCollapsibleProps = {\n /** Optional footer component (e.g., WalletFooter from consumer app) */\n footer?: React.ReactNode;\n /** Default open state */\n defaultOpen?: boolean;\n /** Controlled open state */\n open?: boolean;\n /** Callback when open state changes */\n onOpenChange?: (open: boolean) => void;\n /** Additional className */\n className?: string;\n};\n\nexport function ThreadListCollapsible({\n footer,\n defaultOpen = false,\n open,\n onOpenChange,\n className,\n}: ThreadListCollapsibleProps) {\n const [isOpen, setIsOpen] = React.useState(defaultOpen);\n\n const handleOpenChange = React.useCallback(\n (newOpen: boolean) => {\n if (onOpenChange) {\n onOpenChange(newOpen);\n } else {\n setIsOpen(newOpen);\n }\n },\n [onOpenChange]\n );\n\n const isControlled = open !== undefined;\n const collapsibleOpen = isControlled ? open : isOpen;\n\n return (\n <div\n className={cn(\n \"flex flex-col border-r bg-background transition-all duration-200\",\n collapsibleOpen ? \"w-64\" : \"w-16\",\n className\n )}\n >\n <Collapsible\n open={collapsibleOpen}\n onOpenChange={handleOpenChange}\n className=\"flex flex-1 flex-col overflow-hidden\"\n >\n {/* Header */}\n <div className=\"aomi-collapsible-header flex h-14 shrink-0 items-center gap-2 border-b px-3\">\n <div className=\"flex items-center justify-between gap-2 w-full\">\n <CollapsibleTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"shrink-0\"\n aria-label={collapsibleOpen ? \"Collapse sidebar\" : \"Expand sidebar\"}\n >\n <MenuIcon className=\"size-5\" />\n </Button>\n </CollapsibleTrigger>\n\n {collapsibleOpen && (\n <Link\n href=\"https://aomi.dev\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"flex items-center gap-2 rounded-md px-2 py-1.5 hover:bg-accent transition-colors\"\n >\n <div className=\"aomi-collapsible-header-icon-wrapper flex aspect-square size-8 items-center justify-center rounded-lg bg-white\">\n <Image\n src=\"/assets/images/a.svg\"\n alt=\"Aomi Logo\"\n width={28}\n height={28}\n className=\"aomi-collapsible-header-icon size-7 ml-3\"\n priority\n />\n </div>\n </Link>\n )}\n </div>\n </div>\n\n {/* Collapsible Content */}\n <CollapsibleContent className=\"flex flex-1 flex-col overflow-hidden data-[state=closed]:hidden\">\n <div className=\"aomi-collapsible-content flex flex-1 flex-col overflow-hidden p-2\">\n <ThreadList />\n </div>\n {footer && (\n <>\n <Separator />\n <div className=\"aomi-collapsible-footer border-t border-sm py-4 px-2\">\n {footer}\n </div>\n </>\n )}\n </CollapsibleContent>\n </Collapsible>\n </div>\n );\n}\n"
|
|
11
|
-
}
|
|
12
|
-
],
|
|
13
|
-
"dependencies": [
|
|
14
|
-
"lucide-react",
|
|
15
|
-
"next"
|
|
16
|
-
],
|
|
17
|
-
"registryDependencies": [
|
|
18
|
-
"https://widget.aomi.dev/r/assistant-thread-list.json",
|
|
19
|
-
"collapsible",
|
|
20
|
-
"button",
|
|
21
|
-
"separator"
|
|
22
|
-
]
|
|
23
|
-
}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
|
|
5
|
-
import { registry } from "../src/registry.js";
|
|
6
|
-
|
|
7
|
-
const REGISTRY_NAME = "aomi";
|
|
8
|
-
const REGISTRY_HOMEPAGE = "https://r.aomi.dev";
|
|
9
|
-
|
|
10
|
-
const baseDir = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
-
const distDir = path.resolve(baseDir, "../dist");
|
|
12
|
-
const srcDir = path.resolve(baseDir, "../src");
|
|
13
|
-
|
|
14
|
-
function buildComponent(entry) {
|
|
15
|
-
const filePath = path.join(srcDir, entry.file);
|
|
16
|
-
const content = readFileSync(filePath, "utf8");
|
|
17
|
-
|
|
18
|
-
const payload = {
|
|
19
|
-
$schema: "https://ui.shadcn.com/schema/registry-item.json",
|
|
20
|
-
name: entry.name,
|
|
21
|
-
type: "registry:component",
|
|
22
|
-
description: entry.description,
|
|
23
|
-
files: [
|
|
24
|
-
{
|
|
25
|
-
type: "registry:component",
|
|
26
|
-
path: entry.file,
|
|
27
|
-
content,
|
|
28
|
-
},
|
|
29
|
-
],
|
|
30
|
-
dependencies: entry.dependencies ?? [],
|
|
31
|
-
registryDependencies: entry.registryDependencies ?? [],
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
const outPath = path.join(distDir, `${entry.name}.json`);
|
|
35
|
-
writeFileSync(outPath, JSON.stringify(payload, null, 2));
|
|
36
|
-
|
|
37
|
-
// Return item for registry.json (without content)
|
|
38
|
-
return {
|
|
39
|
-
name: entry.name,
|
|
40
|
-
type: "registry:component",
|
|
41
|
-
description: entry.description,
|
|
42
|
-
files: [
|
|
43
|
-
{
|
|
44
|
-
type: "registry:component",
|
|
45
|
-
path: entry.file,
|
|
46
|
-
},
|
|
47
|
-
],
|
|
48
|
-
dependencies: entry.dependencies ?? [],
|
|
49
|
-
registryDependencies: entry.registryDependencies ?? [],
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function main() {
|
|
54
|
-
mkdirSync(distDir, { recursive: true });
|
|
55
|
-
|
|
56
|
-
const items = registry.map(buildComponent);
|
|
57
|
-
|
|
58
|
-
// Generate registry.json index
|
|
59
|
-
const registryIndex = {
|
|
60
|
-
$schema: "https://ui.shadcn.com/schema/registry.json",
|
|
61
|
-
name: REGISTRY_NAME,
|
|
62
|
-
homepage: REGISTRY_HOMEPAGE,
|
|
63
|
-
items,
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
writeFileSync(
|
|
67
|
-
path.join(distDir, "registry.json"),
|
|
68
|
-
JSON.stringify(registryIndex, null, 2),
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
console.log(`Wrote ${items.length} component files + registry.json to dist/`);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
main();
|
package/tsconfig.json
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2017",
|
|
4
|
-
"lib": ["dom", "dom.iterable", "esnext"],
|
|
5
|
-
"skipLibCheck": true,
|
|
6
|
-
"strict": true,
|
|
7
|
-
"module": "esnext",
|
|
8
|
-
"moduleResolution": "bundler",
|
|
9
|
-
"resolveJsonModule": true,
|
|
10
|
-
"isolatedModules": true,
|
|
11
|
-
"jsx": "react-jsx",
|
|
12
|
-
"baseUrl": ".",
|
|
13
|
-
"paths": {
|
|
14
|
-
"@/*": ["./src/*"]
|
|
15
|
-
}
|
|
16
|
-
},
|
|
17
|
-
"include": ["src/**/*.ts", "src/**/*.tsx", "scripts/**/*.js"],
|
|
18
|
-
"exclude": ["node_modules", "dist"]
|
|
19
|
-
}
|