@coze-arch/cli 0.0.19-alpha.ad1979 → 0.0.20-alpha.6ed483

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 (48) 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/app/pages/index.vue +6 -0
  9. package/lib/__templates__/nuxt-vue/eslint.config.mjs +25 -0
  10. package/lib/__templates__/nuxt-vue/nuxt.config.ts +2 -2
  11. package/lib/__templates__/nuxt-vue/package.json +9 -2
  12. package/lib/__templates__/nuxt-vue/pnpm-lock.yaml +790 -10
  13. package/lib/__templates__/nuxt-vue/scripts/validate.sh +10 -0
  14. package/lib/__templates__/pi-agent/AGENTS.md +7 -2
  15. package/lib/__templates__/pi-agent/README.md +2 -0
  16. package/lib/__templates__/pi-agent/docs/project-overview.md +9 -15
  17. package/lib/__templates__/pi-agent/docs/user/getting-started.md +3 -4
  18. package/lib/__templates__/pi-agent/pi-resources/skills/coze-asr/SKILL.md +4 -10
  19. package/lib/__templates__/pi-agent/pi-resources/skills/coze-image-gen/SKILL.md +6 -18
  20. package/lib/__templates__/pi-agent/pi-resources/skills/coze-tts/SKILL.md +9 -37
  21. package/lib/__templates__/pi-agent/pi-resources/skills/coze-video-gen/SKILL.md +17 -30
  22. package/lib/__templates__/pi-agent/src/config.ts +60 -19
  23. package/lib/__templates__/pi-agent/src/core.ts +1 -0
  24. package/lib/__templates__/pi-agent/src/dashboard/index.ts +39 -4
  25. package/lib/__templates__/pi-agent/src/dashboard/server.ts +0 -12
  26. package/lib/__templates__/pi-agent/src/dashboard/web/src/components/app-layout.tsx +1 -15
  27. package/lib/__templates__/pi-agent/src/dashboard/web/src/main.tsx +0 -6
  28. package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/chat-page.tsx +0 -11
  29. package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/overview-page.tsx +268 -72
  30. package/lib/__templates__/pi-agent/src/dashboard/web/src/styles.css +0 -91
  31. package/lib/__templates__/pi-agent/tests/config.test.ts +63 -1
  32. package/lib/__templates__/taro/.coze +1 -0
  33. package/lib/__templates__/taro/.cozeproj/scripts/validate.sh +8 -0
  34. package/lib/__templates__/taro/package.json +1 -1
  35. package/lib/__templates__/vite/.coze +1 -0
  36. package/lib/__templates__/vite/package.json +3 -1
  37. package/lib/__templates__/vite/scripts/validate.sh +10 -0
  38. package/lib/cli.js +17 -6
  39. package/package.json +1 -1
  40. package/lib/__templates__/pi-agent/pi-resources/skills/coze-asr/scripts/asr.mjs +0 -9
  41. package/lib/__templates__/pi-agent/pi-resources/skills/coze-image-gen/scripts/gen.mjs +0 -9
  42. package/lib/__templates__/pi-agent/pi-resources/skills/coze-tts/scripts/tts.mjs +0 -9
  43. package/lib/__templates__/pi-agent/pi-resources/skills/coze-video-gen/scripts/gen.mjs +0 -9
  44. package/lib/__templates__/pi-agent/src/dashboard/api/docs.ts +0 -204
  45. package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/channels-page.tsx +0 -188
  46. package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/docs-page.tsx +0 -65
  47. package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/models-page.tsx +0 -122
  48. package/lib/__templates__/pi-agent/tests/dashboard-docs-api.test.ts +0 -125
package/lib/cli.js CHANGED
@@ -2107,7 +2107,7 @@ const EventBuilder = {
2107
2107
  };
2108
2108
 
2109
2109
  var name = "@coze-arch/cli";
2110
- var version = "0.0.19-alpha.ad1979";
2110
+ var version = "0.0.20-alpha.6ed483";
2111
2111
  var description = "coze coding devtools cli";
2112
2112
  var license = "MIT";
2113
2113
  var author = "fanwenjie.fe@bytedance.com";
