@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.
- package/lib/__templates__/expo/.coze +1 -0
- package/lib/__templates__/expo/.cozeproj/scripts/validate.sh +8 -0
- package/lib/__templates__/expo/package.json +2 -1
- package/lib/__templates__/nextjs/.coze +1 -0
- package/lib/__templates__/nextjs/package.json +3 -1
- package/lib/__templates__/nextjs/scripts/validate.sh +10 -0
- package/lib/__templates__/nuxt-vue/.coze +1 -0
- package/lib/__templates__/nuxt-vue/app/pages/index.vue +6 -0
- package/lib/__templates__/nuxt-vue/eslint.config.mjs +25 -0
- package/lib/__templates__/nuxt-vue/nuxt.config.ts +2 -2
- package/lib/__templates__/nuxt-vue/package.json +9 -2
- package/lib/__templates__/nuxt-vue/pnpm-lock.yaml +790 -10
- package/lib/__templates__/nuxt-vue/scripts/validate.sh +10 -0
- package/lib/__templates__/pi-agent/AGENTS.md +7 -2
- package/lib/__templates__/pi-agent/README.md +2 -0
- package/lib/__templates__/pi-agent/docs/project-overview.md +9 -15
- package/lib/__templates__/pi-agent/docs/user/getting-started.md +3 -4
- package/lib/__templates__/pi-agent/pi-resources/skills/coze-asr/SKILL.md +4 -10
- package/lib/__templates__/pi-agent/pi-resources/skills/coze-image-gen/SKILL.md +6 -18
- package/lib/__templates__/pi-agent/pi-resources/skills/coze-tts/SKILL.md +9 -37
- package/lib/__templates__/pi-agent/pi-resources/skills/coze-video-gen/SKILL.md +17 -30
- package/lib/__templates__/pi-agent/src/config.ts +60 -19
- package/lib/__templates__/pi-agent/src/core.ts +1 -0
- package/lib/__templates__/pi-agent/src/dashboard/index.ts +39 -4
- package/lib/__templates__/pi-agent/src/dashboard/server.ts +0 -12
- package/lib/__templates__/pi-agent/src/dashboard/web/src/components/app-layout.tsx +1 -15
- package/lib/__templates__/pi-agent/src/dashboard/web/src/main.tsx +0 -6
- package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/chat-page.tsx +0 -11
- package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/overview-page.tsx +268 -72
- package/lib/__templates__/pi-agent/src/dashboard/web/src/styles.css +0 -91
- package/lib/__templates__/pi-agent/tests/config.test.ts +63 -1
- package/lib/__templates__/taro/.coze +1 -0
- package/lib/__templates__/taro/.cozeproj/scripts/validate.sh +8 -0
- package/lib/__templates__/taro/package.json +1 -1
- package/lib/__templates__/vite/.coze +1 -0
- package/lib/__templates__/vite/package.json +3 -1
- package/lib/__templates__/vite/scripts/validate.sh +10 -0
- package/lib/cli.js +17 -6
- package/package.json +1 -1
- package/lib/__templates__/pi-agent/pi-resources/skills/coze-asr/scripts/asr.mjs +0 -9
- package/lib/__templates__/pi-agent/pi-resources/skills/coze-image-gen/scripts/gen.mjs +0 -9
- package/lib/__templates__/pi-agent/pi-resources/skills/coze-tts/scripts/tts.mjs +0 -9
- package/lib/__templates__/pi-agent/pi-resources/skills/coze-video-gen/scripts/gen.mjs +0 -9
- package/lib/__templates__/pi-agent/src/dashboard/api/docs.ts +0 -204
- package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/channels-page.tsx +0 -188
- package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/docs-page.tsx +0 -65
- package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/models-page.tsx +0 -122
- 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.
|
|
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,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
|
-
}
|