@coze-arch/cli 0.0.13 → 0.0.14-alpha.c52ee4
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/lib/__templates__/expo/AGENTS.md +15 -7
- package/lib/__templates__/expo/README.md +15 -7
- package/lib/__templates__/expo/client/eslint.config.mjs +3 -0
- package/lib/__templates__/expo/eslint-plugins/expo/index.js +9 -0
- package/lib/__templates__/expo/eslint-plugins/expo/rule.js +105 -0
- package/lib/__templates__/expo/eslint-plugins/expo/tech.md +108 -0
- package/lib/__templates__/nextjs/AGENTS.md +9 -0
- package/lib/__templates__/nextjs/eslint.config.mjs +15 -0
- package/lib/__templates__/pi-agent/.coze +10 -0
- package/lib/__templates__/pi-agent/AGENTS.md +150 -0
- package/lib/__templates__/pi-agent/README.md +155 -0
- package/lib/__templates__/pi-agent/_gitignore +3 -0
- package/lib/__templates__/pi-agent/docs/project-overview.md +273 -0
- package/lib/__templates__/pi-agent/docs/user/getting-started.md +46 -0
- package/lib/__templates__/pi-agent/package.json +52 -0
- package/lib/__templates__/pi-agent/pnpm-lock.yaml +7840 -0
- package/lib/__templates__/pi-agent/scripts/dev.sh +14 -0
- package/lib/__templates__/pi-agent/scripts/prepare.sh +2 -0
- package/lib/__templates__/pi-agent/src/agent.ts +367 -0
- package/lib/__templates__/pi-agent/src/channels/feishu/index.ts +760 -0
- package/lib/__templates__/pi-agent/src/channels/feishu/streaming-card.ts +297 -0
- package/lib/__templates__/pi-agent/src/channels/wechat/index.ts +171 -0
- package/lib/__templates__/pi-agent/src/config.ts +596 -0
- package/lib/__templates__/pi-agent/src/core.ts +218 -0
- package/lib/__templates__/pi-agent/src/dashboard/api/channels.ts +148 -0
- package/lib/__templates__/pi-agent/src/dashboard/api/docs.ts +204 -0
- package/lib/__templates__/pi-agent/src/dashboard/api/models.ts +141 -0
- package/lib/__templates__/pi-agent/src/dashboard/api/overview.ts +33 -0
- package/lib/__templates__/pi-agent/src/dashboard/config-store.ts +64 -0
- package/lib/__templates__/pi-agent/src/dashboard/index.ts +39 -0
- package/lib/__templates__/pi-agent/src/dashboard/server.ts +622 -0
- package/lib/__templates__/pi-agent/src/dashboard/types.ts +25 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/index.html +13 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/postcss.config.cjs +7 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/components/app-layout.tsx +186 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/components/page-title.tsx +17 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/alert.tsx +22 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/badge.tsx +25 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/button.tsx +40 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/card.tsx +29 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/input.tsx +18 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/label.tsx +8 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/select.tsx +80 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/separator.tsx +23 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/hooks/use-fetch.ts +32 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/hooks/use-local-storage-state.ts +23 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/main.tsx +30 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/channels-page.tsx +188 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/chat-page.tsx +451 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/docs-page.tsx +65 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/models-page.tsx +122 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/overview-page.tsx +134 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/services/chat-ws-service.ts +167 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/styles.css +294 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/utils/index.ts +11 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/tsconfig.json +13 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/vite.config.ts +17 -0
- package/lib/__templates__/pi-agent/src/index.ts +123 -0
- package/lib/__templates__/pi-agent/src/session-store.ts +223 -0
- package/lib/__templates__/pi-agent/template.config.js +45 -0
- package/lib/__templates__/pi-agent/tests/config.test.ts +292 -0
- package/lib/__templates__/pi-agent/tests/dashboard-docs-api.test.ts +125 -0
- package/lib/__templates__/pi-agent/tests/dashboard-models-api.test.ts +171 -0
- package/lib/__templates__/pi-agent/tests/feishu-channel.test.ts +149 -0
- package/lib/__templates__/pi-agent/tests/feishu-streaming-card.test.ts +15 -0
- package/lib/__templates__/pi-agent/tests/session-store.test.ts +61 -0
- package/lib/__templates__/pi-agent/tests/smoke/run-smoke.ts +275 -0
- package/lib/__templates__/pi-agent/tsconfig.json +20 -0
- package/lib/__templates__/pi-agent/types/larksuiteoapi-node-sdk.d.ts +113 -0
- package/lib/__templates__/taro/pnpm-lock.yaml +24 -14
- package/lib/__templates__/taro/server/package.json +0 -2
- package/lib/__templates__/taro/src/presets/dev-debug.ts +2 -2
- package/lib/__templates__/templates.json +24 -0
- package/lib/__templates__/vite/AGENTS.md +5 -0
- package/lib/cli.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import {
|
|
3
|
+
BookOpenText as DocsIcon,
|
|
4
|
+
Cpu as CpuIcon,
|
|
5
|
+
LayoutDashboard as OverviewIcon,
|
|
6
|
+
Menu as MenuIcon,
|
|
7
|
+
MessageSquare as MessageIcon,
|
|
8
|
+
Moon as MoonIcon,
|
|
9
|
+
Settings as SettingsIcon,
|
|
10
|
+
Sun as SunIcon,
|
|
11
|
+
} from "lucide-react";
|
|
12
|
+
import { NavLink, useLocation } from "react-router-dom";
|
|
13
|
+
import { Toaster } from "sonner";
|
|
14
|
+
import { Button, buttonVariants } from "./ui/button";
|
|
15
|
+
import { cn } from "../utils";
|
|
16
|
+
import { useLocalStorageState } from "../hooks/use-local-storage-state";
|
|
17
|
+
|
|
18
|
+
const navItems = [
|
|
19
|
+
{ to: "/chat", label: "聊天", icon: MessageIcon },
|
|
20
|
+
{ to: "/overview", label: "概览", icon: OverviewIcon },
|
|
21
|
+
{ to: "/docs", label: "文档", icon: DocsIcon },
|
|
22
|
+
{ to: "/models", label: "模型", icon: CpuIcon },
|
|
23
|
+
{ to: "/channels", label: "渠道", icon: SettingsIcon },
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
export function AppLayout(props: { children: React.ReactNode }) {
|
|
27
|
+
const location = useLocation();
|
|
28
|
+
const [theme, setTheme] = useLocalStorageState<"light" | "dark">("pi-bot.dashboard.theme", "light");
|
|
29
|
+
const [mobileNavOpen, setMobileNavOpen] = React.useState(false);
|
|
30
|
+
const shellRef = React.useRef<HTMLDivElement | null>(null);
|
|
31
|
+
const headerRef = React.useRef<HTMLElement | null>(null);
|
|
32
|
+
const [mobileMenuPos, setMobileMenuPos] = React.useState<{ top: number; left: number; width: number } | null>(null);
|
|
33
|
+
const headerTitle = "仪表盘";
|
|
34
|
+
|
|
35
|
+
React.useEffect(() => {
|
|
36
|
+
document.documentElement.classList.toggle("dark", theme === "dark");
|
|
37
|
+
}, [theme]);
|
|
38
|
+
|
|
39
|
+
React.useEffect(() => {
|
|
40
|
+
setMobileNavOpen(false);
|
|
41
|
+
}, [location.pathname]);
|
|
42
|
+
|
|
43
|
+
const computeMobileMenuPos = React.useCallback(() => {
|
|
44
|
+
if (!shellRef.current || !headerRef.current) return;
|
|
45
|
+
const shellRect = shellRef.current.getBoundingClientRect();
|
|
46
|
+
const headerRect = headerRef.current.getBoundingClientRect();
|
|
47
|
+
const pad = 12;
|
|
48
|
+
const left = Math.round(shellRect.left + pad);
|
|
49
|
+
const width = Math.round(shellRect.width - pad * 2);
|
|
50
|
+
const top = Math.round(headerRect.bottom + 8);
|
|
51
|
+
setMobileMenuPos({ top, left, width });
|
|
52
|
+
}, []);
|
|
53
|
+
|
|
54
|
+
React.useLayoutEffect(() => {
|
|
55
|
+
if (!mobileNavOpen) return;
|
|
56
|
+
computeMobileMenuPos();
|
|
57
|
+
const onUpdate = () => computeMobileMenuPos();
|
|
58
|
+
window.addEventListener("resize", onUpdate);
|
|
59
|
+
// capture=true: catch scroll events from nested scroll containers as well
|
|
60
|
+
window.addEventListener("scroll", onUpdate, true);
|
|
61
|
+
return () => {
|
|
62
|
+
window.removeEventListener("resize", onUpdate);
|
|
63
|
+
window.removeEventListener("scroll", onUpdate, true);
|
|
64
|
+
};
|
|
65
|
+
}, [mobileNavOpen, computeMobileMenuPos]);
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<div className="mx-auto flex h-full w-full max-w-7xl flex-col px-4 py-4 md:px-6 md:py-6 lg:px-8">
|
|
69
|
+
<div
|
|
70
|
+
ref={shellRef}
|
|
71
|
+
className="flex min-h-0 flex-1 flex-col overflow-hidden rounded-lg border border-border/80 bg-background"
|
|
72
|
+
>
|
|
73
|
+
<Toaster richColors position="top-right" />
|
|
74
|
+
<header
|
|
75
|
+
ref={headerRef}
|
|
76
|
+
className="z-40 flex-shrink-0 border-b border-border/80 bg-background/95 px-4 py-3 backdrop-blur supports-[backdrop-filter]:bg-background/85 sm:px-5"
|
|
77
|
+
>
|
|
78
|
+
<div className="flex items-center justify-between gap-3">
|
|
79
|
+
<div className="flex items-center gap-3">
|
|
80
|
+
<Button
|
|
81
|
+
variant="outline"
|
|
82
|
+
size="sm"
|
|
83
|
+
className="h-9 w-9 p-0 md:hidden"
|
|
84
|
+
onClick={() => {
|
|
85
|
+
setMobileNavOpen((open) => !open);
|
|
86
|
+
// Best-effort: compute position immediately for snappy open.
|
|
87
|
+
queueMicrotask(() => computeMobileMenuPos());
|
|
88
|
+
}}
|
|
89
|
+
aria-label={mobileNavOpen ? "关闭导航" : "打开导航"}
|
|
90
|
+
aria-expanded={mobileNavOpen}
|
|
91
|
+
>
|
|
92
|
+
<MenuIcon className="h-4 w-4" />
|
|
93
|
+
</Button>
|
|
94
|
+
<div>
|
|
95
|
+
<div className="text-sm font-semibold tracking-tight">{headerTitle}</div>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
<Button
|
|
99
|
+
variant="outline"
|
|
100
|
+
size="sm"
|
|
101
|
+
className="h-9 w-9 rounded-xl p-0"
|
|
102
|
+
onClick={() => setTheme((prev) => (prev === "dark" ? "light" : "dark"))}
|
|
103
|
+
aria-label="切换暗黑模式"
|
|
104
|
+
title={theme === "dark" ? "切换到浅色模式" : "切换到暗黑模式"}
|
|
105
|
+
>
|
|
106
|
+
{theme === "dark" ? <SunIcon className="h-4 w-4" /> : <MoonIcon className="h-4 w-4" />}
|
|
107
|
+
</Button>
|
|
108
|
+
</div>
|
|
109
|
+
</header>
|
|
110
|
+
{mobileNavOpen ? (
|
|
111
|
+
<div className="fixed inset-0 z-30 md:hidden">
|
|
112
|
+
<button
|
|
113
|
+
className="absolute inset-0 bg-background/40 backdrop-blur-sm"
|
|
114
|
+
onClick={() => setMobileNavOpen(false)}
|
|
115
|
+
aria-label="关闭导航"
|
|
116
|
+
/>
|
|
117
|
+
<div
|
|
118
|
+
className="absolute rounded-2xl border border-border bg-card p-2"
|
|
119
|
+
style={{
|
|
120
|
+
top: mobileMenuPos?.top ?? 80,
|
|
121
|
+
left: mobileMenuPos?.left ?? 16,
|
|
122
|
+
width: mobileMenuPos?.width ?? undefined,
|
|
123
|
+
}}
|
|
124
|
+
>
|
|
125
|
+
<nav className="grid gap-2">
|
|
126
|
+
{navItems.map((item) => (
|
|
127
|
+
<NavItem key={item.to} item={item} compact={false} onClick={() => setMobileNavOpen(false)} />
|
|
128
|
+
))}
|
|
129
|
+
</nav>
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
) : null}
|
|
133
|
+
|
|
134
|
+
<div className="flex min-h-0 flex-1 items-stretch">
|
|
135
|
+
<aside className="hidden shrink-0 p-2 md:block xl:hidden">
|
|
136
|
+
<nav className="grid gap-1">
|
|
137
|
+
{navItems.map((item) => (
|
|
138
|
+
<NavItem key={item.to} item={item} compact={true} />
|
|
139
|
+
))}
|
|
140
|
+
</nav>
|
|
141
|
+
</aside>
|
|
142
|
+
|
|
143
|
+
<aside className="hidden shrink-0 p-3 xl:block">
|
|
144
|
+
<nav className="grid gap-1">
|
|
145
|
+
{navItems.map((item) => (
|
|
146
|
+
<NavItem key={item.to} item={item} compact={false} />
|
|
147
|
+
))}
|
|
148
|
+
</nav>
|
|
149
|
+
</aside>
|
|
150
|
+
|
|
151
|
+
<main
|
|
152
|
+
className={cn(
|
|
153
|
+
"min-w-0 flex-1 md:border-l md:border-border",
|
|
154
|
+
mobileNavOpen ? "overflow-hidden" : "overflow-y-auto",
|
|
155
|
+
)}
|
|
156
|
+
>
|
|
157
|
+
{props.children}
|
|
158
|
+
</main>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function NavItem(props: { item: (typeof navItems)[number]; compact?: boolean; onClick?: () => void }) {
|
|
166
|
+
const Icon = props.item.icon;
|
|
167
|
+
|
|
168
|
+
return (
|
|
169
|
+
<NavLink
|
|
170
|
+
to={props.item.to}
|
|
171
|
+
onClick={props.onClick}
|
|
172
|
+
className={({ isActive }) =>
|
|
173
|
+
cn(
|
|
174
|
+
buttonVariants({ variant: "ghost" }),
|
|
175
|
+
"h-10 justify-start rounded-xl border border-transparent px-3 text-sm",
|
|
176
|
+
props.compact && "h-9 w-9 justify-center px-0",
|
|
177
|
+
isActive && "border-border/80 bg-secondary text-secondary-foreground",
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
title={props.item.label}
|
|
181
|
+
>
|
|
182
|
+
<Icon className="h-4 w-4 shrink-0" />
|
|
183
|
+
{props.compact ? <span className="sr-only">{props.item.label}</span> : <span>{props.item.label}</span>}
|
|
184
|
+
</NavLink>
|
|
185
|
+
);
|
|
186
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
export function PageTitle(props: { eyebrow?: string; title: string; right?: React.ReactNode }) {
|
|
4
|
+
return (
|
|
5
|
+
<header className="mb-4">
|
|
6
|
+
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
|
7
|
+
<div>
|
|
8
|
+
{props.eyebrow ? (
|
|
9
|
+
<div className="text-xs text-muted-foreground">{props.eyebrow}</div>
|
|
10
|
+
) : null}
|
|
11
|
+
<h1 className="text-xl font-semibold tracking-tight sm:text-2xl">{props.title}</h1>
|
|
12
|
+
</div>
|
|
13
|
+
{props.right ? <div className="shrink-0">{props.right}</div> : null}
|
|
14
|
+
</div>
|
|
15
|
+
</header>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "../../utils";
|
|
3
|
+
|
|
4
|
+
function Alert({ className, ...props }: React.ComponentProps<"div">) {
|
|
5
|
+
return (
|
|
6
|
+
<div
|
|
7
|
+
role="alert"
|
|
8
|
+
className={cn("relative w-full rounded-xl border border-border/70 bg-muted/50 px-4 py-3 text-sm text-foreground", className)}
|
|
9
|
+
{...props}
|
|
10
|
+
/>
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function AlertTitle({ className, ...props }: React.ComponentProps<"h5">) {
|
|
15
|
+
return <h5 className={cn("mb-1 font-medium leading-none tracking-tight", className)} {...props} />;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function AlertDescription({ className, ...props }: React.ComponentProps<"div">) {
|
|
19
|
+
return <div className={cn("text-sm text-muted-foreground", className)} {...props} />;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export { Alert, AlertTitle, AlertDescription };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
+
import { cn } from "../../utils";
|
|
4
|
+
|
|
5
|
+
const badgeVariants = cva(
|
|
6
|
+
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-medium transition-colors",
|
|
7
|
+
{
|
|
8
|
+
variants: {
|
|
9
|
+
variant: {
|
|
10
|
+
default: "border-transparent bg-primary text-primary-foreground",
|
|
11
|
+
secondary: "border-transparent bg-secondary text-secondary-foreground",
|
|
12
|
+
outline: "border-border/80 bg-background/70 text-foreground",
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
defaultVariants: {
|
|
16
|
+
variant: "default",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
function Badge({ className, variant, ...props }: React.ComponentProps<"div"> & VariantProps<typeof badgeVariants>) {
|
|
22
|
+
return <div className={cn(badgeVariants({ variant }), className)} {...props} />;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export { Badge, badgeVariants };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
|
+
import { cn } from "../../utils";
|
|
5
|
+
|
|
6
|
+
const buttonVariants = cva(
|
|
7
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 outline-none focus-visible:ring-2 focus-visible:ring-ring/50",
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
12
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/85",
|
|
13
|
+
outline: "border border-border bg-background/70 hover:bg-accent hover:text-accent-foreground",
|
|
14
|
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
15
|
+
},
|
|
16
|
+
size: {
|
|
17
|
+
default: "h-10 px-4 py-2",
|
|
18
|
+
sm: "h-9 rounded-md px-3",
|
|
19
|
+
lg: "h-11 rounded-md px-8",
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
defaultVariants: {
|
|
23
|
+
variant: "default",
|
|
24
|
+
size: "default",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
type ButtonProps = React.ComponentProps<"button"> &
|
|
30
|
+
VariantProps<typeof buttonVariants> & {
|
|
31
|
+
asChild?: boolean;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function Button({ className, variant, size, asChild = false, ...props }: ButtonProps) {
|
|
35
|
+
const Comp = asChild ? Slot : "button";
|
|
36
|
+
|
|
37
|
+
return <Comp className={cn(buttonVariants({ variant, size, className }))} {...props} />;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export { Button, buttonVariants };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "../../utils";
|
|
3
|
+
|
|
4
|
+
function Card({ className, ...props }: React.ComponentProps<"div">) {
|
|
5
|
+
return (
|
|
6
|
+
<div
|
|
7
|
+
className={cn("rounded-2xl border border-border/70 bg-card transition-colors", className)}
|
|
8
|
+
{...props}
|
|
9
|
+
/>
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
14
|
+
return <div className={cn("flex flex-col space-y-1.5 p-5 sm:p-6", className)} {...props} />;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
18
|
+
return <div className={cn("text-lg font-semibold tracking-tight", className)} {...props} />;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
|
|
22
|
+
return <div className={cn("text-sm text-muted-foreground", className)} {...props} />;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
26
|
+
return <div className={cn("p-5 pt-0 sm:p-6 sm:pt-0", className)} {...props} />;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export { Card, CardHeader, CardTitle, CardDescription, CardContent };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "../../utils";
|
|
3
|
+
|
|
4
|
+
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
|
5
|
+
return (
|
|
6
|
+
<input
|
|
7
|
+
type={type}
|
|
8
|
+
data-slot="input"
|
|
9
|
+
className={cn(
|
|
10
|
+
"flex h-10 w-full rounded-md border border-input bg-background/80 px-3 py-2 text-sm transition-colors outline-none file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:ring-2 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50",
|
|
11
|
+
className,
|
|
12
|
+
)}
|
|
13
|
+
{...props}
|
|
14
|
+
/>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export { Input };
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import * as SelectPrimitive from "@radix-ui/react-select";
|
|
3
|
+
import { Check, ChevronDown } from "lucide-react";
|
|
4
|
+
import { cn } from "../../utils";
|
|
5
|
+
|
|
6
|
+
const Select = SelectPrimitive.Root;
|
|
7
|
+
const SelectValue = SelectPrimitive.Value;
|
|
8
|
+
|
|
9
|
+
function SelectTrigger({
|
|
10
|
+
className,
|
|
11
|
+
children,
|
|
12
|
+
...props
|
|
13
|
+
}: React.ComponentProps<typeof SelectPrimitive.Trigger>) {
|
|
14
|
+
return (
|
|
15
|
+
<SelectPrimitive.Trigger
|
|
16
|
+
className={cn(
|
|
17
|
+
"flex h-10 w-full items-center justify-between gap-2 rounded-md border border-input bg-background/80 px-3 py-2 font-mono text-xs outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 data-[placeholder]:text-muted-foreground sm:text-sm",
|
|
18
|
+
className,
|
|
19
|
+
)}
|
|
20
|
+
{...props}
|
|
21
|
+
>
|
|
22
|
+
{children}
|
|
23
|
+
<SelectPrimitive.Icon asChild>
|
|
24
|
+
<ChevronDown className="h-4 w-4 shrink-0 opacity-50" />
|
|
25
|
+
</SelectPrimitive.Icon>
|
|
26
|
+
</SelectPrimitive.Trigger>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function SelectContent({
|
|
31
|
+
className,
|
|
32
|
+
children,
|
|
33
|
+
position = "popper",
|
|
34
|
+
...props
|
|
35
|
+
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
|
|
36
|
+
return (
|
|
37
|
+
<SelectPrimitive.Portal>
|
|
38
|
+
<SelectPrimitive.Content
|
|
39
|
+
className={cn(
|
|
40
|
+
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border border-border bg-popover text-popover-foreground shadow-md",
|
|
41
|
+
position === "popper" && "translate-y-1",
|
|
42
|
+
className,
|
|
43
|
+
)}
|
|
44
|
+
position={position}
|
|
45
|
+
{...props}
|
|
46
|
+
>
|
|
47
|
+
<SelectPrimitive.Viewport
|
|
48
|
+
className={cn("p-1", position === "popper" && "min-w-[var(--radix-select-trigger-width)]")}
|
|
49
|
+
>
|
|
50
|
+
{children}
|
|
51
|
+
</SelectPrimitive.Viewport>
|
|
52
|
+
</SelectPrimitive.Content>
|
|
53
|
+
</SelectPrimitive.Portal>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function SelectItem({
|
|
58
|
+
className,
|
|
59
|
+
children,
|
|
60
|
+
...props
|
|
61
|
+
}: React.ComponentProps<typeof SelectPrimitive.Item>) {
|
|
62
|
+
return (
|
|
63
|
+
<SelectPrimitive.Item
|
|
64
|
+
className={cn(
|
|
65
|
+
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
66
|
+
className,
|
|
67
|
+
)}
|
|
68
|
+
{...props}
|
|
69
|
+
>
|
|
70
|
+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
71
|
+
<SelectPrimitive.ItemIndicator>
|
|
72
|
+
<Check className="h-4 w-4" />
|
|
73
|
+
</SelectPrimitive.ItemIndicator>
|
|
74
|
+
</span>
|
|
75
|
+
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
|
76
|
+
</SelectPrimitive.Item>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export { Select, SelectContent, SelectItem, SelectTrigger, SelectValue };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "../../utils";
|
|
3
|
+
|
|
4
|
+
function Separator({
|
|
5
|
+
className,
|
|
6
|
+
orientation = "horizontal",
|
|
7
|
+
...props
|
|
8
|
+
}: React.ComponentProps<"div"> & { orientation?: "horizontal" | "vertical" }) {
|
|
9
|
+
return (
|
|
10
|
+
<div
|
|
11
|
+
role="separator"
|
|
12
|
+
aria-orientation={orientation}
|
|
13
|
+
className={cn(
|
|
14
|
+
"shrink-0 bg-border",
|
|
15
|
+
orientation === "horizontal" ? "h-px w-full" : "h-full w-px",
|
|
16
|
+
className,
|
|
17
|
+
)}
|
|
18
|
+
{...props}
|
|
19
|
+
/>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export { Separator };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
export function useFetch<T>(url: string) {
|
|
4
|
+
const [data, setData] = React.useState<T | null>(null);
|
|
5
|
+
const [error, setError] = React.useState<string | null>(null);
|
|
6
|
+
const [loading, setLoading] = React.useState(true);
|
|
7
|
+
|
|
8
|
+
React.useEffect(() => {
|
|
9
|
+
let canceled = false;
|
|
10
|
+
setLoading(true);
|
|
11
|
+
setError(null);
|
|
12
|
+
fetch(url)
|
|
13
|
+
.then(async (r) => {
|
|
14
|
+
if (!r.ok) throw new Error(`${r.status} ${r.statusText}`);
|
|
15
|
+
return (await r.json()) as T;
|
|
16
|
+
})
|
|
17
|
+
.then((d) => {
|
|
18
|
+
if (!canceled) setData(d);
|
|
19
|
+
})
|
|
20
|
+
.catch((e) => {
|
|
21
|
+
if (!canceled) setError(String(e));
|
|
22
|
+
})
|
|
23
|
+
.finally(() => {
|
|
24
|
+
if (!canceled) setLoading(false);
|
|
25
|
+
});
|
|
26
|
+
return () => {
|
|
27
|
+
canceled = true;
|
|
28
|
+
};
|
|
29
|
+
}, [url]);
|
|
30
|
+
|
|
31
|
+
return { data, error, loading };
|
|
32
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
export function useLocalStorageState<T>(key: string, initialValue: T) {
|
|
4
|
+
const [value, setValue] = React.useState<T>(() => {
|
|
5
|
+
try {
|
|
6
|
+
const raw = localStorage.getItem(key);
|
|
7
|
+
return raw ? (JSON.parse(raw) as T) : initialValue;
|
|
8
|
+
} catch {
|
|
9
|
+
return initialValue;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
React.useEffect(() => {
|
|
14
|
+
try {
|
|
15
|
+
localStorage.setItem(key, JSON.stringify(value));
|
|
16
|
+
} catch {
|
|
17
|
+
// ignore
|
|
18
|
+
}
|
|
19
|
+
}, [key, value]);
|
|
20
|
+
|
|
21
|
+
return [value, setValue] as const;
|
|
22
|
+
}
|
|
23
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createRoot } from "react-dom/client";
|
|
2
|
+
import { BrowserRouter, Route, Routes, Navigate } from "react-router-dom";
|
|
3
|
+
import "./styles.css";
|
|
4
|
+
import "streamdown/styles.css";
|
|
5
|
+
import { AppLayout } from "./components/app-layout";
|
|
6
|
+
import { OverviewPage } from "./pages/overview-page";
|
|
7
|
+
import { ChannelsPage } from "./pages/channels-page";
|
|
8
|
+
import { ChatPage } from "./pages/chat-page";
|
|
9
|
+
import { DocsPage } from "./pages/docs-page";
|
|
10
|
+
import { ModelsPage } from "./pages/models-page";
|
|
11
|
+
|
|
12
|
+
function App() {
|
|
13
|
+
return (
|
|
14
|
+
<BrowserRouter>
|
|
15
|
+
<AppLayout>
|
|
16
|
+
<Routes>
|
|
17
|
+
<Route path="/" element={<Navigate to="/chat" replace />} />
|
|
18
|
+
<Route path="/overview" element={<OverviewPage />} />
|
|
19
|
+
<Route path="/docs" element={<DocsPage />} />
|
|
20
|
+
<Route path="/models" element={<ModelsPage />} />
|
|
21
|
+
<Route path="/channels" element={<ChannelsPage />} />
|
|
22
|
+
<Route path="/chat" element={<ChatPage />} />
|
|
23
|
+
</Routes>
|
|
24
|
+
</AppLayout>
|
|
25
|
+
</BrowserRouter>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const root = createRoot(document.getElementById("root")!);
|
|
30
|
+
root.render(<App />);
|