@@ -5985,7 +5985,6 @@ const getCommandConfig = (
5985
5985
  ) => {
5986
5986
  let commandConfig;
5987
5987
 
5988
- // 根据命令名称映射到配置路径
5989
5988
  switch (commandName) {
5990
5989
  case 'dev':
5991
5990
  commandConfig = _optionalChain$8([config, 'access', _ => _.dev, 'optionalAccess', _2 => _2.run]);
@@ -5996,6 +5995,9 @@ const getCommandConfig = (
5996
5995
  case 'start':
5997
5996
  commandConfig = _optionalChain$8([config, 'access', _5 => _5.deploy, 'optionalAccess', _6 => _6.run]);
5998
5997
  break;
5998
+ case 'validate':
5999
+ commandConfig = _optionalChain$8([config, 'access', _7 => _7.dev, 'optionalAccess', _8 => _8.validate]);
6000
+ break;
5999
6001
  default:
6000
6002
  throw new Error(`Unknown command: ${commandName}`);
6001
6003
  }
@@ -6215,7 +6217,7 @@ const isPackageJsonValid$3 = async (projectFolder) => {
6215
6217
  return false;
6216
6218
  }
6217
6219
  if (typeof scripts.validate !== 'undefined') {
6218
- logger.info('[patch-expo-validate] NOT APPLY: validate exists');
6220
+ logger.info('[patch-expo-validate] NOT APPLY: pnpm validate exists');
6219
6221
  return false;
6220
6222
  }
6221
6223
  return true;
@@ -6389,7 +6391,7 @@ const isPackageJsonValid$2 = async (projectFolder) => {
6389
6391
  return false;
6390
6392
  }
6391
6393
  if (typeof scripts.validate !== 'undefined') {
6392
- logger.info('[patch-nextjs-validate] NOT APPLY: validate exists');
6394
+ logger.info('[patch-nextjs-validate] NOT APPLY: pnpm validate exists');
6393
6395
  return false;
6394
6396
  }
6395
6397
  return true;
@@ -6570,7 +6572,7 @@ const isPackageJsonValid$1 = async (projectFolder) => {
6570
6572
  return false;
6571
6573
  }
6572
6574
  if (typeof scripts.validate !== 'undefined') {
6573
- logger.info('[patch-vite-validate] NOT APPLY: validate exists');
6575
+ logger.info('[patch-vite-validate] NOT APPLY: pnpm validate exists');
6574
6576
  return false;
6575
6577
  }
6576
6578
  return true;
@@ -6753,7 +6755,7 @@ const isPackageJsonValid = async (projectFolder) => {
6753
6755
  }
6754
6756
 
6755
6757
  if (typeof scripts.validate !== 'undefined') {
6756
- logger.info('[patch-taro-validate] NOT APPLY: validate exists');
6758
+ logger.info('[patch-taro-validate] NOT APPLY: pnpm validate exists');
6757
6759
  return false;
6758
6760
  }
6759
6761
 
@@ -7825,6 +7827,15 @@ const registerCommand$3 = program => {
7825
7827
  .action(async options => {
7826
7828
  await executeRun('start', options);
7827
7829
  });
7830
+
7831
+ // validate 命令
7832
+ program
7833
+ .command('validate')
7834
+ .description('Validate project (lint + type check)')
7835
+ .option('--log-file <path>', 'Log file path')
7836
+ .action(async options => {
7837
+ await executeRun('validate', options);
7838
+ });
7828
7839
  };
7829
7840
 
7830
7841
  // ABOUTME: Nuxt route scanner
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coze-arch/cli",
3
- "version": "0.0.19-alpha.ad1979",
3
+ "version": "0.0.20-alpha.6ed483",
4
4
  "private": false,
5
5
  "description": "coze coding devtools cli",
6
6
  "license": "MIT",
@@ -1,9 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import createJiti from "jiti";
4
-
5
- const jiti = createJiti(import.meta.url);
6
- const { runAsrCli } = await jiti.import("../../../src/skill-cli.ts");
7
-
8
- const code = await runAsrCli(process.argv.slice(2), process.env);
9
- process.exit(code);
@@ -1,9 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import createJiti from "jiti";
4
-
5
- const jiti = createJiti(import.meta.url);
6
- const { runImageCli } = await jiti.import("../../../src/skill-cli.ts");
7
-
8
- const code = await runImageCli(process.argv.slice(2), process.env);
9
- process.exit(code);
@@ -1,9 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import createJiti from "jiti";
4
-
5
- const jiti = createJiti(import.meta.url);
6
- const { runTtsCli } = await jiti.import("../../../src/skill-cli.ts");
7
-
8
- const code = await runTtsCli(process.argv.slice(2), process.env);
9
- process.exit(code);
@@ -1,9 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import createJiti from "jiti";
4
-
5
- const jiti = createJiti(import.meta.url);
6
- const { runVideoCli } = await jiti.import("../../../src/skill-cli.ts");
7
-
8
- const code = await runVideoCli(process.argv.slice(2), process.env);
9
- process.exit(code);
@@ -1,204 +0,0 @@
1
- import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
2
- import { join, relative, sep } from "node:path";
3
-
4
- export type DashboardDocSummary = {
5
- slug: string;
6
- title: string;
7
- summary: string;
8
- group: string;
9
- order: number;
10
- updatedAt: string;
11
- };
12
-
13
- export type DashboardDocDetail = DashboardDocSummary & {
14
- content: string;
15
- };
16
-
17
- export type DashboardDocsResponse = {
18
- docs: DashboardDocSummary[];
19
- selectedDoc: DashboardDocDetail | null;
20
- requestedSlug?: string;
21
- requestedSlugFound: boolean;
22
- };
23
-
24
- type ParsedFrontmatter = {
25
- title?: string;
26
- summary?: string;
27
- group?: string;
28
- order?: number;
29
- };
30
-
31
- function listMarkdownFiles(rootDir: string): string[] {
32
- if (!existsSync(rootDir)) {
33
- return [];
34
- }
35
-
36
- const entries = readdirSync(rootDir, { withFileTypes: true }).sort((left, right) =>
37
- left.name.localeCompare(right.name)
38
- );
39
- const files: string[] = [];
40
-
41
- for (const entry of entries) {
42
- const fullPath = join(rootDir, entry.name);
43
- if (entry.isDirectory()) {
44
- files.push(...listMarkdownFiles(fullPath));
45
- continue;
46
- }
47
- if (entry.isFile() && entry.name.endsWith(".md")) {
48
- files.push(fullPath);
49
- }
50
- }
51
-
52
- return files;
53
- }
54
-
55
- function stripQuotes(value: string): string {
56
- if (
57
- (value.startsWith('"') && value.endsWith('"')) ||
58
- (value.startsWith("'") && value.endsWith("'"))
59
- ) {
60
- return value.slice(1, -1);
61
- }
62
- return value;
63
- }
64
-
65
- function parseFrontmatter(raw: string): { meta: ParsedFrontmatter; content: string } {
66
- const normalized = raw.replace(/\r\n/g, "\n");
67
- if (!normalized.startsWith("---\n")) {
68
- return { meta: {}, content: raw };
69
- }
70
-
71
- const lines = normalized.split("\n");
72
- let closingIndex = -1;
73
- for (let index = 1; index < lines.length; index += 1) {
74
- if (lines[index]?.trim() === "---") {
75
- closingIndex = index;
76
- break;
77
- }
78
- }
79
-
80
- if (closingIndex === -1) {
81
- return { meta: {}, content: raw };
82
- }
83
-
84
- const meta: ParsedFrontmatter = {};
85
- for (const line of lines.slice(1, closingIndex)) {
86
- const trimmed = line.trim();
87
- if (!trimmed || trimmed.startsWith("#")) continue;
88
-
89
- const match = trimmed.match(/^([A-Za-z][A-Za-z0-9_-]*):\s*(.*)$/);
90
- if (!match) continue;
91
-
92
- const key = match[1]!;
93
- const value = stripQuotes(match[2]!.trim());
94
- if (!value) continue;
95
-
96
- if (key === "order") {
97
- const parsed = Number(value);
98
- if (Number.isFinite(parsed)) {
99
- meta.order = parsed;
100
- }
101
- continue;
102
- }
103
-
104
- if (key === "title") meta.title = value;
105
- if (key === "summary") meta.summary = value;
106
- if (key === "group") meta.group = value;
107
- }
108
-
109
- return {
110
- meta,
111
- content: lines.slice(closingIndex + 1).join("\n").trimStart(),
112
- };
113
- }
114
-
115
- function extractHeading(content: string): string {
116
- const lines = content.replace(/\r\n/g, "\n").split("\n");
117
- for (const line of lines) {
118
- const match = line.trim().match(/^#\s+(.+)$/);
119
- if (match) {
120
- return match[1]!.trim();
121
- }
122
- }
123
- return "";
124
- }
125
-
126
- function extractSummary(content: string): string {
127
- const lines = content.replace(/\r\n/g, "\n").split("\n");
128
- const paragraph: string[] = [];
129
- let inCodeBlock = false;
130
-
131
- for (const line of lines) {
132
- const trimmed = line.trim();
133
- if (!trimmed) {
134
- if (paragraph.length > 0) break;
135
- continue;
136
- }
137
- if (trimmed.startsWith("```")) {
138
- inCodeBlock = !inCodeBlock;
139
- if (paragraph.length > 0) break;
140
- continue;
141
- }
142
- if (inCodeBlock) continue;
143
- if (trimmed.startsWith("#")) continue;
144
- if (trimmed.startsWith("- ") || trimmed.startsWith("* ") || /^\d+\.\s/.test(trimmed)) {
145
- continue;
146
- }
147
-
148
- paragraph.push(trimmed.replace(/^>\s?/, ""));
149
- }
150
-
151
- return paragraph.join(" ").trim();
152
- }
153
-
154
- function createTitleFromSlug(slug: string): string {
155
- const tail = slug.split("/").at(-1) ?? slug;
156
- return tail
157
- .split(/[-_]/g)
158
- .filter(Boolean)
159
- .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
160
- .join(" ");
161
- }
162
-
163
- function loadDoc(filePath: string, docsDir: string): DashboardDocDetail {
164
- const relativePath = relative(docsDir, filePath).split(sep).join("/");
165
- const slug = relativePath.replace(/\.md$/i, "");
166
- const raw = readFileSync(filePath, "utf-8");
167
- const parsed = parseFrontmatter(raw);
168
- const title = parsed.meta.title?.trim() || extractHeading(parsed.content) || createTitleFromSlug(slug);
169
- const summary = parsed.meta.summary?.trim() || extractSummary(parsed.content);
170
- const group = parsed.meta.group?.trim() || "其他";
171
- const order = parsed.meta.order ?? Number.MAX_SAFE_INTEGER;
172
- const updatedAt = statSync(filePath).mtime.toISOString();
173
-
174
- return {
175
- slug,
176
- title,
177
- summary,
178
- group,
179
- order,
180
- updatedAt,
181
- content: parsed.content,
182
- };
183
- }
184
-
185
- export function readDocsResponse(args: { docsDir: string; slug?: string }): DashboardDocsResponse {
186
- const docs = listMarkdownFiles(args.docsDir)
187
- .map((filePath) => loadDoc(filePath, args.docsDir))
188
- .sort((left, right) => {
189
- if (left.order !== right.order) return left.order - right.order;
190
- return left.title.localeCompare(right.title);
191
- });
192
-
193
- const requestedSlug = args.slug?.trim();
194
- const selectedDoc = requestedSlug
195
- ? docs.find((doc) => doc.slug === requestedSlug) ?? docs[0] ?? null
196
- : docs[0] ?? null;
197
-
198
- return {
199
- docs: docs.map(({ content: _content, ...summary }) => summary),
200
- selectedDoc,
201
- requestedSlug,
202
- requestedSlugFound: requestedSlug ? selectedDoc?.slug === requestedSlug : true,
203
- };
204
- }
@@ -1,188 +0,0 @@
1
- import React from "react";
2
- import { Alert, AlertDescription, AlertTitle } from "../components/ui/alert";
3
- import { Button } from "../components/ui/button";
4
- import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../components/ui/card";
5
- import { Input } from "../components/ui/input";
6
- import { Label } from "../components/ui/label";
7
- import { useFetch } from "../hooks/use-fetch";
8
- import { toast } from "sonner";
9
- import { Link } from "react-router-dom";
10
-
11
- type Channels = {
12
- routing: {
13
- feishuGroupRequireMention: boolean;
14
- wechatGroupRequireMention: boolean;
15
- };
16
- feishu: {
17
- enabled: boolean;
18
- requireMention?: boolean;
19
- appId?: string;
20
- domain?: string;
21
- encryptKey?: string;
22
- verificationToken?: string;
23
- appSecret?: string;
24
- thinkingReaction?: {
25
- enabled?: boolean;
26
- emojiType?: string;
27
- };
28
- };
29
- wechat: {
30
- enabled: boolean;
31
- requireMention?: boolean;
32
- implementation?: string;
33
- };
34
- };
35
-
36
- export function ChannelsPage() {
37
- const { data, error, loading } = useFetch<Channels>("/api/channels");
38
- const [saving, setSaving] = React.useState(false);
39
- const [form, setForm] = React.useState<Channels | null>(null);
40
-
41
- React.useEffect(() => {
42
- if (data) setForm(data);
43
- }, [data]);
44
-
45
- const patch = (fn: (draft: Channels) => void) => {
46
- if (!form) return;
47
- const draft = { ...form, feishu: { ...form.feishu }, wechat: { ...form.wechat } };
48
- fn(draft);
49
- setForm(draft);
50
- };
51
-
52
- const onSave = async () => {
53
- if (!form) return;
54
- setSaving(true);
55
- try {
56
- const res = await fetch("/api/channels", {
57
- method: "POST",
58
- headers: { "Content-Type": "application/json" },
59
- body: JSON.stringify({ channels: form }),
60
- });
61
- const json = (await res.json()) as { ok?: boolean; error?: string };
62
- if (!res.ok || json.ok === false) {
63
- throw new Error(json.error || `${res.status} ${res.statusText}`);
64
- }
65
- toast.success("已保存(需要重启进程生效)");
66
- } catch (e) {
67
- toast.error(`保存失败:${String(e)}`, { duration: 4500 });
68
- } finally {
69
- setSaving(false);
70
- }
71
- };
72
-
73
- return (
74
- <section className="space-y-4 px-4 pb-4 pt-0 sm:px-5 sm:pb-5 sm:pt-0 md:px-6 md:pb-6 md:pt-0">
75
- <div className="sticky top-0 z-20 -mx-4 bg-background px-4 pb-3 pt-4 sm:-mx-5 sm:px-5 sm:pb-4 sm:pt-5 md:-mx-6 md:px-6 md:pb-5 md:pt-6">
76
- <div className="flex flex-col gap-3 bg-background md:flex-row md:items-start md:justify-between">
77
- <div className="space-y-1">
78
- <h1 className="text-lg font-semibold tracking-tight">渠道配置</h1>
79
- <p className="text-sm text-muted-foreground">管理各渠道的启用状态与接入参数。</p>
80
- </div>
81
- <Button
82
- onClick={onSave}
83
- disabled={!form || saving || loading || Boolean(error)}
84
- className="shrink-0"
85
- >
86
- {saving ? "保存中…" : "保存"}
87
- </Button>
88
- </div>
89
- </div>
90
-
91
- {loading ? (
92
- <Alert>
93
- <AlertTitle>加载中</AlertTitle>
94
- <AlertDescription>正在读取渠道配置。</AlertDescription>
95
- </Alert>
96
- ) : error ? (
97
- <Alert className="border-destructive/20 bg-destructive/5 text-destructive">
98
- <AlertTitle>读取失败</AlertTitle>
99
- <AlertDescription>错误:{error}</AlertDescription>
100
- </Alert>
101
- ) : form ? (
102
- <>
103
- <div className="space-y-4">
104
- <Card className="border-border/60 bg-background/70 shadow-none">
105
- <CardHeader>
106
- <div className="flex items-center gap-3">
107
- <CardTitle>飞书</CardTitle>
108
- <Link
109
- to="/docs"
110
- className="text-sm font-medium text-muted-foreground underline decoration-border underline-offset-4 transition-colors hover:text-primary"
111
- >
112
- 接入说明
113
- </Link>
114
- </div>
115
- <CardDescription>管理飞书渠道的启用状态与接入凭证。</CardDescription>
116
- </CardHeader>
117
- <CardContent className="grid gap-4">
118
- <CheckboxField
119
- label="启用状态"
120
- checked={!!form.feishu.enabled}
121
- onChange={(checked) => patch((d) => (d.feishu.enabled = checked))}
122
- />
123
- <TextField
124
- label="App ID"
125
- value={form.feishu.appId ?? ""}
126
- onChange={(value) => patch((d) => (d.feishu.appId = value))}
127
- />
128
- <TextField
129
- label="App Secret"
130
- value={form.feishu.appSecret ?? ""}
131
- onChange={(value) => patch((d) => (d.feishu.appSecret = value))}
132
- />
133
- </CardContent>
134
- </Card>
135
- </div>
136
- </>
137
- ) : null}
138
- </section>
139
- );
140
- }
141
-
142
- function FieldShell(props: { label: string; children: React.ReactNode; description?: string }) {
143
- return (
144
- <div className="space-y-2">
145
- <Label className="text-xs uppercase tracking-[0.16em] text-muted-foreground">{props.label}</Label>
146
- {props.children}
147
- {props.description ? <p className="text-xs text-muted-foreground">{props.description}</p> : null}
148
- </div>
149
- );
150
- }
151
-
152
- function TextField(props: {
153
- label: string;
154
- value: string;
155
- placeholder?: string;
156
- onChange: (value: string) => void;
157
- }) {
158
- return (
159
- <FieldShell label={props.label}>
160
- <Input
161
- className="font-mono text-xs sm:text-sm"
162
- value={props.value}
163
- placeholder={props.placeholder}
164
- onChange={(e) => props.onChange(e.target.value)}
165
- />
166
- </FieldShell>
167
- );
168
- }
169
-
170
- function CheckboxField(props: {
171
- label: string;
172
- checked: boolean;
173
- onChange: (checked: boolean) => void;
174
- }) {
175
- return (
176
- <FieldShell label={props.label}>
177
- <label className="flex h-10 items-center gap-3 rounded-md border border-input bg-background/80 px-3 text-sm">
178
- <input
179
- type="checkbox"
180
- className="h-4 w-4 rounded border-border accent-primary"
181
- checked={props.checked}
182
- onChange={(e) => props.onChange(e.target.checked)}
183
- />
184
- <span className="text-sm text-foreground">{props.checked ? "已启用" : "未启用"}</span>
185
- </label>
186
- </FieldShell>
187
- );
188
- }
@@ -1,65 +0,0 @@
1
- import type { ComponentPropsWithoutRef } from "react";
2
- import { Streamdown } from "streamdown";
3
- import { code } from "@streamdown/code";
4
- import { Link } from "react-router-dom";
5
- import { Alert, AlertDescription, AlertTitle } from "../components/ui/alert";
6
- import { useFetch } from "../hooks/use-fetch";
7
-
8
- type DashboardDocDetail = {
9
- slug: string;
10
- title: string;
11
- content: string;
12
- };
13
-
14
- type DashboardDocsResponse = {
15
- selectedDoc: DashboardDocDetail | null;
16
- };
17
-
18
- const DEFAULT_DOC_SLUG = "getting-started";
19
-
20
- function DocsLink(props: ComponentPropsWithoutRef<"a">) {
21
- const href = typeof props.href === "string" ? props.href : "";
22
- const isInternalRoute = href.startsWith("/") && !href.startsWith("//");
23
-
24
- if (isInternalRoute) {
25
- return (
26
- <Link to={href} className={props.className} title={props.title}>
27
- {props.children}
28
- </Link>
29
- );
30
- }
31
-
32
- return <a {...props} />;
33
- }
34
-
35
- export function DocsPage() {
36
- const { data, error, loading } = useFetch<DashboardDocsResponse>(`/api/docs?slug=${DEFAULT_DOC_SLUG}`);
37
- const selectedDoc = data?.selectedDoc ?? null;
38
-
39
- return (
40
- <section className="p-4 sm:p-5 md:p-6">
41
- {loading ? (
42
- <Alert>
43
- <AlertTitle>加载中</AlertTitle>
44
- <AlertDescription>正在读取项目文档。</AlertDescription>
45
- </Alert>
46
- ) : error ? (
47
- <Alert className="border-destructive/20 bg-destructive/5 text-destructive">
48
- <AlertTitle>读取失败</AlertTitle>
49
- <AlertDescription>错误:{error}</AlertDescription>
50
- </Alert>
51
- ) : !selectedDoc ? (
52
- <Alert>
53
- <AlertTitle>暂无文档</AlertTitle>
54
- <AlertDescription>当前项目里还没有可展示的用户文档。</AlertDescription>
55
- </Alert>
56
- ) : (
57
- <div className="docs-content streamdown-content">
58
- <Streamdown plugins={{ code }} parseIncompleteMarkdown={true} components={{ a: DocsLink }}>
59
- {selectedDoc.content}
60
- </Streamdown>
61
- </div>
62
- )}
63
- </section>
64
- );
65
- }