@coze-arch/cli 0.0.18 → 0.0.19-beta.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.
Files changed (104) hide show
  1. package/lib/__templates__/expo/.coze +1 -0
  2. package/lib/__templates__/expo/.cozeproj/scripts/validate.sh +8 -0
  3. package/lib/__templates__/expo/package.json +2 -1
  4. package/lib/__templates__/nextjs/.coze +1 -0
  5. package/lib/__templates__/nextjs/package.json +3 -1
  6. package/lib/__templates__/nextjs/scripts/validate.sh +10 -0
  7. package/lib/__templates__/nuxt-vue/.coze +1 -0
  8. package/lib/__templates__/nuxt-vue/eslint.config.mjs +25 -0
  9. package/lib/__templates__/nuxt-vue/package.json +9 -2
  10. package/lib/__templates__/nuxt-vue/pnpm-lock.yaml +790 -10
  11. package/lib/__templates__/nuxt-vue/scripts/validate.sh +10 -0
  12. package/lib/__templates__/pi-agent/.coze +10 -0
  13. package/lib/__templates__/pi-agent/AGENTS.md +144 -0
  14. package/lib/__templates__/pi-agent/README.md +216 -0
  15. package/lib/__templates__/pi-agent/_gitignore +3 -0
  16. package/lib/__templates__/pi-agent/_npmrc +23 -0
  17. package/lib/__templates__/pi-agent/bin/pi-bot.ts +8 -0
  18. package/lib/__templates__/pi-agent/docs/project-overview.md +374 -0
  19. package/lib/__templates__/pi-agent/docs/user/getting-started.md +47 -0
  20. package/lib/__templates__/pi-agent/package.json +63 -0
  21. package/lib/__templates__/pi-agent/pi-resources/SYSTEM.md +15 -0
  22. package/lib/__templates__/pi-agent/pi-resources/extensions/preference-memory/index.ts +355 -0
  23. package/lib/__templates__/pi-agent/pi-resources/skills/coze-asr/SKILL.md +36 -0
  24. package/lib/__templates__/pi-agent/pi-resources/skills/coze-asr/scripts/asr.mjs +9 -0
  25. package/lib/__templates__/pi-agent/pi-resources/skills/coze-image-gen/SKILL.md +41 -0
  26. package/lib/__templates__/pi-agent/pi-resources/skills/coze-image-gen/scripts/gen.mjs +9 -0
  27. package/lib/__templates__/pi-agent/pi-resources/skills/coze-tts/SKILL.md +85 -0
  28. package/lib/__templates__/pi-agent/pi-resources/skills/coze-tts/scripts/tts.mjs +9 -0
  29. package/lib/__templates__/pi-agent/pi-resources/skills/coze-video-gen/SKILL.md +53 -0
  30. package/lib/__templates__/pi-agent/pi-resources/skills/coze-video-gen/scripts/gen.mjs +9 -0
  31. package/lib/__templates__/pi-agent/pnpm-lock.yaml +8282 -0
  32. package/lib/__templates__/pi-agent/scripts/dev.sh +14 -0
  33. package/lib/__templates__/pi-agent/scripts/prepare.sh +35 -0
  34. package/lib/__templates__/pi-agent/src/agent.ts +363 -0
  35. package/lib/__templates__/pi-agent/src/channels/feishu/index.ts +760 -0
  36. package/lib/__templates__/pi-agent/src/channels/feishu/streaming-card.ts +297 -0
  37. package/lib/__templates__/pi-agent/src/channels/wechat/index.ts +171 -0
  38. package/lib/__templates__/pi-agent/src/cli.ts +117 -0
  39. package/lib/__templates__/pi-agent/src/config.ts +708 -0
  40. package/lib/__templates__/pi-agent/src/core.ts +218 -0
  41. package/lib/__templates__/pi-agent/src/dashboard/api/channels.ts +104 -0
  42. package/lib/__templates__/pi-agent/src/dashboard/api/docs.ts +204 -0
  43. package/lib/__templates__/pi-agent/src/dashboard/api/models.ts +98 -0
  44. package/lib/__templates__/pi-agent/src/dashboard/api/overview.ts +33 -0
  45. package/lib/__templates__/pi-agent/src/dashboard/config-store.ts +64 -0
  46. package/lib/__templates__/pi-agent/src/dashboard/index.ts +39 -0
  47. package/lib/__templates__/pi-agent/src/dashboard/server.ts +622 -0
  48. package/lib/__templates__/pi-agent/src/dashboard/types.ts +25 -0
  49. package/lib/__templates__/pi-agent/src/dashboard/web/index.html +13 -0
  50. package/lib/__templates__/pi-agent/src/dashboard/web/postcss.config.cjs +7 -0
  51. package/lib/__templates__/pi-agent/src/dashboard/web/src/components/app-layout.tsx +186 -0
  52. package/lib/__templates__/pi-agent/src/dashboard/web/src/components/page-title.tsx +17 -0
  53. package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/alert.tsx +22 -0
  54. package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/badge.tsx +25 -0
  55. package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/button.tsx +40 -0
  56. package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/card.tsx +29 -0
  57. package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/input.tsx +18 -0
  58. package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/label.tsx +8 -0
  59. package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/select.tsx +80 -0
  60. package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/separator.tsx +23 -0
  61. package/lib/__templates__/pi-agent/src/dashboard/web/src/hooks/use-fetch.ts +32 -0
  62. package/lib/__templates__/pi-agent/src/dashboard/web/src/hooks/use-local-storage-state.ts +23 -0
  63. package/lib/__templates__/pi-agent/src/dashboard/web/src/main.tsx +30 -0
  64. package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/channels-page.tsx +188 -0
  65. package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/chat-page.tsx +451 -0
  66. package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/docs-page.tsx +65 -0
  67. package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/models-page.tsx +122 -0
  68. package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/overview-page.tsx +134 -0
  69. package/lib/__templates__/pi-agent/src/dashboard/web/src/services/chat-ws-service.ts +167 -0
  70. package/lib/__templates__/pi-agent/src/dashboard/web/src/styles.css +294 -0
  71. package/lib/__templates__/pi-agent/src/dashboard/web/src/utils/index.ts +11 -0
  72. package/lib/__templates__/pi-agent/src/dashboard/web/tsconfig.json +13 -0
  73. package/lib/__templates__/pi-agent/src/dashboard/web/vite.config.ts +17 -0
  74. package/lib/__templates__/pi-agent/src/index.ts +123 -0
  75. package/lib/__templates__/pi-agent/src/pi-resources.ts +125 -0
  76. package/lib/__templates__/pi-agent/src/session-store.ts +223 -0
  77. package/lib/__templates__/pi-agent/src/tools/common/format-coze-error.ts +12 -0
  78. package/lib/__templates__/pi-agent/src/tools/index.ts +2 -0
  79. package/lib/__templates__/pi-agent/src/tools/web-fetch/index.ts +195 -0
  80. package/lib/__templates__/pi-agent/src/tools/web-search/index.ts +206 -0
  81. package/lib/__templates__/pi-agent/template.config.js +45 -0
  82. package/lib/__templates__/pi-agent/tests/cli.test.ts +136 -0
  83. package/lib/__templates__/pi-agent/tests/config.test.ts +315 -0
  84. package/lib/__templates__/pi-agent/tests/dashboard-docs-api.test.ts +125 -0
  85. package/lib/__templates__/pi-agent/tests/dashboard-models-api.test.ts +171 -0
  86. package/lib/__templates__/pi-agent/tests/feishu-channel.test.ts +149 -0
  87. package/lib/__templates__/pi-agent/tests/feishu-streaming-card.test.ts +15 -0
  88. package/lib/__templates__/pi-agent/tests/pi-resources.test.ts +73 -0
  89. package/lib/__templates__/pi-agent/tests/preference-memory.test.ts +43 -0
  90. package/lib/__templates__/pi-agent/tests/session-store.test.ts +61 -0
  91. package/lib/__templates__/pi-agent/tests/smoke/run-smoke.ts +275 -0
  92. package/lib/__templates__/pi-agent/tests/web-fetch.test.ts +157 -0
  93. package/lib/__templates__/pi-agent/tests/web-search.test.ts +208 -0
  94. package/lib/__templates__/pi-agent/tsconfig.json +21 -0
  95. package/lib/__templates__/pi-agent/types/larksuiteoapi-node-sdk.d.ts +113 -0
  96. package/lib/__templates__/taro/.coze +1 -0
  97. package/lib/__templates__/taro/.cozeproj/scripts/validate.sh +8 -0
  98. package/lib/__templates__/taro/package.json +1 -1
  99. package/lib/__templates__/templates.json +24 -0
  100. package/lib/__templates__/vite/.coze +1 -0
  101. package/lib/__templates__/vite/package.json +3 -1
  102. package/lib/__templates__/vite/scripts/validate.sh +10 -0
  103. package/lib/cli.js +13 -2
  104. 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,8 @@
1
+ import * as React from "react";
2
+ import { cn } from "../../utils";
3
+
4
+ function Label({ className, ...props }: React.ComponentProps<"label">) {
5
+ return <label className={cn("text-sm font-medium leading-none", className)} {...props} />;
6
+ }
7
+
8
+ export { Label };
@@ -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 />);