chatccc 0.2.223 → 0.2.224
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/config.sample.json +40 -39
- package/package.json +1 -1
- package/src/__tests__/builtin-chat-session.test.ts +33 -0
- package/src/__tests__/builtin-config.test.ts +1 -0
- package/src/__tests__/ccc-adapter.test.ts +20 -0
- package/src/__tests__/config-reload.test.ts +52 -52
- package/src/__tests__/config-sample.test.ts +41 -40
- package/src/__tests__/orchestrator.test.ts +836 -803
- package/src/__tests__/session.test.ts +1183 -1173
- package/src/adapters/ccc-adapter.ts +1 -0
- package/src/builtin/cli.ts +4 -0
- package/src/builtin/index.ts +10 -0
- package/src/config.ts +203 -192
- package/src/session.ts +5 -2
- package/src/web-ui.ts +610 -607
package/src/web-ui.ts
CHANGED
|
@@ -12,12 +12,12 @@ import { readFile, writeFile, stat } from "node:fs/promises";
|
|
|
12
12
|
import { homedir } from "node:os";
|
|
13
13
|
import { join, dirname } from "node:path";
|
|
14
14
|
import { fileURLToPath } from "node:url";
|
|
15
|
-
import { spawn, execSync } from "node:child_process";
|
|
16
|
-
import {
|
|
17
|
-
buildWebUiUrl,
|
|
18
|
-
createInternalRestartEnv,
|
|
19
|
-
openWebUiInDefaultBrowser,
|
|
20
|
-
} from "./startup-lifecycle.ts";
|
|
15
|
+
import { spawn, execSync } from "node:child_process";
|
|
16
|
+
import {
|
|
17
|
+
buildWebUiUrl,
|
|
18
|
+
createInternalRestartEnv,
|
|
19
|
+
openWebUiInDefaultBrowser,
|
|
20
|
+
} from "./startup-lifecycle.ts";
|
|
21
21
|
|
|
22
22
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
23
23
|
const PROJECT_ROOT = join(__dirname, "..");
|
|
@@ -31,16 +31,16 @@ const ILINK_AUTH_PATH = join(USER_DATA_DIR, "state", "ilink-auth.json");
|
|
|
31
31
|
// Helpers — config.json parsing & generation
|
|
32
32
|
// ---------------------------------------------------------------------------
|
|
33
33
|
|
|
34
|
-
interface AppConfig {
|
|
35
|
-
feishu?: { appId?: string; appSecret?: string };
|
|
36
|
-
platforms?: {
|
|
37
|
-
feishu?: { enabled?: boolean; platformType?: string };
|
|
38
|
-
ilink?: { enabled?: boolean; reuseTokenOnStart?: boolean };
|
|
39
|
-
};
|
|
40
|
-
webUi?: { openOnStart?: boolean };
|
|
41
|
-
chromeDevtools?: { enabled?: boolean; port?: number; chromePath?: string };
|
|
42
|
-
port?: number;
|
|
43
|
-
gitTimeoutSeconds?: number;
|
|
34
|
+
interface AppConfig {
|
|
35
|
+
feishu?: { appId?: string; appSecret?: string };
|
|
36
|
+
platforms?: {
|
|
37
|
+
feishu?: { enabled?: boolean; platformType?: string };
|
|
38
|
+
ilink?: { enabled?: boolean; reuseTokenOnStart?: boolean };
|
|
39
|
+
};
|
|
40
|
+
webUi?: { openOnStart?: boolean };
|
|
41
|
+
chromeDevtools?: { enabled?: boolean; port?: number; chromePath?: string };
|
|
42
|
+
port?: number;
|
|
43
|
+
gitTimeoutSeconds?: number;
|
|
44
44
|
claude?: { enabled?: boolean; defaultAgent?: boolean; model?: string; subagentModel?: string; effort?: string; apiKey?: string; baseUrl?: string; maxTurn?: number };
|
|
45
45
|
// `command` 是已废弃的旧字段名,保留只读以兼容升级前的 config.json
|
|
46
46
|
cursor?: {
|
|
@@ -52,17 +52,17 @@ interface AppConfig {
|
|
|
52
52
|
alternativeModel?: string;
|
|
53
53
|
avatarBatteryMode?: string;
|
|
54
54
|
onDemandMonthlyBudget?: number;
|
|
55
|
-
};
|
|
56
|
-
codex?: { enabled?: boolean; defaultAgent?: boolean; path?: string; command?: string; model?: string; alternativeModel?: string; effort?: string; fastMode?: boolean };
|
|
57
|
-
ccc?: {
|
|
58
|
-
enabled?: boolean;
|
|
59
|
-
defaultAgent?: boolean;
|
|
60
|
-
DEEPSEEK_API_KEY?: string;
|
|
61
|
-
DEEPSEEK_BASE_URL?: string;
|
|
62
|
-
model?: string;
|
|
63
|
-
alternativeModel?: string;
|
|
64
|
-
};
|
|
65
|
-
}
|
|
55
|
+
};
|
|
56
|
+
codex?: { enabled?: boolean; defaultAgent?: boolean; path?: string; command?: string; model?: string; alternativeModel?: string; effort?: string; fastMode?: boolean };
|
|
57
|
+
ccc?: {
|
|
58
|
+
enabled?: boolean;
|
|
59
|
+
defaultAgent?: boolean;
|
|
60
|
+
DEEPSEEK_API_KEY?: string;
|
|
61
|
+
DEEPSEEK_BASE_URL?: string;
|
|
62
|
+
model?: string;
|
|
63
|
+
alternativeModel?: string;
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
66
|
|
|
67
67
|
// ---------------------------------------------------------------------------
|
|
68
68
|
// /api/start 路径选择(纯函数,便于单测护栏)
|
|
@@ -118,76 +118,76 @@ function loadConfig(): AppConfig {
|
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
function saveConfig(cfg: AppConfig): void {
|
|
122
|
-
writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2), "utf8");
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
type ConfigApplyMode = "saved" | "reload" | "restart-required";
|
|
126
|
-
|
|
127
|
-
interface ConfigApplyResult {
|
|
128
|
-
mode: ConfigApplyMode;
|
|
129
|
-
restartRequired: boolean;
|
|
130
|
-
restartReasons: string[];
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function stringValue(value: unknown): string {
|
|
134
|
-
return typeof value === "string" ? value.trim() : "";
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function portValue(cfg: AppConfig): number {
|
|
138
|
-
return Number.isInteger(cfg.port) && cfg.port! >= 1 && cfg.port! <= 65535 ? cfg.port! : 18080;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function platformEnabled(cfg: AppConfig, platform: "feishu" | "ilink"): boolean {
|
|
142
|
-
return cfg.platforms?.[platform]?.enabled !== false;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function feishuPlatformType(cfg: AppConfig): string {
|
|
146
|
-
return cfg.platforms?.feishu?.platformType === "lark" ? "lark" : "feishu";
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function ilinkReuseTokenOnStart(cfg: AppConfig): boolean {
|
|
150
|
-
return cfg.platforms?.ilink?.reuseTokenOnStart !== false;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
export function getRestartRequiredReasons(before: AppConfig, after: AppConfig): string[] {
|
|
154
|
-
const reasons: string[] = [];
|
|
155
|
-
|
|
156
|
-
if (portValue(before) !== portValue(after)) reasons.push("port");
|
|
157
|
-
if (stringValue(before.feishu?.appId) !== stringValue(after.feishu?.appId)) reasons.push("feishu.appId");
|
|
158
|
-
if (stringValue(before.feishu?.appSecret) !== stringValue(after.feishu?.appSecret)) reasons.push("feishu.appSecret");
|
|
159
|
-
if (feishuPlatformType(before) !== feishuPlatformType(after)) reasons.push("platforms.feishu.platformType");
|
|
160
|
-
if (platformEnabled(before, "feishu") !== platformEnabled(after, "feishu")) reasons.push("platforms.feishu.enabled");
|
|
161
|
-
if (platformEnabled(before, "ilink") !== platformEnabled(after, "ilink")) reasons.push("platforms.ilink.enabled");
|
|
162
|
-
if (ilinkReuseTokenOnStart(before) !== ilinkReuseTokenOnStart(after)) {
|
|
163
|
-
reasons.push("platforms.ilink.reuseTokenOnStart");
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return reasons;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
async function applySavedConfigIfPossible(before: AppConfig, after: AppConfig): Promise<ConfigApplyResult> {
|
|
170
|
-
const setupMode = Boolean(setupActivateHook && setupHttpServer);
|
|
171
|
-
if (setupMode) {
|
|
172
|
-
return { mode: "saved", restartRequired: false, restartReasons: [] };
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const running = isServiceRunning();
|
|
176
|
-
if (!running) {
|
|
177
|
-
return { mode: "saved", restartRequired: false, restartReasons: [] };
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
const restartReasons = getRestartRequiredReasons(before, after);
|
|
181
|
-
if (restartReasons.length > 0) {
|
|
182
|
-
return { mode: "restart-required", restartRequired: true, restartReasons };
|
|
183
|
-
}
|
|
184
|
-
if (!reloadConfigHook) {
|
|
185
|
-
return { mode: "saved", restartRequired: false, restartReasons: [] };
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
await reloadConfigHook();
|
|
189
|
-
return { mode: "reload", restartRequired: false, restartReasons: [] };
|
|
190
|
-
}
|
|
121
|
+
function saveConfig(cfg: AppConfig): void {
|
|
122
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2), "utf8");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
type ConfigApplyMode = "saved" | "reload" | "restart-required";
|
|
126
|
+
|
|
127
|
+
interface ConfigApplyResult {
|
|
128
|
+
mode: ConfigApplyMode;
|
|
129
|
+
restartRequired: boolean;
|
|
130
|
+
restartReasons: string[];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function stringValue(value: unknown): string {
|
|
134
|
+
return typeof value === "string" ? value.trim() : "";
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function portValue(cfg: AppConfig): number {
|
|
138
|
+
return Number.isInteger(cfg.port) && cfg.port! >= 1 && cfg.port! <= 65535 ? cfg.port! : 18080;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function platformEnabled(cfg: AppConfig, platform: "feishu" | "ilink"): boolean {
|
|
142
|
+
return cfg.platforms?.[platform]?.enabled !== false;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function feishuPlatformType(cfg: AppConfig): string {
|
|
146
|
+
return cfg.platforms?.feishu?.platformType === "lark" ? "lark" : "feishu";
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function ilinkReuseTokenOnStart(cfg: AppConfig): boolean {
|
|
150
|
+
return cfg.platforms?.ilink?.reuseTokenOnStart !== false;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function getRestartRequiredReasons(before: AppConfig, after: AppConfig): string[] {
|
|
154
|
+
const reasons: string[] = [];
|
|
155
|
+
|
|
156
|
+
if (portValue(before) !== portValue(after)) reasons.push("port");
|
|
157
|
+
if (stringValue(before.feishu?.appId) !== stringValue(after.feishu?.appId)) reasons.push("feishu.appId");
|
|
158
|
+
if (stringValue(before.feishu?.appSecret) !== stringValue(after.feishu?.appSecret)) reasons.push("feishu.appSecret");
|
|
159
|
+
if (feishuPlatformType(before) !== feishuPlatformType(after)) reasons.push("platforms.feishu.platformType");
|
|
160
|
+
if (platformEnabled(before, "feishu") !== platformEnabled(after, "feishu")) reasons.push("platforms.feishu.enabled");
|
|
161
|
+
if (platformEnabled(before, "ilink") !== platformEnabled(after, "ilink")) reasons.push("platforms.ilink.enabled");
|
|
162
|
+
if (ilinkReuseTokenOnStart(before) !== ilinkReuseTokenOnStart(after)) {
|
|
163
|
+
reasons.push("platforms.ilink.reuseTokenOnStart");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return reasons;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function applySavedConfigIfPossible(before: AppConfig, after: AppConfig): Promise<ConfigApplyResult> {
|
|
170
|
+
const setupMode = Boolean(setupActivateHook && setupHttpServer);
|
|
171
|
+
if (setupMode) {
|
|
172
|
+
return { mode: "saved", restartRequired: false, restartReasons: [] };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const running = isServiceRunning();
|
|
176
|
+
if (!running) {
|
|
177
|
+
return { mode: "saved", restartRequired: false, restartReasons: [] };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const restartReasons = getRestartRequiredReasons(before, after);
|
|
181
|
+
if (restartReasons.length > 0) {
|
|
182
|
+
return { mode: "restart-required", restartRequired: true, restartReasons };
|
|
183
|
+
}
|
|
184
|
+
if (!reloadConfigHook) {
|
|
185
|
+
return { mode: "saved", restartRequired: false, restartReasons: [] };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
await reloadConfigHook();
|
|
189
|
+
return { mode: "reload", restartRequired: false, restartReasons: [] };
|
|
190
|
+
}
|
|
191
191
|
|
|
192
192
|
function maskSecret(value: string | undefined): string {
|
|
193
193
|
if (!value) return "(未设置)";
|
|
@@ -239,12 +239,12 @@ function spawnService(): { ok: boolean; pid?: number; error?: string } {
|
|
|
239
239
|
return { ok: false, error: `Entry not found: ${indexPath}` };
|
|
240
240
|
}
|
|
241
241
|
try {
|
|
242
|
-
const child = spawn("npx", ["tsx", "src/index.ts"], {
|
|
243
|
-
cwd: PROJECT_ROOT,
|
|
244
|
-
detached: true,
|
|
245
|
-
stdio: "ignore",
|
|
246
|
-
shell: true,
|
|
247
|
-
env: createInternalRestartEnv(),
|
|
242
|
+
const child = spawn("npx", ["tsx", "src/index.ts"], {
|
|
243
|
+
cwd: PROJECT_ROOT,
|
|
244
|
+
detached: true,
|
|
245
|
+
stdio: "ignore",
|
|
246
|
+
shell: true,
|
|
247
|
+
env: createInternalRestartEnv(),
|
|
248
248
|
});
|
|
249
249
|
child.unref();
|
|
250
250
|
return { ok: true, pid: child.pid ?? undefined };
|
|
@@ -262,19 +262,19 @@ function scheduleRestart(): void {
|
|
|
262
262
|
if (!existsSync(indexPath)) return;
|
|
263
263
|
if (process.platform === "win32") {
|
|
264
264
|
// Windows: ping 作为 sleep(ping 自己 3 次 ≈ 2 秒延迟)
|
|
265
|
-
spawn("cmd.exe", ["/c", "ping -n 3 127.0.0.1 > nul && npx tsx src/index.ts"], {
|
|
266
|
-
cwd: PROJECT_ROOT,
|
|
267
|
-
detached: true,
|
|
268
|
-
stdio: "ignore",
|
|
269
|
-
windowsHide: true,
|
|
270
|
-
env: createInternalRestartEnv(),
|
|
265
|
+
spawn("cmd.exe", ["/c", "ping -n 3 127.0.0.1 > nul && npx tsx src/index.ts"], {
|
|
266
|
+
cwd: PROJECT_ROOT,
|
|
267
|
+
detached: true,
|
|
268
|
+
stdio: "ignore",
|
|
269
|
+
windowsHide: true,
|
|
270
|
+
env: createInternalRestartEnv(),
|
|
271
271
|
}).unref();
|
|
272
272
|
} else {
|
|
273
|
-
spawn("bash", ["-c", "sleep 2 && npx tsx src/index.ts"], {
|
|
274
|
-
cwd: PROJECT_ROOT,
|
|
275
|
-
detached: true,
|
|
276
|
-
stdio: "ignore",
|
|
277
|
-
env: createInternalRestartEnv(),
|
|
273
|
+
spawn("bash", ["-c", "sleep 2 && npx tsx src/index.ts"], {
|
|
274
|
+
cwd: PROJECT_ROOT,
|
|
275
|
+
detached: true,
|
|
276
|
+
stdio: "ignore",
|
|
277
|
+
env: createInternalRestartEnv(),
|
|
278
278
|
}).unref();
|
|
279
279
|
}
|
|
280
280
|
}
|
|
@@ -383,14 +383,14 @@ async function handlePostConfig(req: IncomingMessage, res: ServerResponse): Prom
|
|
|
383
383
|
existing as unknown as Record<string, unknown>,
|
|
384
384
|
unflattenConfig(updates),
|
|
385
385
|
) as AppConfig;
|
|
386
|
-
try {
|
|
387
|
-
saveConfig(merged);
|
|
388
|
-
const applyResult = await applySavedConfigIfPossible(existing, merged);
|
|
389
|
-
jsonReply(res, 200, { ok: true, ...applyResult });
|
|
390
|
-
} catch (err) {
|
|
391
|
-
jsonReply(res, 500, { ok: false, saved: true, error: (err as Error).message });
|
|
392
|
-
}
|
|
393
|
-
}
|
|
386
|
+
try {
|
|
387
|
+
saveConfig(merged);
|
|
388
|
+
const applyResult = await applySavedConfigIfPossible(existing, merged);
|
|
389
|
+
jsonReply(res, 200, { ok: true, ...applyResult });
|
|
390
|
+
} catch (err) {
|
|
391
|
+
jsonReply(res, 500, { ok: false, saved: true, error: (err as Error).message });
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
394
|
|
|
395
395
|
function deepMerge(target: Record<string, unknown>, source: Record<string, unknown>): Record<string, unknown> {
|
|
396
396
|
const result = { ...target };
|
|
@@ -422,14 +422,14 @@ export function unflattenConfig(flat: Record<string, unknown>): Record<string, u
|
|
|
422
422
|
result.platforms = result.platforms || {};
|
|
423
423
|
(result.platforms as Record<string, unknown>).feishu = (result.platforms as Record<string, unknown>).feishu || {};
|
|
424
424
|
((result.platforms as Record<string, unknown>).feishu as Record<string, unknown>).platformType = val;
|
|
425
|
-
} else if (key === "CHATCCC_ILINK_ENABLED") {
|
|
426
|
-
result.platforms = result.platforms || {};
|
|
427
|
-
(result.platforms as Record<string, unknown>).ilink = (result.platforms as Record<string, unknown>).ilink || {};
|
|
428
|
-
((result.platforms as Record<string, unknown>).ilink as Record<string, unknown>).enabled = val === true || val === "true";
|
|
429
|
-
} else if (key === "CHATCCC_WEB_UI_OPEN_ON_START") {
|
|
430
|
-
result.webUi = result.webUi || {};
|
|
431
|
-
(result.webUi as Record<string, unknown>).openOnStart = val === true || val === "true";
|
|
432
|
-
} else if (key === "CHATCCC_CHROME_DEVTOOLS_ENABLED") {
|
|
425
|
+
} else if (key === "CHATCCC_ILINK_ENABLED") {
|
|
426
|
+
result.platforms = result.platforms || {};
|
|
427
|
+
(result.platforms as Record<string, unknown>).ilink = (result.platforms as Record<string, unknown>).ilink || {};
|
|
428
|
+
((result.platforms as Record<string, unknown>).ilink as Record<string, unknown>).enabled = val === true || val === "true";
|
|
429
|
+
} else if (key === "CHATCCC_WEB_UI_OPEN_ON_START") {
|
|
430
|
+
result.webUi = result.webUi || {};
|
|
431
|
+
(result.webUi as Record<string, unknown>).openOnStart = val === true || val === "true";
|
|
432
|
+
} else if (key === "CHATCCC_CHROME_DEVTOOLS_ENABLED") {
|
|
433
433
|
result.chromeDevtools = result.chromeDevtools || {};
|
|
434
434
|
(result.chromeDevtools as Record<string, unknown>).enabled = val === true || val === "true";
|
|
435
435
|
} else if (key === "CHATCCC_CHROME_DEVTOOLS_PORT") {
|
|
@@ -496,37 +496,40 @@ export function unflattenConfig(flat: Record<string, unknown>): Record<string, u
|
|
|
496
496
|
} else if (key === "CHATCCC_CODEX_ALTERNATIVE_MODEL") {
|
|
497
497
|
result.codex = result.codex || {};
|
|
498
498
|
(result.codex as Record<string, unknown>).alternativeModel = val;
|
|
499
|
-
} else if (key === "CHATCCC_CODEX_EFFORT") {
|
|
500
|
-
result.codex = result.codex || {};
|
|
501
|
-
(result.codex as Record<string, unknown>).effort = val;
|
|
502
|
-
} else if (key === "CHATCCC_CODEX_FAST_MODE") {
|
|
503
|
-
result.codex = result.codex || {};
|
|
504
|
-
(result.codex as Record<string, unknown>).fastMode = val === true || val === "true";
|
|
505
|
-
} else if (key === "CHATCCC_CODEX_ENABLED") {
|
|
499
|
+
} else if (key === "CHATCCC_CODEX_EFFORT") {
|
|
500
|
+
result.codex = result.codex || {};
|
|
501
|
+
(result.codex as Record<string, unknown>).effort = val;
|
|
502
|
+
} else if (key === "CHATCCC_CODEX_FAST_MODE") {
|
|
503
|
+
result.codex = result.codex || {};
|
|
504
|
+
(result.codex as Record<string, unknown>).fastMode = val === true || val === "true";
|
|
505
|
+
} else if (key === "CHATCCC_CODEX_ENABLED") {
|
|
506
506
|
result.codex = result.codex || {};
|
|
507
507
|
(result.codex as Record<string, unknown>).enabled = val === true || val === "true";
|
|
508
|
-
} else if (key === "CHATCCC_CODEX_DEFAULT_AGENT") {
|
|
509
|
-
result.codex = result.codex || {};
|
|
510
|
-
(result.codex as Record<string, unknown>).defaultAgent = val === true || val === "true";
|
|
511
|
-
} else if (key === "CHATCCC_CCC_API_KEY") {
|
|
512
|
-
result.ccc = result.ccc || {};
|
|
513
|
-
(result.ccc as Record<string, unknown>).DEEPSEEK_API_KEY = val;
|
|
514
|
-
} else if (key === "CHATCCC_CCC_BASE_URL") {
|
|
515
|
-
result.ccc = result.ccc || {};
|
|
516
|
-
(result.ccc as Record<string, unknown>).DEEPSEEK_BASE_URL = val;
|
|
517
|
-
} else if (key === "CHATCCC_CCC_MODEL") {
|
|
518
|
-
result.ccc = result.ccc || {};
|
|
519
|
-
(result.ccc as Record<string, unknown>).model = val;
|
|
520
|
-
} else if (key === "CHATCCC_CCC_ALTERNATIVE_MODEL") {
|
|
521
|
-
result.ccc = result.ccc || {};
|
|
522
|
-
(result.ccc as Record<string, unknown>).alternativeModel = val;
|
|
523
|
-
} else if (key === "
|
|
524
|
-
result.ccc = result.ccc || {};
|
|
525
|
-
(result.ccc as Record<string, unknown>).
|
|
526
|
-
} else if (key === "
|
|
527
|
-
result.ccc = result.ccc || {};
|
|
528
|
-
(result.ccc as Record<string, unknown>).
|
|
529
|
-
}
|
|
508
|
+
} else if (key === "CHATCCC_CODEX_DEFAULT_AGENT") {
|
|
509
|
+
result.codex = result.codex || {};
|
|
510
|
+
(result.codex as Record<string, unknown>).defaultAgent = val === true || val === "true";
|
|
511
|
+
} else if (key === "CHATCCC_CCC_API_KEY") {
|
|
512
|
+
result.ccc = result.ccc || {};
|
|
513
|
+
(result.ccc as Record<string, unknown>).DEEPSEEK_API_KEY = val;
|
|
514
|
+
} else if (key === "CHATCCC_CCC_BASE_URL") {
|
|
515
|
+
result.ccc = result.ccc || {};
|
|
516
|
+
(result.ccc as Record<string, unknown>).DEEPSEEK_BASE_URL = val;
|
|
517
|
+
} else if (key === "CHATCCC_CCC_MODEL") {
|
|
518
|
+
result.ccc = result.ccc || {};
|
|
519
|
+
(result.ccc as Record<string, unknown>).model = val;
|
|
520
|
+
} else if (key === "CHATCCC_CCC_ALTERNATIVE_MODEL") {
|
|
521
|
+
result.ccc = result.ccc || {};
|
|
522
|
+
(result.ccc as Record<string, unknown>).alternativeModel = val;
|
|
523
|
+
} else if (key === "CHATCCC_CCC_EFFORT") {
|
|
524
|
+
result.ccc = result.ccc || {};
|
|
525
|
+
(result.ccc as Record<string, unknown>).effort = val;
|
|
526
|
+
} else if (key === "CHATCCC_CCC_ENABLED") {
|
|
527
|
+
result.ccc = result.ccc || {};
|
|
528
|
+
(result.ccc as Record<string, unknown>).enabled = val === true || val === "true";
|
|
529
|
+
} else if (key === "CHATCCC_CCC_DEFAULT_AGENT") {
|
|
530
|
+
result.ccc = result.ccc || {};
|
|
531
|
+
(result.ccc as Record<string, unknown>).defaultAgent = val === true || val === "true";
|
|
532
|
+
}
|
|
530
533
|
}
|
|
531
534
|
return result;
|
|
532
535
|
}
|
|
@@ -562,18 +565,18 @@ async function handleStartService(_req: IncomingMessage, res: ServerResponse): P
|
|
|
562
565
|
return;
|
|
563
566
|
}
|
|
564
567
|
|
|
565
|
-
if (path === "reload") {
|
|
566
|
-
// service 已经在跑:只刷新进程内配置,让后续消息/新会话使用新值。
|
|
567
|
-
if (!reloadConfigHook) {
|
|
568
|
-
jsonReply(res, 200, { ok: true, pid: process.pid, mode: "saved" });
|
|
569
|
-
return;
|
|
570
|
-
}
|
|
571
|
-
try {
|
|
572
|
-
await reloadConfigHook();
|
|
573
|
-
jsonReply(res, 200, { ok: true, pid: process.pid, mode: "reload" });
|
|
574
|
-
} catch (err) {
|
|
575
|
-
jsonReply(res, 500, { ok: false, error: (err as Error).message });
|
|
576
|
-
}
|
|
568
|
+
if (path === "reload") {
|
|
569
|
+
// service 已经在跑:只刷新进程内配置,让后续消息/新会话使用新值。
|
|
570
|
+
if (!reloadConfigHook) {
|
|
571
|
+
jsonReply(res, 200, { ok: true, pid: process.pid, mode: "saved" });
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
try {
|
|
575
|
+
await reloadConfigHook();
|
|
576
|
+
jsonReply(res, 200, { ok: true, pid: process.pid, mode: "reload" });
|
|
577
|
+
} catch (err) {
|
|
578
|
+
jsonReply(res, 500, { ok: false, error: (err as Error).message });
|
|
579
|
+
}
|
|
577
580
|
return;
|
|
578
581
|
}
|
|
579
582
|
|
|
@@ -626,29 +629,29 @@ async function handleForgetIlink(_req: IncomingMessage, res: ServerResponse): Pr
|
|
|
626
629
|
}
|
|
627
630
|
}
|
|
628
631
|
|
|
629
|
-
// ---------------------------------------------------------------------------
|
|
630
|
-
// HTML page (embedded template)
|
|
631
|
-
// ---------------------------------------------------------------------------
|
|
632
|
-
|
|
633
|
-
/** Agent Team 的首个占位页面:按产品约定只展示标题,不提前放入功能内容。 */
|
|
634
|
-
export const AGENT_TEAM_PAGE_HTML = `<!DOCTYPE html>
|
|
635
|
-
<html lang="zh-CN">
|
|
636
|
-
<head>
|
|
637
|
-
<meta charset="UTF-8">
|
|
638
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
639
|
-
<title>Agent Team</title>
|
|
640
|
-
<style>
|
|
641
|
-
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
|
642
|
-
body{min-height:100vh;display:grid;place-items:center;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;background:radial-gradient(circle at 50% 20%,#312e81 0,#111827 42%,#020617 100%);color:#fff}
|
|
643
|
-
h1{font-size:clamp(40px,8vw,88px);font-weight:750;letter-spacing:-.04em;text-shadow:0 12px 42px rgba(129,140,248,.45)}
|
|
644
|
-
</style>
|
|
645
|
-
</head>
|
|
646
|
-
<body>
|
|
647
|
-
<h1>Agent Team</h1>
|
|
648
|
-
</body>
|
|
649
|
-
</html>`;
|
|
650
|
-
|
|
651
|
-
export const PAGE_HTML = `<!DOCTYPE html>
|
|
632
|
+
// ---------------------------------------------------------------------------
|
|
633
|
+
// HTML page (embedded template)
|
|
634
|
+
// ---------------------------------------------------------------------------
|
|
635
|
+
|
|
636
|
+
/** Agent Team 的首个占位页面:按产品约定只展示标题,不提前放入功能内容。 */
|
|
637
|
+
export const AGENT_TEAM_PAGE_HTML = `<!DOCTYPE html>
|
|
638
|
+
<html lang="zh-CN">
|
|
639
|
+
<head>
|
|
640
|
+
<meta charset="UTF-8">
|
|
641
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
642
|
+
<title>Agent Team</title>
|
|
643
|
+
<style>
|
|
644
|
+
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
|
645
|
+
body{min-height:100vh;display:grid;place-items:center;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;background:radial-gradient(circle at 50% 20%,#312e81 0,#111827 42%,#020617 100%);color:#fff}
|
|
646
|
+
h1{font-size:clamp(40px,8vw,88px);font-weight:750;letter-spacing:-.04em;text-shadow:0 12px 42px rgba(129,140,248,.45)}
|
|
647
|
+
</style>
|
|
648
|
+
</head>
|
|
649
|
+
<body>
|
|
650
|
+
<h1>Agent Team</h1>
|
|
651
|
+
</body>
|
|
652
|
+
</html>`;
|
|
653
|
+
|
|
654
|
+
export const PAGE_HTML = `<!DOCTYPE html>
|
|
652
655
|
<html lang="zh-CN">
|
|
653
656
|
<head>
|
|
654
657
|
<meta charset="UTF-8">
|
|
@@ -657,14 +660,14 @@ export const PAGE_HTML = `<!DOCTYPE html>
|
|
|
657
660
|
<style>
|
|
658
661
|
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
|
659
662
|
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;background:#f1f5f9;color:#1e293b;line-height:1.6}
|
|
660
|
-
header{background:#0f172a;color:#fff;padding:16px 24px;display:flex;align-items:center;justify-content:space-between}
|
|
661
|
-
header h1{font-size:20px;font-weight:600}
|
|
662
|
-
header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500}
|
|
663
|
-
.header-actions{display:flex;align-items:center;gap:12px}
|
|
664
|
-
.agent-team-entry{display:inline-flex;align-items:center;gap:8px;padding:9px 16px;border:1px solid rgba(255,255,255,.2);border-radius:999px;background:linear-gradient(135deg,#6366f1,#8b5cf6 55%,#d946ef);color:#fff;text-decoration:none;font-size:14px;font-weight:700;letter-spacing:.01em;box-shadow:0 8px 24px rgba(99,102,241,.38);transition:transform .18s ease,box-shadow .18s ease,filter .18s ease}
|
|
665
|
-
.agent-team-entry:hover{transform:translateY(-2px);box-shadow:0 12px 30px rgba(139,92,246,.52);filter:saturate(1.16)}
|
|
666
|
-
.agent-team-entry:focus-visible{outline:3px solid rgba(196,181,253,.65);outline-offset:3px}
|
|
667
|
-
.agent-team-entry .agent-team-icon{font-size:16px;line-height:1}
|
|
663
|
+
header{background:#0f172a;color:#fff;padding:16px 24px;display:flex;align-items:center;justify-content:space-between}
|
|
664
|
+
header h1{font-size:20px;font-weight:600}
|
|
665
|
+
header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500}
|
|
666
|
+
.header-actions{display:flex;align-items:center;gap:12px}
|
|
667
|
+
.agent-team-entry{display:inline-flex;align-items:center;gap:8px;padding:9px 16px;border:1px solid rgba(255,255,255,.2);border-radius:999px;background:linear-gradient(135deg,#6366f1,#8b5cf6 55%,#d946ef);color:#fff;text-decoration:none;font-size:14px;font-weight:700;letter-spacing:.01em;box-shadow:0 8px 24px rgba(99,102,241,.38);transition:transform .18s ease,box-shadow .18s ease,filter .18s ease}
|
|
668
|
+
.agent-team-entry:hover{transform:translateY(-2px);box-shadow:0 12px 30px rgba(139,92,246,.52);filter:saturate(1.16)}
|
|
669
|
+
.agent-team-entry:focus-visible{outline:3px solid rgba(196,181,253,.65);outline-offset:3px}
|
|
670
|
+
.agent-team-entry .agent-team-icon{font-size:16px;line-height:1}
|
|
668
671
|
.badge-running{background:#16a34a;color:#fff}
|
|
669
672
|
.badge-stopped{background:#94a3b8;color:#fff}
|
|
670
673
|
/* container 完全不限宽、不留左右内边距 —— step-2 是三列卡片需要尽量利用屏幕;
|
|
@@ -732,18 +735,18 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
|
|
|
732
735
|
.toast-error{background:#ef4444}
|
|
733
736
|
@keyframes slideIn{from{transform:translateX(100%);opacity:0}to{transform:translateX(0);opacity:1}}
|
|
734
737
|
.spinner{display:inline-block;width:14px;height:14px;border:2px solid rgba(255,255,255,.3);border-top-color:#fff;border-radius:50%;animation:spin .6s linear infinite}
|
|
735
|
-
@keyframes spin{to{transform:rotate(360deg)}}
|
|
736
|
-
@media(max-width:520px){header{padding:12px 14px}.header-actions{gap:8px}.agent-team-entry{padding:8px 11px;font-size:13px}header .badge{padding:4px 8px}}
|
|
737
|
-
</style>
|
|
738
|
+
@keyframes spin{to{transform:rotate(360deg)}}
|
|
739
|
+
@media(max-width:520px){header{padding:12px 14px}.header-actions{gap:8px}.agent-team-entry{padding:8px 11px;font-size:13px}header .badge{padding:4px 8px}}
|
|
740
|
+
</style>
|
|
738
741
|
</head>
|
|
739
742
|
<body>
|
|
740
|
-
<header>
|
|
741
|
-
<h1>ChatCCC</h1>
|
|
742
|
-
<div class="header-actions">
|
|
743
|
-
<a href="/agent-team" class="agent-team-entry"><span class="agent-team-icon" aria-hidden="true">✦</span>Agent Team <span aria-hidden="true">→</span></a>
|
|
744
|
-
<span id="header-badge" class="badge badge-stopped">未启动</span>
|
|
745
|
-
</div>
|
|
746
|
-
</header>
|
|
743
|
+
<header>
|
|
744
|
+
<h1>ChatCCC</h1>
|
|
745
|
+
<div class="header-actions">
|
|
746
|
+
<a href="/agent-team" class="agent-team-entry"><span class="agent-team-icon" aria-hidden="true">✦</span>Agent Team <span aria-hidden="true">→</span></a>
|
|
747
|
+
<span id="header-badge" class="badge badge-stopped">未启动</span>
|
|
748
|
+
</div>
|
|
749
|
+
</header>
|
|
747
750
|
<div class="container">
|
|
748
751
|
|
|
749
752
|
<!-- ===== Setup Wizard ===== -->
|
|
@@ -791,7 +794,7 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
|
|
|
791
794
|
</div>
|
|
792
795
|
</div>
|
|
793
796
|
|
|
794
|
-
<!-- 微信 iLink -->
|
|
797
|
+
<!-- 微信 iLink -->
|
|
795
798
|
<div style="background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:14px;margin-bottom:12px" id="platform-block-ilink">
|
|
796
799
|
<div style="display:flex;align-items:center;justify-content:space-between">
|
|
797
800
|
<div>
|
|
@@ -800,20 +803,20 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
|
|
|
800
803
|
</div>
|
|
801
804
|
<input type="checkbox" class="agent-toggle" id="platform-enable-ilink" checked onchange="onWizardPlatformToggle('ilink', this.checked)">
|
|
802
805
|
</div>
|
|
803
|
-
</div>
|
|
804
|
-
|
|
805
|
-
<!-- Web UI 启动行为 -->
|
|
806
|
-
<div style="background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:14px;margin-bottom:12px" id="web-ui-startup-block">
|
|
807
|
-
<div style="display:flex;align-items:center;justify-content:space-between;gap:16px">
|
|
808
|
-
<div>
|
|
809
|
-
<div style="font-weight:600;font-size:14px">启动时打开 Web UI</div>
|
|
810
|
-
<div style="font-size:12px;color:#64748b">直接运行 ChatCCC 时用系统默认浏览器打开管理页面;内部重启始终不打开</div>
|
|
811
|
-
</div>
|
|
812
|
-
<input type="checkbox" class="agent-toggle" id="field-CHATCCC_WEB_UI_OPEN_ON_START" checked>
|
|
813
|
-
</div>
|
|
814
|
-
</div>
|
|
815
|
-
|
|
816
|
-
<!-- Chrome CDP -->
|
|
806
|
+
</div>
|
|
807
|
+
|
|
808
|
+
<!-- Web UI 启动行为 -->
|
|
809
|
+
<div style="background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:14px;margin-bottom:12px" id="web-ui-startup-block">
|
|
810
|
+
<div style="display:flex;align-items:center;justify-content:space-between;gap:16px">
|
|
811
|
+
<div>
|
|
812
|
+
<div style="font-weight:600;font-size:14px">启动时打开 Web UI</div>
|
|
813
|
+
<div style="font-size:12px;color:#64748b">直接运行 ChatCCC 时用系统默认浏览器打开管理页面;内部重启始终不打开</div>
|
|
814
|
+
</div>
|
|
815
|
+
<input type="checkbox" class="agent-toggle" id="field-CHATCCC_WEB_UI_OPEN_ON_START" checked>
|
|
816
|
+
</div>
|
|
817
|
+
</div>
|
|
818
|
+
|
|
819
|
+
<!-- Chrome CDP -->
|
|
817
820
|
<div style="background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:14px;margin-bottom:12px" id="chrome-devtools-block">
|
|
818
821
|
<div style="display:flex;align-items:center;justify-content:space-between">
|
|
819
822
|
<div>
|
|
@@ -931,7 +934,7 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
|
|
|
931
934
|
</fieldset>
|
|
932
935
|
</div>
|
|
933
936
|
|
|
934
|
-
<!-- Codex 卡片 -->
|
|
937
|
+
<!-- Codex 卡片 -->
|
|
935
938
|
<div class="agent-card" id="agent-card-codex">
|
|
936
939
|
<div class="agent-card-header">
|
|
937
940
|
<input type="checkbox" class="agent-toggle" id="agent-enable-codex" onchange="onAgentToggle('codex', this.checked)">
|
|
@@ -957,54 +960,54 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
|
|
|
957
960
|
<label>备选模型(选填)</label>
|
|
958
961
|
<input type="text" id="field-CHATCCC_CODEX_ALTERNATIVE_MODEL" placeholder="加入 /model 列表,便于会话内切换">
|
|
959
962
|
</div>
|
|
960
|
-
<div class="form-group">
|
|
961
|
-
<label>努力程度 (Effort)</label>
|
|
962
|
-
<input type="text" id="field-CHATCCC_CODEX_EFFORT" placeholder="留空由 codex config.toml 决定">
|
|
963
|
-
</div>
|
|
964
|
-
<div class="form-group">
|
|
965
|
-
<label style="display:flex;align-items:center;gap:8px">
|
|
966
|
-
<input type="checkbox" id="field-CHATCCC_CODEX_FAST_MODE"> Fast 模式
|
|
967
|
-
</label>
|
|
968
|
-
</div>
|
|
969
|
-
<button class="btn btn-outline" onclick="validateCli('codex')" style="margin-bottom:12px">检测 Codex CLI</button>
|
|
963
|
+
<div class="form-group">
|
|
964
|
+
<label>努力程度 (Effort)</label>
|
|
965
|
+
<input type="text" id="field-CHATCCC_CODEX_EFFORT" placeholder="留空由 codex config.toml 决定">
|
|
966
|
+
</div>
|
|
967
|
+
<div class="form-group">
|
|
968
|
+
<label style="display:flex;align-items:center;gap:8px">
|
|
969
|
+
<input type="checkbox" id="field-CHATCCC_CODEX_FAST_MODE"> Fast 模式
|
|
970
|
+
</label>
|
|
971
|
+
</div>
|
|
972
|
+
<button class="btn btn-outline" onclick="validateCli('codex')" style="margin-bottom:12px">检测 Codex CLI</button>
|
|
970
973
|
<div id="codex-validate-result"></div>
|
|
971
974
|
</fieldset>
|
|
972
|
-
</div>
|
|
973
|
-
|
|
974
|
-
<!-- CCC Agent 卡片 -->
|
|
975
|
-
<div class="agent-card" id="agent-card-ccc">
|
|
976
|
-
<div class="agent-card-header">
|
|
977
|
-
<input type="checkbox" class="agent-toggle" id="agent-enable-ccc" onchange="onAgentToggle('ccc', this.checked)">
|
|
978
|
-
<div class="meta">
|
|
979
|
-
<div class="name">CCC Agent</div>
|
|
980
|
-
<div class="desc">ChatCCC 内置 Agent<br>使用 DeepSeek 兼容 API</div>
|
|
981
|
-
</div>
|
|
982
|
-
</div>
|
|
983
|
-
<label class="agent-default-row">
|
|
984
|
-
<input type="checkbox" id="agent-default-ccc" onchange="onDefaultAgentToggle('ccc', this.checked)">
|
|
985
|
-
设为默认 Agent
|
|
986
|
-
</label>
|
|
987
|
-
<fieldset class="agent-body" id="agent-body-ccc" disabled>
|
|
988
|
-
<div class="form-group">
|
|
989
|
-
<label>API Key</label>
|
|
990
|
-
<input type="password" id="field-CHATCCC_CCC_API_KEY" placeholder="DeepSeek 兼容 API Key">
|
|
991
|
-
</div>
|
|
992
|
-
<div class="form-group">
|
|
993
|
-
<label>Base URL</label>
|
|
994
|
-
<input type="text" id="field-CHATCCC_CCC_BASE_URL" placeholder="https://api.deepseek.com/v1">
|
|
995
|
-
</div>
|
|
996
|
-
<div class="form-group">
|
|
997
|
-
<label>模型</label>
|
|
998
|
-
<input type="text" id="field-CHATCCC_CCC_MODEL" placeholder="deepseek-v4-pro">
|
|
999
|
-
</div>
|
|
1000
|
-
<div class="form-group">
|
|
1001
|
-
<label>备选模型(选填)</label>
|
|
1002
|
-
<input type="text" id="field-CHATCCC_CCC_ALTERNATIVE_MODEL" placeholder="加入 /model 列表,便于会话内切换">
|
|
1003
|
-
</div>
|
|
1004
|
-
</fieldset>
|
|
1005
|
-
</div>
|
|
1006
|
-
|
|
1007
|
-
</div>
|
|
975
|
+
</div>
|
|
976
|
+
|
|
977
|
+
<!-- CCC Agent 卡片 -->
|
|
978
|
+
<div class="agent-card" id="agent-card-ccc">
|
|
979
|
+
<div class="agent-card-header">
|
|
980
|
+
<input type="checkbox" class="agent-toggle" id="agent-enable-ccc" onchange="onAgentToggle('ccc', this.checked)">
|
|
981
|
+
<div class="meta">
|
|
982
|
+
<div class="name">CCC Agent</div>
|
|
983
|
+
<div class="desc">ChatCCC 内置 Agent<br>使用 DeepSeek 兼容 API</div>
|
|
984
|
+
</div>
|
|
985
|
+
</div>
|
|
986
|
+
<label class="agent-default-row">
|
|
987
|
+
<input type="checkbox" id="agent-default-ccc" onchange="onDefaultAgentToggle('ccc', this.checked)">
|
|
988
|
+
设为默认 Agent
|
|
989
|
+
</label>
|
|
990
|
+
<fieldset class="agent-body" id="agent-body-ccc" disabled>
|
|
991
|
+
<div class="form-group">
|
|
992
|
+
<label>API Key</label>
|
|
993
|
+
<input type="password" id="field-CHATCCC_CCC_API_KEY" placeholder="DeepSeek 兼容 API Key">
|
|
994
|
+
</div>
|
|
995
|
+
<div class="form-group">
|
|
996
|
+
<label>Base URL</label>
|
|
997
|
+
<input type="text" id="field-CHATCCC_CCC_BASE_URL" placeholder="https://api.deepseek.com/v1">
|
|
998
|
+
</div>
|
|
999
|
+
<div class="form-group">
|
|
1000
|
+
<label>模型</label>
|
|
1001
|
+
<input type="text" id="field-CHATCCC_CCC_MODEL" placeholder="deepseek-v4-pro">
|
|
1002
|
+
</div>
|
|
1003
|
+
<div class="form-group">
|
|
1004
|
+
<label>备选模型(选填)</label>
|
|
1005
|
+
<input type="text" id="field-CHATCCC_CCC_ALTERNATIVE_MODEL" placeholder="加入 /model 列表,便于会话内切换">
|
|
1006
|
+
</div>
|
|
1007
|
+
</fieldset>
|
|
1008
|
+
</div>
|
|
1009
|
+
|
|
1010
|
+
</div>
|
|
1008
1011
|
|
|
1009
1012
|
<div class="btn-group" style="justify-content:space-between">
|
|
1010
1013
|
<button class="btn btn-outline" onclick="goStep(1)">返回</button>
|
|
@@ -1047,16 +1050,16 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
|
|
|
1047
1050
|
<span id="dash-platform-feishu-label">已启用</span>
|
|
1048
1051
|
</label>
|
|
1049
1052
|
</div>
|
|
1050
|
-
<div class="config-row"><span class="key">App ID</span><span class="val" id="cfg-APP_ID">-</span></div>
|
|
1051
|
-
<div class="config-row"><span class="key">App Secret</span><span class="val" id="cfg-APP_SECRET">-</span></div>
|
|
1052
|
-
<div class="config-row"><span class="key">平台类型</span><span class="val" id="cfg-FEISHU_PLATFORM_TYPE">-</span></div>
|
|
1053
|
-
<div class="hint" style="margin-top:6px;line-height:1.6">生效范围:飞书开关、App ID、App Secret 或平台类型变更需要重启 ChatCCC。</div>
|
|
1054
|
-
<button class="btn btn-outline" style="margin-top:8px" onclick="editSection('feishu')">编辑</button>
|
|
1053
|
+
<div class="config-row"><span class="key">App ID</span><span class="val" id="cfg-APP_ID">-</span></div>
|
|
1054
|
+
<div class="config-row"><span class="key">App Secret</span><span class="val" id="cfg-APP_SECRET">-</span></div>
|
|
1055
|
+
<div class="config-row"><span class="key">平台类型</span><span class="val" id="cfg-FEISHU_PLATFORM_TYPE">-</span></div>
|
|
1056
|
+
<div class="hint" style="margin-top:6px;line-height:1.6">生效范围:飞书开关、App ID、App Secret 或平台类型变更需要重启 ChatCCC。</div>
|
|
1057
|
+
<button class="btn btn-outline" style="margin-top:8px" onclick="editSection('feishu')">编辑</button>
|
|
1055
1058
|
</div>
|
|
1056
1059
|
</details>
|
|
1057
1060
|
|
|
1058
|
-
<details class="card config-section">
|
|
1059
|
-
<summary>微信 iLink</summary>
|
|
1061
|
+
<details class="card config-section">
|
|
1062
|
+
<summary>微信 iLink</summary>
|
|
1060
1063
|
<div class="section-detail">
|
|
1061
1064
|
<div class="config-row">
|
|
1062
1065
|
<span class="key">状态</span>
|
|
@@ -1065,30 +1068,30 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
|
|
|
1065
1068
|
<span id="dash-platform-ilink-label">未启用</span>
|
|
1066
1069
|
</label>
|
|
1067
1070
|
</div>
|
|
1068
|
-
<div id="ilink-forget-row" style="margin-top:6px;display:none">
|
|
1069
|
-
<button class="btn btn-outline" style="font-size:12px" onclick="forgetIlink()">忘记扫码</button>
|
|
1070
|
-
<span style="font-size:11px;color:#94a3b8;margin-left:8px">清除登录状态,重启后重新扫码</span>
|
|
1071
|
-
</div>
|
|
1072
|
-
<div class="hint" style="margin-top:6px;line-height:1.6">生效范围:微信 iLink 开关变更需要重启 ChatCCC。</div>
|
|
1073
|
-
</div>
|
|
1074
|
-
</details>
|
|
1075
|
-
|
|
1076
|
-
<details class="card config-section">
|
|
1077
|
-
<summary>Web UI</summary>
|
|
1078
|
-
<div class="section-detail">
|
|
1079
|
-
<div class="config-row"><span class="key">直接启动时自动打开</span><span class="val" id="cfg-WEB_UI_OPEN_ON_START">-</span></div>
|
|
1080
|
-
<div class="hint" style="margin-top:6px;line-height:1.6">生效范围:下次直接启动生效;内部重启始终不打开。Linux 无图形桌面时会跳过打开并输出 SSH 隧道提示。</div>
|
|
1081
|
-
<button class="btn btn-outline" style="margin-top:8px" onclick="editSection('webUi')">编辑</button>
|
|
1082
|
-
</div>
|
|
1083
|
-
</details>
|
|
1084
|
-
|
|
1085
|
-
<details class="card config-section">
|
|
1071
|
+
<div id="ilink-forget-row" style="margin-top:6px;display:none">
|
|
1072
|
+
<button class="btn btn-outline" style="font-size:12px" onclick="forgetIlink()">忘记扫码</button>
|
|
1073
|
+
<span style="font-size:11px;color:#94a3b8;margin-left:8px">清除登录状态,重启后重新扫码</span>
|
|
1074
|
+
</div>
|
|
1075
|
+
<div class="hint" style="margin-top:6px;line-height:1.6">生效范围:微信 iLink 开关变更需要重启 ChatCCC。</div>
|
|
1076
|
+
</div>
|
|
1077
|
+
</details>
|
|
1078
|
+
|
|
1079
|
+
<details class="card config-section">
|
|
1080
|
+
<summary>Web UI</summary>
|
|
1081
|
+
<div class="section-detail">
|
|
1082
|
+
<div class="config-row"><span class="key">直接启动时自动打开</span><span class="val" id="cfg-WEB_UI_OPEN_ON_START">-</span></div>
|
|
1083
|
+
<div class="hint" style="margin-top:6px;line-height:1.6">生效范围:下次直接启动生效;内部重启始终不打开。Linux 无图形桌面时会跳过打开并输出 SSH 隧道提示。</div>
|
|
1084
|
+
<button class="btn btn-outline" style="margin-top:8px" onclick="editSection('webUi')">编辑</button>
|
|
1085
|
+
</div>
|
|
1086
|
+
</details>
|
|
1087
|
+
|
|
1088
|
+
<details class="card config-section">
|
|
1086
1089
|
<summary>Chrome CDP(选填)</summary>
|
|
1087
1090
|
<div class="section-detail">
|
|
1088
1091
|
<div class="config-row"><span class="key">状态</span><span class="val" id="cfg-CHROME_DEVTOOLS_ENABLED">-</span></div>
|
|
1089
1092
|
<div class="config-row" id="cfg-CHROME_DEVTOOLS_PORT_ROW"><span class="key">CDP 端口</span><span class="val" id="cfg-CHROME_DEVTOOLS_PORT">-</span></div>
|
|
1090
1093
|
<div class="config-row" id="cfg-CHROME_DEVTOOLS_PATH_ROW"><span class="key">Chrome 路径</span><span class="val" id="cfg-CHROME_DEVTOOLS_PATH">-</span></div>
|
|
1091
|
-
<div class="hint" style="margin-top:6px;line-height:1.6">生效范围:保存后立即应用到 Chrome CDP 守护进程。依赖:本机 Google Chrome;ChatGPT 订阅到期查询需要在该 CDP Chrome 中登录 ChatGPT。</div>
|
|
1094
|
+
<div class="hint" style="margin-top:6px;line-height:1.6">生效范围:保存后立即应用到 Chrome CDP 守护进程。依赖:本机 Google Chrome;ChatGPT 订阅到期查询需要在该 CDP Chrome 中登录 ChatGPT。</div>
|
|
1092
1095
|
<button class="btn btn-outline" style="margin-top:8px" onclick="editSection('chromeDevtools')">编辑</button>
|
|
1093
1096
|
</div>
|
|
1094
1097
|
</details>
|
|
@@ -1101,10 +1104,10 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
|
|
|
1101
1104
|
<div class="config-row"><span class="key">Effort</span><span class="val" id="cfg-ANTHROPIC_EFFORT">-</span></div>
|
|
1102
1105
|
<div class="config-row"><span class="key">API Key</span><span class="val" id="cfg-ANTHROPIC_API_KEY">-</span></div>
|
|
1103
1106
|
<div class="config-row"><span class="key">Base URL</span><span class="val" id="cfg-ANTHROPIC_BASE_URL">-</span></div>
|
|
1104
|
-
<div class="config-row"><span class="key">Max Turns</span><span class="val" id="cfg-ANTHROPIC_MAX_TURN">-</span><span class="hint">(0=无限制)</span></div>
|
|
1105
|
-
<label class="agent-default-row" style="margin-top:10px"><input type="checkbox" id="dash-default-claude" onchange="setDashboardDefaultAgent('claude', this.checked)"> 设为默认 Agent</label>
|
|
1106
|
-
<div class="hint" style="margin-top:6px;line-height:1.6">生效范围:保存后下一条消息或下个新会话生效,当前生成不中断。</div>
|
|
1107
|
-
<button class="btn btn-outline" style="margin-top:8px" onclick="editSection('claude')">编辑</button>
|
|
1107
|
+
<div class="config-row"><span class="key">Max Turns</span><span class="val" id="cfg-ANTHROPIC_MAX_TURN">-</span><span class="hint">(0=无限制)</span></div>
|
|
1108
|
+
<label class="agent-default-row" style="margin-top:10px"><input type="checkbox" id="dash-default-claude" onchange="setDashboardDefaultAgent('claude', this.checked)"> 设为默认 Agent</label>
|
|
1109
|
+
<div class="hint" style="margin-top:6px;line-height:1.6">生效范围:保存后下一条消息或下个新会话生效,当前生成不中断。</div>
|
|
1110
|
+
<button class="btn btn-outline" style="margin-top:8px" onclick="editSection('claude')">编辑</button>
|
|
1108
1111
|
</div>
|
|
1109
1112
|
</details>
|
|
1110
1113
|
|
|
@@ -1115,39 +1118,39 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
|
|
|
1115
1118
|
<div class="config-row"><span class="key">模型</span><span class="val" id="cfg-CURSOR_MODEL">-</span></div>
|
|
1116
1119
|
<div class="config-row"><span class="key">备选模型</span><span class="val" id="cfg-CURSOR_ALTERNATIVE_MODEL">-</span></div>
|
|
1117
1120
|
<div class="config-row"><span class="key">头像电池电量</span><span class="val" id="cfg-CURSOR_AVATAR_BATTERY_MODE">-</span></div>
|
|
1118
|
-
<div class="config-row" id="cfg-CURSOR_ON_DEMAND_MONTHLY_BUDGET_ROW"><span class="key">每月On demand use预算</span><span class="val" id="cfg-CURSOR_ON_DEMAND_MONTHLY_BUDGET">-</span></div>
|
|
1119
|
-
<label class="agent-default-row" style="margin-top:10px"><input type="checkbox" id="dash-default-cursor" onchange="setDashboardDefaultAgent('cursor', this.checked)"> 设为默认 Agent</label>
|
|
1120
|
-
<div class="hint" style="margin-top:6px;line-height:1.6">生效范围:保存后下一条消息或下个新会话生效,当前生成不中断。</div>
|
|
1121
|
-
<button class="btn btn-outline" style="margin-top:8px" onclick="editSection('cursor')">编辑</button>
|
|
1121
|
+
<div class="config-row" id="cfg-CURSOR_ON_DEMAND_MONTHLY_BUDGET_ROW"><span class="key">每月On demand use预算</span><span class="val" id="cfg-CURSOR_ON_DEMAND_MONTHLY_BUDGET">-</span></div>
|
|
1122
|
+
<label class="agent-default-row" style="margin-top:10px"><input type="checkbox" id="dash-default-cursor" onchange="setDashboardDefaultAgent('cursor', this.checked)"> 设为默认 Agent</label>
|
|
1123
|
+
<div class="hint" style="margin-top:6px;line-height:1.6">生效范围:保存后下一条消息或下个新会话生效,当前生成不中断。</div>
|
|
1124
|
+
<button class="btn btn-outline" style="margin-top:8px" onclick="editSection('cursor')">编辑</button>
|
|
1122
1125
|
</div>
|
|
1123
1126
|
</details>
|
|
1124
1127
|
|
|
1125
|
-
<details class="card config-section" id="dash-codex">
|
|
1128
|
+
<details class="card config-section" id="dash-codex">
|
|
1126
1129
|
<summary>Codex Agent</summary>
|
|
1127
1130
|
<div class="section-detail">
|
|
1128
1131
|
<div class="config-row"><span class="key">CLI 路径</span><span class="val" id="cfg-CODEX_PATH">-</span></div>
|
|
1129
1132
|
<div class="config-row"><span class="key">模型</span><span class="val" id="cfg-CODEX_MODEL">-</span></div>
|
|
1130
|
-
<div class="config-row"><span class="key">备选模型</span><span class="val" id="cfg-CODEX_ALTERNATIVE_MODEL">-</span></div>
|
|
1131
|
-
<div class="config-row"><span class="key">Effort</span><span class="val" id="cfg-CODEX_EFFORT">-</span></div>
|
|
1132
|
-
<div class="config-row"><span class="key">Fast 模式</span><span class="val" id="cfg-CODEX_FAST_MODE">-</span></div>
|
|
1133
|
-
<label class="agent-default-row" style="margin-top:10px"><input type="checkbox" id="dash-default-codex" onchange="setDashboardDefaultAgent('codex', this.checked)"> 设为默认 Agent</label>
|
|
1134
|
-
<div class="hint" style="margin-top:6px;line-height:1.6">生效范围:保存后下一条消息或下个新会话生效,当前生成不中断。</div>
|
|
1135
|
-
<button class="btn btn-outline" style="margin-top:8px" onclick="editSection('codex')">编辑</button>
|
|
1133
|
+
<div class="config-row"><span class="key">备选模型</span><span class="val" id="cfg-CODEX_ALTERNATIVE_MODEL">-</span></div>
|
|
1134
|
+
<div class="config-row"><span class="key">Effort</span><span class="val" id="cfg-CODEX_EFFORT">-</span></div>
|
|
1135
|
+
<div class="config-row"><span class="key">Fast 模式</span><span class="val" id="cfg-CODEX_FAST_MODE">-</span></div>
|
|
1136
|
+
<label class="agent-default-row" style="margin-top:10px"><input type="checkbox" id="dash-default-codex" onchange="setDashboardDefaultAgent('codex', this.checked)"> 设为默认 Agent</label>
|
|
1137
|
+
<div class="hint" style="margin-top:6px;line-height:1.6">生效范围:保存后下一条消息或下个新会话生效,当前生成不中断。</div>
|
|
1138
|
+
<button class="btn btn-outline" style="margin-top:8px" onclick="editSection('codex')">编辑</button>
|
|
1139
|
+
</div>
|
|
1140
|
+
</details>
|
|
1141
|
+
|
|
1142
|
+
<details class="card config-section" id="dash-ccc">
|
|
1143
|
+
<summary>CCC Agent</summary>
|
|
1144
|
+
<div class="section-detail">
|
|
1145
|
+
<div class="config-row"><span class="key">API Key</span><span class="val" id="cfg-CCC_API_KEY">-</span></div>
|
|
1146
|
+
<div class="config-row"><span class="key">Base URL</span><span class="val" id="cfg-CCC_BASE_URL">-</span></div>
|
|
1147
|
+
<div class="config-row"><span class="key">模型</span><span class="val" id="cfg-CCC_MODEL">-</span></div>
|
|
1148
|
+
<div class="config-row"><span class="key">备选模型</span><span class="val" id="cfg-CCC_ALTERNATIVE_MODEL">-</span></div>
|
|
1149
|
+
<label class="agent-default-row" style="margin-top:10px"><input type="checkbox" id="dash-default-ccc" onchange="setDashboardDefaultAgent('ccc', this.checked)"> 设为默认 Agent</label>
|
|
1150
|
+
<div class="hint" style="margin-top:6px;line-height:1.6">备选模型仅加入 /model 人工切换列表;保存后下一条消息或下个新会话生效。</div>
|
|
1151
|
+
<button class="btn btn-outline" style="margin-top:8px" onclick="editSection('ccc')">编辑</button>
|
|
1136
1152
|
</div>
|
|
1137
|
-
</details>
|
|
1138
|
-
|
|
1139
|
-
<details class="card config-section" id="dash-ccc">
|
|
1140
|
-
<summary>CCC Agent</summary>
|
|
1141
|
-
<div class="section-detail">
|
|
1142
|
-
<div class="config-row"><span class="key">API Key</span><span class="val" id="cfg-CCC_API_KEY">-</span></div>
|
|
1143
|
-
<div class="config-row"><span class="key">Base URL</span><span class="val" id="cfg-CCC_BASE_URL">-</span></div>
|
|
1144
|
-
<div class="config-row"><span class="key">模型</span><span class="val" id="cfg-CCC_MODEL">-</span></div>
|
|
1145
|
-
<div class="config-row"><span class="key">备选模型</span><span class="val" id="cfg-CCC_ALTERNATIVE_MODEL">-</span></div>
|
|
1146
|
-
<label class="agent-default-row" style="margin-top:10px"><input type="checkbox" id="dash-default-ccc" onchange="setDashboardDefaultAgent('ccc', this.checked)"> 设为默认 Agent</label>
|
|
1147
|
-
<div class="hint" style="margin-top:6px;line-height:1.6">备选模型仅加入 /model 人工切换列表;保存后下一条消息或下个新会话生效。</div>
|
|
1148
|
-
<button class="btn btn-outline" style="margin-top:8px" onclick="editSection('ccc')">编辑</button>
|
|
1149
|
-
</div>
|
|
1150
|
-
</details>
|
|
1153
|
+
</details>
|
|
1151
1154
|
|
|
1152
1155
|
<div class="card" id="dash-no-agent-hint" style="text-align:center;color:#94a3b8;display:none">
|
|
1153
1156
|
未启用任何 AI Agent。点击下方按钮重新运行配置向导启用。
|
|
@@ -1161,8 +1164,8 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
|
|
|
1161
1164
|
<!-- Edit Modal -->
|
|
1162
1165
|
<div id="edit-modal" class="card hidden" style="position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:90%;max-width:480px;z-index:200;max-height:80vh;overflow-y:auto">
|
|
1163
1166
|
<h2 id="edit-modal-title">编辑配置</h2>
|
|
1164
|
-
<div id="edit-modal-effect" class="hint" style="margin-bottom:12px;line-height:1.6"></div>
|
|
1165
|
-
<div id="edit-modal-fields"></div>
|
|
1167
|
+
<div id="edit-modal-effect" class="hint" style="margin-bottom:12px;line-height:1.6"></div>
|
|
1168
|
+
<div id="edit-modal-fields"></div>
|
|
1166
1169
|
<div class="btn-group" style="justify-content:flex-end;margin-top:16px">
|
|
1167
1170
|
<button class="btn btn-outline" onclick="closeEditModal()">取消</button>
|
|
1168
1171
|
<button class="btn btn-primary" onclick="saveEdit()">保存</button>
|
|
@@ -1175,8 +1178,8 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
|
|
|
1175
1178
|
<script>
|
|
1176
1179
|
let state = {
|
|
1177
1180
|
view: 'loading',
|
|
1178
|
-
// 四个 Agent 各自的启用开关;初始全 false,renderStep2() 会按已存在 config 自动打开
|
|
1179
|
-
agentsEnabled: { claude: false, cursor: false, codex: false, ccc: false },
|
|
1181
|
+
// 四个 Agent 各自的启用开关;初始全 false,renderStep2() 会按已存在 config 自动打开
|
|
1182
|
+
agentsEnabled: { claude: false, cursor: false, codex: false, ccc: false },
|
|
1180
1183
|
defaultAgent: 'claude',
|
|
1181
1184
|
wizardStep: 1,
|
|
1182
1185
|
config: {},
|
|
@@ -1192,39 +1195,39 @@ var step2InputBound = false;
|
|
|
1192
1195
|
const AGENT_FIELDS = {
|
|
1193
1196
|
claude: ['CHATCCC_ANTHROPIC_MODEL','CHATCCC_ANTHROPIC_SUBAGENT_MODEL','CHATCCC_ANTHROPIC_EFFORT','CHATCCC_ANTHROPIC_API_KEY','CHATCCC_ANTHROPIC_BASE_URL','CHATCCC_ANTHROPIC_MAX_TURN'],
|
|
1194
1197
|
cursor: ['CHATCCC_CURSOR_PATH','CHATCCC_CURSOR_MODEL','CHATCCC_CURSOR_ALTERNATIVE_MODEL','CHATCCC_CURSOR_AVATAR_BATTERY_MODE','CHATCCC_CURSOR_ON_DEMAND_MONTHLY_BUDGET'],
|
|
1195
|
-
codex: ['CHATCCC_CODEX_PATH','CHATCCC_CODEX_MODEL','CHATCCC_CODEX_ALTERNATIVE_MODEL','CHATCCC_CODEX_EFFORT','CHATCCC_CODEX_FAST_MODE'],
|
|
1196
|
-
ccc: ['CHATCCC_CCC_API_KEY','CHATCCC_CCC_BASE_URL','CHATCCC_CCC_MODEL','CHATCCC_CCC_ALTERNATIVE_MODEL']
|
|
1198
|
+
codex: ['CHATCCC_CODEX_PATH','CHATCCC_CODEX_MODEL','CHATCCC_CODEX_ALTERNATIVE_MODEL','CHATCCC_CODEX_EFFORT','CHATCCC_CODEX_FAST_MODE'],
|
|
1199
|
+
ccc: ['CHATCCC_CCC_API_KEY','CHATCCC_CCC_BASE_URL','CHATCCC_CCC_MODEL','CHATCCC_CCC_ALTERNATIVE_MODEL']
|
|
1197
1200
|
};
|
|
1198
|
-
const FEISHU_FIELDS = ['CHATCCC_APP_ID','CHATCCC_APP_SECRET'];
|
|
1199
|
-
const WEB_UI_FIELDS = ['CHATCCC_WEB_UI_OPEN_ON_START'];
|
|
1200
|
-
const CHROME_DEVTOOLS_FIELDS = ['CHATCCC_CHROME_DEVTOOLS_ENABLED','CHATCCC_CHROME_DEVTOOLS_PORT','CHATCCC_CHROME_DEVTOOLS_PATH'];
|
|
1201
|
+
const FEISHU_FIELDS = ['CHATCCC_APP_ID','CHATCCC_APP_SECRET'];
|
|
1202
|
+
const WEB_UI_FIELDS = ['CHATCCC_WEB_UI_OPEN_ON_START'];
|
|
1203
|
+
const CHROME_DEVTOOLS_FIELDS = ['CHATCCC_CHROME_DEVTOOLS_ENABLED','CHATCCC_CHROME_DEVTOOLS_PORT','CHATCCC_CHROME_DEVTOOLS_PATH'];
|
|
1201
1204
|
|
|
1202
1205
|
function cursorBatteryModeLabel(value) {
|
|
1203
1206
|
return value === 'onDemandUse' ? 'On demand use 金额' : 'API 使用比例';
|
|
1204
1207
|
}
|
|
1205
1208
|
|
|
1206
|
-
function configEffectHint(section) {
|
|
1207
|
-
if (section === 'feishu') return '生效范围:App ID、App Secret、平台类型或飞书开关变更需要重启 ChatCCC;其它未变更项仅保存。';
|
|
1208
|
-
if (section === 'webUi') return '生效范围:下次直接启动生效;内部重启始终不打开。';
|
|
1209
|
-
if (section === 'chromeDevtools') return '生效范围:保存后立即应用到 Chrome CDP 守护进程。';
|
|
1210
|
-
if (section === 'claude') return '生效范围:保存后下一条消息或下个新会话生效,当前生成不中断。';
|
|
1211
|
-
if (section === 'cursor') return '生效范围:保存后下一条消息或下个新会话生效,当前生成不中断。';
|
|
1212
|
-
if (section === 'codex') return '生效范围:保存后下一条消息或下个新会话生效,当前生成不中断。';
|
|
1213
|
-
return '生效范围:保存后按配置类型应用。';
|
|
1214
|
-
}
|
|
1215
|
-
|
|
1216
|
-
function toastConfigApplyResult(result, savedText) {
|
|
1217
|
-
var text = savedText || '配置已保存';
|
|
1218
|
-
if (result && result.mode === 'reload') {
|
|
1219
|
-
toast(text + ',后续消息/新会话已生效。');
|
|
1220
|
-
} else if (result && result.mode === 'restart-required') {
|
|
1221
|
-
toast(text + ',需重启服务生效。');
|
|
1222
|
-
} else {
|
|
1223
|
-
toast(text);
|
|
1224
|
-
}
|
|
1225
|
-
}
|
|
1226
|
-
|
|
1227
|
-
function onCursorBatteryModeChange(prefix, value) {
|
|
1209
|
+
function configEffectHint(section) {
|
|
1210
|
+
if (section === 'feishu') return '生效范围:App ID、App Secret、平台类型或飞书开关变更需要重启 ChatCCC;其它未变更项仅保存。';
|
|
1211
|
+
if (section === 'webUi') return '生效范围:下次直接启动生效;内部重启始终不打开。';
|
|
1212
|
+
if (section === 'chromeDevtools') return '生效范围:保存后立即应用到 Chrome CDP 守护进程。';
|
|
1213
|
+
if (section === 'claude') return '生效范围:保存后下一条消息或下个新会话生效,当前生成不中断。';
|
|
1214
|
+
if (section === 'cursor') return '生效范围:保存后下一条消息或下个新会话生效,当前生成不中断。';
|
|
1215
|
+
if (section === 'codex') return '生效范围:保存后下一条消息或下个新会话生效,当前生成不中断。';
|
|
1216
|
+
return '生效范围:保存后按配置类型应用。';
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
function toastConfigApplyResult(result, savedText) {
|
|
1220
|
+
var text = savedText || '配置已保存';
|
|
1221
|
+
if (result && result.mode === 'reload') {
|
|
1222
|
+
toast(text + ',后续消息/新会话已生效。');
|
|
1223
|
+
} else if (result && result.mode === 'restart-required') {
|
|
1224
|
+
toast(text + ',需重启服务生效。');
|
|
1225
|
+
} else {
|
|
1226
|
+
toast(text);
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
function onCursorBatteryModeChange(prefix, value) {
|
|
1228
1231
|
var rowId = prefix === 'edit-' ? 'edit-cursor-on-demand-budget-row' : 'field-cursor-on-demand-budget-row';
|
|
1229
1232
|
var row = document.getElementById(rowId);
|
|
1230
1233
|
if (row) row.style.display = value === 'onDemandUse' ? '' : 'none';
|
|
@@ -1258,10 +1261,10 @@ async function onPlatformToggle(platform, enabled) {
|
|
|
1258
1261
|
if (result.ok) {
|
|
1259
1262
|
state.config.platforms = state.config.platforms || {};
|
|
1260
1263
|
state.config.platforms[platform] = state.config.platforms[platform] || {};
|
|
1261
|
-
state.config.platforms[platform].enabled = enabled;
|
|
1262
|
-
var label = document.getElementById('dash-platform-' + platform + '-label');
|
|
1263
|
-
if (label) label.textContent = enabled ? '已启用' : '未启用';
|
|
1264
|
-
toastConfigApplyResult(result, (platform === 'feishu' ? '飞书' : '微信 iLink') + (enabled ? ' 已启用' : ' 已禁用'));
|
|
1264
|
+
state.config.platforms[platform].enabled = enabled;
|
|
1265
|
+
var label = document.getElementById('dash-platform-' + platform + '-label');
|
|
1266
|
+
if (label) label.textContent = enabled ? '已启用' : '未启用';
|
|
1267
|
+
toastConfigApplyResult(result, (platform === 'feishu' ? '飞书' : '微信 iLink') + (enabled ? ' 已启用' : ' 已禁用'));
|
|
1265
1268
|
} else {
|
|
1266
1269
|
toast('保存失败: ' + (result.error || '未知错误'), 'error');
|
|
1267
1270
|
// 还原 toggle
|
|
@@ -1298,25 +1301,25 @@ function onAgentToggle(agent, enabled) {
|
|
|
1298
1301
|
function firstEnabledAgent() {
|
|
1299
1302
|
if (state.agentsEnabled.claude) return 'claude';
|
|
1300
1303
|
if (state.agentsEnabled.cursor) return 'cursor';
|
|
1301
|
-
if (state.agentsEnabled.codex) return 'codex';
|
|
1302
|
-
if (state.agentsEnabled.ccc) return 'ccc';
|
|
1304
|
+
if (state.agentsEnabled.codex) return 'codex';
|
|
1305
|
+
if (state.agentsEnabled.ccc) return 'ccc';
|
|
1303
1306
|
return null;
|
|
1304
1307
|
}
|
|
1305
1308
|
|
|
1306
|
-
function resolveDefaultAgentFromConfig(c, claudeOn, cursorOn, codexOn, cccOn) {
|
|
1309
|
+
function resolveDefaultAgentFromConfig(c, claudeOn, cursorOn, codexOn, cccOn) {
|
|
1307
1310
|
if (claudeOn && c.claude && c.claude.defaultAgent === true) return 'claude';
|
|
1308
1311
|
if (cursorOn && c.cursor && c.cursor.defaultAgent === true) return 'cursor';
|
|
1309
|
-
if (codexOn && c.codex && c.codex.defaultAgent === true) return 'codex';
|
|
1310
|
-
if (cccOn && c.ccc && c.ccc.defaultAgent === true) return 'ccc';
|
|
1312
|
+
if (codexOn && c.codex && c.codex.defaultAgent === true) return 'codex';
|
|
1313
|
+
if (cccOn && c.ccc && c.ccc.defaultAgent === true) return 'ccc';
|
|
1311
1314
|
if (claudeOn) return 'claude';
|
|
1312
1315
|
if (cursorOn) return 'cursor';
|
|
1313
|
-
if (codexOn) return 'codex';
|
|
1314
|
-
if (cccOn) return 'ccc';
|
|
1316
|
+
if (codexOn) return 'codex';
|
|
1317
|
+
if (cccOn) return 'ccc';
|
|
1315
1318
|
return 'claude';
|
|
1316
1319
|
}
|
|
1317
1320
|
|
|
1318
1321
|
function updateDefaultAgentToggles() {
|
|
1319
|
-
['claude','cursor','codex','ccc'].forEach(function(agent){
|
|
1322
|
+
['claude','cursor','codex','ccc'].forEach(function(agent){
|
|
1320
1323
|
var el = document.getElementById('agent-default-' + agent);
|
|
1321
1324
|
if (el) {
|
|
1322
1325
|
el.checked = state.defaultAgent === agent;
|
|
@@ -1347,8 +1350,8 @@ function updateStep2NextBtn() {
|
|
|
1347
1350
|
var validCount = 0;
|
|
1348
1351
|
if (state.agentsEnabled.claude) validCount++;
|
|
1349
1352
|
if (state.agentsEnabled.cursor) validCount++;
|
|
1350
|
-
if (state.agentsEnabled.codex) validCount++;
|
|
1351
|
-
if (state.agentsEnabled.ccc) validCount++;
|
|
1353
|
+
if (state.agentsEnabled.codex) validCount++;
|
|
1354
|
+
if (state.agentsEnabled.ccc) validCount++;
|
|
1352
1355
|
if (validCount > 0 && !state.defaultAgent) {
|
|
1353
1356
|
state.defaultAgent = firstEnabledAgent();
|
|
1354
1357
|
updateDefaultAgentToggles();
|
|
@@ -1459,11 +1462,11 @@ function renderStep1() {
|
|
|
1459
1462
|
// 飞书凭证字段初始显隐
|
|
1460
1463
|
var credFields = document.getElementById('feishu-cred-fields');
|
|
1461
1464
|
if (credFields) credFields.style.display = feishuEnabled ? '' : 'none';
|
|
1462
|
-
var ilToggle = document.getElementById('platform-enable-ilink');
|
|
1463
|
-
if (ilToggle) ilToggle.checked = ilinkEnabled;
|
|
1464
|
-
var webUiOpenOnStart = document.getElementById('field-CHATCCC_WEB_UI_OPEN_ON_START');
|
|
1465
|
-
if (webUiOpenOnStart) webUiOpenOnStart.checked = !c.webUi || c.webUi.openOnStart !== false;
|
|
1466
|
-
var cd = c.chromeDevtools || {};
|
|
1465
|
+
var ilToggle = document.getElementById('platform-enable-ilink');
|
|
1466
|
+
if (ilToggle) ilToggle.checked = ilinkEnabled;
|
|
1467
|
+
var webUiOpenOnStart = document.getElementById('field-CHATCCC_WEB_UI_OPEN_ON_START');
|
|
1468
|
+
if (webUiOpenOnStart) webUiOpenOnStart.checked = !c.webUi || c.webUi.openOnStart !== false;
|
|
1469
|
+
var cd = c.chromeDevtools || {};
|
|
1467
1470
|
var cdpEnabled = document.getElementById('field-CHATCCC_CHROME_DEVTOOLS_ENABLED');
|
|
1468
1471
|
if (cdpEnabled) cdpEnabled.checked = cd.enabled === true;
|
|
1469
1472
|
prefillNested('field-CHATCCC_CHROME_DEVTOOLS_PORT', cd.port || 15166);
|
|
@@ -1488,9 +1491,9 @@ function isAgentEnabled(node, keys) {
|
|
|
1488
1491
|
|
|
1489
1492
|
var CLAUDE_FALLBACK_KEYS = ['model','subagentModel','effort','maxTurn'];
|
|
1490
1493
|
var CURSOR_FALLBACK_KEYS = ['path','command','model','alternativeModel'];
|
|
1491
|
-
var CODEX_FALLBACK_KEYS = ['path','command','model','alternativeModel','effort','fastMode'];
|
|
1492
|
-
// 旧版 CCC 配置没有 enabled;只有 API Key 才表示用户实际完成了配置。
|
|
1493
|
-
var CCC_FALLBACK_KEYS = ['DEEPSEEK_API_KEY'];
|
|
1494
|
+
var CODEX_FALLBACK_KEYS = ['path','command','model','alternativeModel','effort','fastMode'];
|
|
1495
|
+
// 旧版 CCC 配置没有 enabled;只有 API Key 才表示用户实际完成了配置。
|
|
1496
|
+
var CCC_FALLBACK_KEYS = ['DEEPSEEK_API_KEY'];
|
|
1494
1497
|
|
|
1495
1498
|
function renderStep2() {
|
|
1496
1499
|
var c = state.config || {};
|
|
@@ -1515,35 +1518,35 @@ function renderStep2() {
|
|
|
1515
1518
|
var cursorBatteryModeEl = document.getElementById('field-CHATCCC_CURSOR_AVATAR_BATTERY_MODE');
|
|
1516
1519
|
if (cursorBatteryModeEl && !cursorBatteryModeEl.value) cursorBatteryModeEl.value = 'apiPercent';
|
|
1517
1520
|
onCursorBatteryModeChange('field-', cursorBatteryModeEl ? cursorBatteryModeEl.value : 'apiPercent');
|
|
1518
|
-
if (c.codex) {
|
|
1519
|
-
prefillNested('field-CHATCCC_CODEX_PATH', c.codex.path || c.codex.command);
|
|
1520
|
-
prefillNested('field-CHATCCC_CODEX_MODEL', c.codex.model);
|
|
1521
|
-
prefillNested('field-CHATCCC_CODEX_ALTERNATIVE_MODEL', c.codex.alternativeModel);
|
|
1522
|
-
prefillNested('field-CHATCCC_CODEX_EFFORT', c.codex.effort);
|
|
1523
|
-
}
|
|
1524
|
-
var codexFastModeEl = document.getElementById('field-CHATCCC_CODEX_FAST_MODE');
|
|
1525
|
-
if (codexFastModeEl) codexFastModeEl.checked = !!(c.codex && c.codex.fastMode === true);
|
|
1526
|
-
if (c.ccc) {
|
|
1527
|
-
prefillNested('field-CHATCCC_CCC_API_KEY', c.ccc.DEEPSEEK_API_KEY);
|
|
1528
|
-
prefillNested('field-CHATCCC_CCC_BASE_URL', c.ccc.DEEPSEEK_BASE_URL);
|
|
1529
|
-
prefillNested('field-CHATCCC_CCC_MODEL', c.ccc.model);
|
|
1530
|
-
prefillNested('field-CHATCCC_CCC_ALTERNATIVE_MODEL', c.ccc.alternativeModel);
|
|
1531
|
-
}
|
|
1521
|
+
if (c.codex) {
|
|
1522
|
+
prefillNested('field-CHATCCC_CODEX_PATH', c.codex.path || c.codex.command);
|
|
1523
|
+
prefillNested('field-CHATCCC_CODEX_MODEL', c.codex.model);
|
|
1524
|
+
prefillNested('field-CHATCCC_CODEX_ALTERNATIVE_MODEL', c.codex.alternativeModel);
|
|
1525
|
+
prefillNested('field-CHATCCC_CODEX_EFFORT', c.codex.effort);
|
|
1526
|
+
}
|
|
1527
|
+
var codexFastModeEl = document.getElementById('field-CHATCCC_CODEX_FAST_MODE');
|
|
1528
|
+
if (codexFastModeEl) codexFastModeEl.checked = !!(c.codex && c.codex.fastMode === true);
|
|
1529
|
+
if (c.ccc) {
|
|
1530
|
+
prefillNested('field-CHATCCC_CCC_API_KEY', c.ccc.DEEPSEEK_API_KEY);
|
|
1531
|
+
prefillNested('field-CHATCCC_CCC_BASE_URL', c.ccc.DEEPSEEK_BASE_URL);
|
|
1532
|
+
prefillNested('field-CHATCCC_CCC_MODEL', c.ccc.model);
|
|
1533
|
+
prefillNested('field-CHATCCC_CCC_ALTERNATIVE_MODEL', c.ccc.alternativeModel);
|
|
1534
|
+
}
|
|
1532
1535
|
|
|
1533
1536
|
// 按已有 config 决定每个 Agent 默认是否开启:优先 enabled 字段,缺省时按"任一字段非空"
|
|
1534
1537
|
var claudeOn = isAgentEnabled(c.claude, CLAUDE_FALLBACK_KEYS);
|
|
1535
1538
|
var cursorOn = isAgentEnabled(c.cursor, CURSOR_FALLBACK_KEYS);
|
|
1536
|
-
var codexOn = isAgentEnabled(c.codex, CODEX_FALLBACK_KEYS);
|
|
1537
|
-
var cccOn = isAgentEnabled(c.ccc, CCC_FALLBACK_KEYS);
|
|
1538
|
-
state.defaultAgent = resolveDefaultAgentFromConfig(c, claudeOn, cursorOn, codexOn, cccOn);
|
|
1539
|
+
var codexOn = isAgentEnabled(c.codex, CODEX_FALLBACK_KEYS);
|
|
1540
|
+
var cccOn = isAgentEnabled(c.ccc, CCC_FALLBACK_KEYS);
|
|
1541
|
+
state.defaultAgent = resolveDefaultAgentFromConfig(c, claudeOn, cursorOn, codexOn, cccOn);
|
|
1539
1542
|
document.getElementById('agent-enable-claude').checked = claudeOn;
|
|
1540
1543
|
document.getElementById('agent-enable-cursor').checked = cursorOn;
|
|
1541
|
-
document.getElementById('agent-enable-codex').checked = codexOn;
|
|
1542
|
-
document.getElementById('agent-enable-ccc').checked = cccOn;
|
|
1544
|
+
document.getElementById('agent-enable-codex').checked = codexOn;
|
|
1545
|
+
document.getElementById('agent-enable-ccc').checked = cccOn;
|
|
1543
1546
|
onAgentToggle('claude', claudeOn);
|
|
1544
1547
|
onAgentToggle('cursor', cursorOn);
|
|
1545
|
-
onAgentToggle('codex', codexOn);
|
|
1546
|
-
onAgentToggle('ccc', cccOn);
|
|
1548
|
+
onAgentToggle('codex', codexOn);
|
|
1549
|
+
onAgentToggle('ccc', cccOn);
|
|
1547
1550
|
updateDefaultAgentToggles();
|
|
1548
1551
|
|
|
1549
1552
|
// Cursor path placeholder/hint:把已探测到的路径显示为占位
|
|
@@ -1567,7 +1570,7 @@ function renderStep2() {
|
|
|
1567
1570
|
* 收集"待落地到 config.json 的扁平 vars"。
|
|
1568
1571
|
*
|
|
1569
1572
|
* - 飞书字段始终收集
|
|
1570
|
-
* - 四个 Agent 的 enabled 状态都显式下发,让 config.json 持久化用户的最新开关偏好
|
|
1573
|
+
* - 四个 Agent 的 enabled 状态都显式下发,让 config.json 持久化用户的最新开关偏好
|
|
1571
1574
|
* - Agent 字段仅在该 Agent 开关启用时收集;未启用的 Agent 不下发其它字段,
|
|
1572
1575
|
* 服务端 deepMerge 会保留 config.json 中已有值(避免关闭开关时误清空旧配置)
|
|
1573
1576
|
*/
|
|
@@ -1580,11 +1583,11 @@ function collectAllFields() {
|
|
|
1580
1583
|
// 平台类型(下拉选择框,始终发送以保持与服务端同步)
|
|
1581
1584
|
var ptEl = document.getElementById('field-CHATCCC_FEISHU_PLATFORM_TYPE');
|
|
1582
1585
|
if (ptEl && ptEl.value.trim()) vars['CHATCCC_FEISHU_PLATFORM_TYPE'] = ptEl.value.trim();
|
|
1583
|
-
vars.CHATCCC_FEISHU_ENABLED = !!state.platformsEnabled.feishu;
|
|
1584
|
-
vars.CHATCCC_ILINK_ENABLED = !!state.platformsEnabled.ilink;
|
|
1585
|
-
var webUiOpenOnStartEl = document.getElementById('field-CHATCCC_WEB_UI_OPEN_ON_START');
|
|
1586
|
-
vars.CHATCCC_WEB_UI_OPEN_ON_START = !webUiOpenOnStartEl || !!webUiOpenOnStartEl.checked;
|
|
1587
|
-
var cdpEnabledEl = document.getElementById('field-CHATCCC_CHROME_DEVTOOLS_ENABLED');
|
|
1586
|
+
vars.CHATCCC_FEISHU_ENABLED = !!state.platformsEnabled.feishu;
|
|
1587
|
+
vars.CHATCCC_ILINK_ENABLED = !!state.platformsEnabled.ilink;
|
|
1588
|
+
var webUiOpenOnStartEl = document.getElementById('field-CHATCCC_WEB_UI_OPEN_ON_START');
|
|
1589
|
+
vars.CHATCCC_WEB_UI_OPEN_ON_START = !webUiOpenOnStartEl || !!webUiOpenOnStartEl.checked;
|
|
1590
|
+
var cdpEnabledEl = document.getElementById('field-CHATCCC_CHROME_DEVTOOLS_ENABLED');
|
|
1588
1591
|
vars.CHATCCC_CHROME_DEVTOOLS_ENABLED = !!(cdpEnabledEl && cdpEnabledEl.checked);
|
|
1589
1592
|
var cdpPortEl = document.getElementById('field-CHATCCC_CHROME_DEVTOOLS_PORT');
|
|
1590
1593
|
vars.CHATCCC_CHROME_DEVTOOLS_PORT = (cdpPortEl && cdpPortEl.value.trim()) ? cdpPortEl.value.trim() : '15166';
|
|
@@ -1592,15 +1595,15 @@ function collectAllFields() {
|
|
|
1592
1595
|
if (cdpPathEl && cdpPathEl.value.trim()) vars.CHATCCC_CHROME_DEVTOOLS_PATH = cdpPathEl.value.trim();
|
|
1593
1596
|
vars.CHATCCC_CLAUDE_ENABLED = !!state.agentsEnabled.claude;
|
|
1594
1597
|
vars.CHATCCC_CURSOR_ENABLED = !!state.agentsEnabled.cursor;
|
|
1595
|
-
vars.CHATCCC_CODEX_ENABLED = !!state.agentsEnabled.codex;
|
|
1596
|
-
vars.CHATCCC_CCC_ENABLED = !!state.agentsEnabled.ccc;
|
|
1598
|
+
vars.CHATCCC_CODEX_ENABLED = !!state.agentsEnabled.codex;
|
|
1599
|
+
vars.CHATCCC_CCC_ENABLED = !!state.agentsEnabled.ccc;
|
|
1597
1600
|
if (!state.defaultAgent || !state.agentsEnabled[state.defaultAgent]) {
|
|
1598
1601
|
state.defaultAgent = firstEnabledAgent();
|
|
1599
1602
|
}
|
|
1600
1603
|
vars.CHATCCC_CLAUDE_DEFAULT_AGENT = state.defaultAgent === 'claude';
|
|
1601
1604
|
vars.CHATCCC_CURSOR_DEFAULT_AGENT = state.defaultAgent === 'cursor';
|
|
1602
|
-
vars.CHATCCC_CODEX_DEFAULT_AGENT = state.defaultAgent === 'codex';
|
|
1603
|
-
vars.CHATCCC_CCC_DEFAULT_AGENT = state.defaultAgent === 'ccc';
|
|
1605
|
+
vars.CHATCCC_CODEX_DEFAULT_AGENT = state.defaultAgent === 'codex';
|
|
1606
|
+
vars.CHATCCC_CCC_DEFAULT_AGENT = state.defaultAgent === 'ccc';
|
|
1604
1607
|
if (state.agentsEnabled.claude) {
|
|
1605
1608
|
AGENT_FIELDS.claude.forEach(function(key){
|
|
1606
1609
|
var el = document.getElementById('field-' + key);
|
|
@@ -1613,21 +1616,21 @@ function collectAllFields() {
|
|
|
1613
1616
|
if (el && el.value.trim()) vars[key] = el.value.trim();
|
|
1614
1617
|
});
|
|
1615
1618
|
}
|
|
1616
|
-
if (state.agentsEnabled.codex) {
|
|
1617
|
-
AGENT_FIELDS.codex.forEach(function(key){
|
|
1618
|
-
var el = document.getElementById('field-' + key);
|
|
1619
|
-
if (!el) return;
|
|
1620
|
-
if (key === 'CHATCCC_CODEX_FAST_MODE') vars[key] = !!el.checked;
|
|
1621
|
-
else if (el.value.trim()) vars[key] = el.value.trim();
|
|
1622
|
-
});
|
|
1623
|
-
}
|
|
1624
|
-
if (state.agentsEnabled.ccc) {
|
|
1625
|
-
AGENT_FIELDS.ccc.forEach(function(key){
|
|
1626
|
-
var el = document.getElementById('field-' + key);
|
|
1627
|
-
if (el && el.value.trim()) vars[key] = el.value.trim();
|
|
1628
|
-
});
|
|
1629
|
-
}
|
|
1630
|
-
return vars;
|
|
1619
|
+
if (state.agentsEnabled.codex) {
|
|
1620
|
+
AGENT_FIELDS.codex.forEach(function(key){
|
|
1621
|
+
var el = document.getElementById('field-' + key);
|
|
1622
|
+
if (!el) return;
|
|
1623
|
+
if (key === 'CHATCCC_CODEX_FAST_MODE') vars[key] = !!el.checked;
|
|
1624
|
+
else if (el.value.trim()) vars[key] = el.value.trim();
|
|
1625
|
+
});
|
|
1626
|
+
}
|
|
1627
|
+
if (state.agentsEnabled.ccc) {
|
|
1628
|
+
AGENT_FIELDS.ccc.forEach(function(key){
|
|
1629
|
+
var el = document.getElementById('field-' + key);
|
|
1630
|
+
if (el && el.value.trim()) vars[key] = el.value.trim();
|
|
1631
|
+
});
|
|
1632
|
+
}
|
|
1633
|
+
return vars;
|
|
1631
1634
|
}
|
|
1632
1635
|
|
|
1633
1636
|
function renderStep3() {
|
|
@@ -1643,16 +1646,16 @@ function renderStep3() {
|
|
|
1643
1646
|
lines.push('<div class="config-row"><span class="key">平台类型</span><span class="val">' + ptLabel + '</span></div>');
|
|
1644
1647
|
}
|
|
1645
1648
|
|
|
1646
|
-
lines.push('<h3 style="margin:16px 0 8px">微信 iLink</h3>');
|
|
1647
|
-
lines.push('<div class="config-row"><span class="key">状态</span><span class="val">' + (state.platformsEnabled.ilink ? '已启用' : '已禁用') + '</span></div>');
|
|
1649
|
+
lines.push('<h3 style="margin:16px 0 8px">微信 iLink</h3>');
|
|
1650
|
+
lines.push('<div class="config-row"><span class="key">状态</span><span class="val">' + (state.platformsEnabled.ilink ? '已启用' : '已禁用') + '</span></div>');
|
|
1648
1651
|
|
|
1649
1652
|
if (!state.platformsEnabled.feishu && !state.platformsEnabled.ilink) {
|
|
1650
|
-
lines.push('<div style="color:#ef4444;margin-top:8px">未启用任何平台</div>');
|
|
1651
|
-
}
|
|
1652
|
-
lines.push('<h3 style="margin:16px 0 8px">Web UI</h3>');
|
|
1653
|
-
lines.push('<div class="config-row"><span class="key">直接启动时自动打开</span><span class="val">' + (vars.CHATCCC_WEB_UI_OPEN_ON_START ? '已启用' : '已禁用') + '</span></div>');
|
|
1654
|
-
|
|
1655
|
-
lines.push('<h3 style="margin:16px 0 8px">Chrome CDP</h3>');
|
|
1653
|
+
lines.push('<div style="color:#ef4444;margin-top:8px">未启用任何平台</div>');
|
|
1654
|
+
}
|
|
1655
|
+
lines.push('<h3 style="margin:16px 0 8px">Web UI</h3>');
|
|
1656
|
+
lines.push('<div class="config-row"><span class="key">直接启动时自动打开</span><span class="val">' + (vars.CHATCCC_WEB_UI_OPEN_ON_START ? '已启用' : '已禁用') + '</span></div>');
|
|
1657
|
+
|
|
1658
|
+
lines.push('<h3 style="margin:16px 0 8px">Chrome CDP</h3>');
|
|
1656
1659
|
lines.push('<div class="config-row"><span class="key">状态</span><span class="val">' + (vars.CHATCCC_CHROME_DEVTOOLS_ENABLED ? '已启用' : '已禁用') + '</span></div>');
|
|
1657
1660
|
if (vars.CHATCCC_CHROME_DEVTOOLS_ENABLED) {
|
|
1658
1661
|
lines.push('<div class="config-row"><span class="key">CDP 端口</span><span class="val">' + (vars.CHATCCC_CHROME_DEVTOOLS_PORT || '15166') + '</span></div>');
|
|
@@ -1665,12 +1668,12 @@ function renderStep3() {
|
|
|
1665
1668
|
var enabledList = [];
|
|
1666
1669
|
if (state.agentsEnabled.claude) enabledList.push('claude');
|
|
1667
1670
|
if (state.agentsEnabled.cursor) enabledList.push('cursor');
|
|
1668
|
-
if (state.agentsEnabled.codex) enabledList.push('codex');
|
|
1669
|
-
if (state.agentsEnabled.ccc) enabledList.push('ccc');
|
|
1671
|
+
if (state.agentsEnabled.codex) enabledList.push('codex');
|
|
1672
|
+
if (state.agentsEnabled.ccc) enabledList.push('ccc');
|
|
1670
1673
|
if (enabledList.length === 0) {
|
|
1671
1674
|
lines.push('<div style="color:#ef4444">未启用任何 AI Agent</div>');
|
|
1672
1675
|
} else {
|
|
1673
|
-
var defaultLabel = state.defaultAgent === 'cursor' ? 'Cursor' : state.defaultAgent === 'codex' ? 'Codex' : state.defaultAgent === 'ccc' ? 'CCC Agent' : 'Claude Code';
|
|
1676
|
+
var defaultLabel = state.defaultAgent === 'cursor' ? 'Cursor' : state.defaultAgent === 'codex' ? 'Codex' : state.defaultAgent === 'ccc' ? 'CCC Agent' : 'Claude Code';
|
|
1674
1677
|
lines.push('<div class="config-row"><span class="key">/new 默认 Agent</span><span class="val">' + defaultLabel + '</span></div>');
|
|
1675
1678
|
}
|
|
1676
1679
|
enabledList.forEach(function(t){
|
|
@@ -1690,35 +1693,35 @@ function renderStep3() {
|
|
|
1690
1693
|
if (vars.CHATCCC_CURSOR_AVATAR_BATTERY_MODE === 'onDemandUse') {
|
|
1691
1694
|
lines.push('<div class="config-row"><span class="key">每月On demand use预算</span><span class="val">' + (vars.CHATCCC_CURSOR_ON_DEMAND_MONTHLY_BUDGET || '1000') + '</span></div>');
|
|
1692
1695
|
}
|
|
1693
|
-
} else if (t === 'codex') {
|
|
1696
|
+
} else if (t === 'codex') {
|
|
1694
1697
|
lines.push('<h4 style="margin:10px 0 4px;color:#334155">Codex</h4>');
|
|
1695
1698
|
if (vars.CHATCCC_CODEX_PATH) lines.push('<div class="config-row"><span class="key">CLI 路径</span><span class="val">' + vars.CHATCCC_CODEX_PATH + '</span></div>');
|
|
1696
1699
|
lines.push('<div class="config-row"><span class="key">模型</span><span class="val">' + (vars.CHATCCC_CODEX_MODEL || '(留空)') + '</span></div>');
|
|
1697
|
-
lines.push('<div class="config-row"><span class="key">备选模型</span><span class="val">' + (vars.CHATCCC_CODEX_ALTERNATIVE_MODEL || '(留空)') + '</span></div>');
|
|
1698
|
-
lines.push('<div class="config-row"><span class="key">Effort</span><span class="val">' + (vars.CHATCCC_CODEX_EFFORT || '(留空)') + '</span></div>');
|
|
1699
|
-
lines.push('<div class="config-row"><span class="key">Fast 模式</span><span class="val">' + (vars.CHATCCC_CODEX_FAST_MODE ? '已启用' : '已禁用') + '</span></div>');
|
|
1700
|
-
} else if (t === 'ccc') {
|
|
1701
|
-
lines.push('<h4 style="margin:10px 0 4px;color:#334155">CCC Agent</h4>');
|
|
1702
|
-
lines.push('<div class="config-row"><span class="key">API Key</span><span class="val">' + (vars.CHATCCC_CCC_API_KEY ? '***已设置***' : '(留空)') + '</span></div>');
|
|
1703
|
-
lines.push('<div class="config-row"><span class="key">Base URL</span><span class="val">' + (vars.CHATCCC_CCC_BASE_URL || '(留空)') + '</span></div>');
|
|
1704
|
-
lines.push('<div class="config-row"><span class="key">模型</span><span class="val">' + (vars.CHATCCC_CCC_MODEL || '(留空)') + '</span></div>');
|
|
1705
|
-
lines.push('<div class="config-row"><span class="key">备选模型</span><span class="val">' + (vars.CHATCCC_CCC_ALTERNATIVE_MODEL || '(留空)') + '</span></div>');
|
|
1700
|
+
lines.push('<div class="config-row"><span class="key">备选模型</span><span class="val">' + (vars.CHATCCC_CODEX_ALTERNATIVE_MODEL || '(留空)') + '</span></div>');
|
|
1701
|
+
lines.push('<div class="config-row"><span class="key">Effort</span><span class="val">' + (vars.CHATCCC_CODEX_EFFORT || '(留空)') + '</span></div>');
|
|
1702
|
+
lines.push('<div class="config-row"><span class="key">Fast 模式</span><span class="val">' + (vars.CHATCCC_CODEX_FAST_MODE ? '已启用' : '已禁用') + '</span></div>');
|
|
1703
|
+
} else if (t === 'ccc') {
|
|
1704
|
+
lines.push('<h4 style="margin:10px 0 4px;color:#334155">CCC Agent</h4>');
|
|
1705
|
+
lines.push('<div class="config-row"><span class="key">API Key</span><span class="val">' + (vars.CHATCCC_CCC_API_KEY ? '***已设置***' : '(留空)') + '</span></div>');
|
|
1706
|
+
lines.push('<div class="config-row"><span class="key">Base URL</span><span class="val">' + (vars.CHATCCC_CCC_BASE_URL || '(留空)') + '</span></div>');
|
|
1707
|
+
lines.push('<div class="config-row"><span class="key">模型</span><span class="val">' + (vars.CHATCCC_CCC_MODEL || '(留空)') + '</span></div>');
|
|
1708
|
+
lines.push('<div class="config-row"><span class="key">备选模型</span><span class="val">' + (vars.CHATCCC_CCC_ALTERNATIVE_MODEL || '(留空)') + '</span></div>');
|
|
1706
1709
|
}
|
|
1707
1710
|
});
|
|
1708
1711
|
document.getElementById('review-content').innerHTML = lines.join('');
|
|
1709
1712
|
}
|
|
1710
1713
|
|
|
1711
|
-
async function saveConfig(vars, options) {
|
|
1712
|
-
options = options || {};
|
|
1714
|
+
async function saveConfig(vars, options) {
|
|
1715
|
+
options = options || {};
|
|
1713
1716
|
var result = await api('/api/config', 'POST', { vars: vars });
|
|
1714
|
-
if (result.ok) {
|
|
1715
|
-
state.config = Object.assign({}, state.config, vars);
|
|
1716
|
-
if (!options.quiet) toastConfigApplyResult(result, '配置已保存');
|
|
1717
|
+
if (result.ok) {
|
|
1718
|
+
state.config = Object.assign({}, state.config, vars);
|
|
1719
|
+
if (!options.quiet) toastConfigApplyResult(result, '配置已保存');
|
|
1717
1720
|
} else {
|
|
1718
1721
|
toast('保存失败: ' + (result.error || '未知错误'), 'error');
|
|
1719
1722
|
}
|
|
1720
|
-
return result;
|
|
1721
|
-
}
|
|
1723
|
+
return result;
|
|
1724
|
+
}
|
|
1722
1725
|
|
|
1723
1726
|
async function setDashboardDefaultAgent(agent, enabled) {
|
|
1724
1727
|
if (!enabled) {
|
|
@@ -1728,22 +1731,22 @@ async function setDashboardDefaultAgent(agent, enabled) {
|
|
|
1728
1731
|
state.defaultAgent = agent;
|
|
1729
1732
|
var vars = {
|
|
1730
1733
|
CHATCCC_CLAUDE_DEFAULT_AGENT: agent === 'claude',
|
|
1731
|
-
CHATCCC_CURSOR_DEFAULT_AGENT: agent === 'cursor',
|
|
1732
|
-
CHATCCC_CODEX_DEFAULT_AGENT: agent === 'codex',
|
|
1733
|
-
CHATCCC_CCC_DEFAULT_AGENT: agent === 'ccc'
|
|
1734
|
+
CHATCCC_CURSOR_DEFAULT_AGENT: agent === 'cursor',
|
|
1735
|
+
CHATCCC_CODEX_DEFAULT_AGENT: agent === 'codex',
|
|
1736
|
+
CHATCCC_CCC_DEFAULT_AGENT: agent === 'ccc'
|
|
1734
1737
|
};
|
|
1735
1738
|
var result = await api('/api/config', 'POST', { vars: vars });
|
|
1736
1739
|
if (result.ok) {
|
|
1737
1740
|
state.config.claude = state.config.claude || {};
|
|
1738
1741
|
state.config.cursor = state.config.cursor || {};
|
|
1739
|
-
state.config.codex = state.config.codex || {};
|
|
1740
|
-
state.config.ccc = state.config.ccc || {};
|
|
1742
|
+
state.config.codex = state.config.codex || {};
|
|
1743
|
+
state.config.ccc = state.config.ccc || {};
|
|
1741
1744
|
state.config.claude.defaultAgent = agent === 'claude';
|
|
1742
|
-
state.config.cursor.defaultAgent = agent === 'cursor';
|
|
1743
|
-
state.config.codex.defaultAgent = agent === 'codex';
|
|
1744
|
-
state.config.ccc.defaultAgent = agent === 'ccc';
|
|
1745
|
-
updateDefaultAgentToggles();
|
|
1746
|
-
toastConfigApplyResult(result, '默认 Agent 已更新');
|
|
1745
|
+
state.config.cursor.defaultAgent = agent === 'cursor';
|
|
1746
|
+
state.config.codex.defaultAgent = agent === 'codex';
|
|
1747
|
+
state.config.ccc.defaultAgent = agent === 'ccc';
|
|
1748
|
+
updateDefaultAgentToggles();
|
|
1749
|
+
toastConfigApplyResult(result, '默认 Agent 已更新');
|
|
1747
1750
|
} else {
|
|
1748
1751
|
toast('保存失败: ' + (result.error || '未知错误'), 'error');
|
|
1749
1752
|
updateDefaultAgentToggles();
|
|
@@ -1756,36 +1759,36 @@ async function saveAndStart() {
|
|
|
1756
1759
|
toast('飞书已启用,请先填写 App ID 和 App Secret', 'error');
|
|
1757
1760
|
return;
|
|
1758
1761
|
}
|
|
1759
|
-
var saved = await saveConfig(vars, { quiet: true });
|
|
1760
|
-
if (saved.ok !== true) return;
|
|
1761
|
-
document.getElementById('btn-save-start').disabled = true;
|
|
1762
|
-
document.getElementById('btn-save-start').innerHTML = '<span class="spinner"></span> 应用中...';
|
|
1763
|
-
|
|
1764
|
-
if (state.running && saved.restartRequired === true) {
|
|
1765
|
-
await api('/api/restart', 'POST');
|
|
1766
|
-
toast('配置已保存,服务正在重启…');
|
|
1767
|
-
pollUntilRunning();
|
|
1768
|
-
return;
|
|
1769
|
-
}
|
|
1770
|
-
|
|
1771
|
-
if (state.running && saved.mode === 'reload') {
|
|
1772
|
-
toastConfigApplyResult(saved, '配置已保存');
|
|
1773
|
-
setTimeout(function(){ location.reload(); }, 1000);
|
|
1774
|
-
return;
|
|
1775
|
-
}
|
|
1776
|
-
|
|
1777
|
-
var result = await api('/api/start', 'POST');
|
|
1778
|
-
if (result.ok) {
|
|
1779
|
-
var msg;
|
|
1780
|
-
if (result.mode === 'reload') {
|
|
1781
|
-
msg = '配置已保存,后续消息/新会话已生效。';
|
|
1782
|
-
} else if (result.mode === 'inplace') {
|
|
1783
|
-
msg = '服务已启动! PID: ' + result.pid;
|
|
1784
|
-
} else {
|
|
1785
|
-
msg = '服务已启动! PID: ' + (result.pid || '?');
|
|
1786
|
-
}
|
|
1787
|
-
toast(msg);
|
|
1788
|
-
setTimeout(function(){ location.reload(); }, 1500);
|
|
1762
|
+
var saved = await saveConfig(vars, { quiet: true });
|
|
1763
|
+
if (saved.ok !== true) return;
|
|
1764
|
+
document.getElementById('btn-save-start').disabled = true;
|
|
1765
|
+
document.getElementById('btn-save-start').innerHTML = '<span class="spinner"></span> 应用中...';
|
|
1766
|
+
|
|
1767
|
+
if (state.running && saved.restartRequired === true) {
|
|
1768
|
+
await api('/api/restart', 'POST');
|
|
1769
|
+
toast('配置已保存,服务正在重启…');
|
|
1770
|
+
pollUntilRunning();
|
|
1771
|
+
return;
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
if (state.running && saved.mode === 'reload') {
|
|
1775
|
+
toastConfigApplyResult(saved, '配置已保存');
|
|
1776
|
+
setTimeout(function(){ location.reload(); }, 1000);
|
|
1777
|
+
return;
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
var result = await api('/api/start', 'POST');
|
|
1781
|
+
if (result.ok) {
|
|
1782
|
+
var msg;
|
|
1783
|
+
if (result.mode === 'reload') {
|
|
1784
|
+
msg = '配置已保存,后续消息/新会话已生效。';
|
|
1785
|
+
} else if (result.mode === 'inplace') {
|
|
1786
|
+
msg = '服务已启动! PID: ' + result.pid;
|
|
1787
|
+
} else {
|
|
1788
|
+
msg = '服务已启动! PID: ' + (result.pid || '?');
|
|
1789
|
+
}
|
|
1790
|
+
toast(msg);
|
|
1791
|
+
setTimeout(function(){ location.reload(); }, 1500);
|
|
1789
1792
|
} else {
|
|
1790
1793
|
toast('保存失败: ' + (result.error || '未知错误'), 'error');
|
|
1791
1794
|
document.getElementById('btn-save-start').disabled = false;
|
|
@@ -1857,10 +1860,10 @@ function updateDashboardUI() {
|
|
|
1857
1860
|
// 只显示已启用的 Agent 卡片(按 enabled 字段;缺省时退回到"任一字段非空"兼容旧 config)
|
|
1858
1861
|
var claudeOn = isAgentEnabled(c.claude, CLAUDE_FALLBACK_KEYS);
|
|
1859
1862
|
var cursorOn = isAgentEnabled(c.cursor, CURSOR_FALLBACK_KEYS);
|
|
1860
|
-
var codexOn = isAgentEnabled(c.codex, CODEX_FALLBACK_KEYS);
|
|
1861
|
-
var cccOn = isAgentEnabled(c.ccc, CCC_FALLBACK_KEYS);
|
|
1862
|
-
state.agentsEnabled = { claude: claudeOn, cursor: cursorOn, codex: codexOn, ccc: cccOn };
|
|
1863
|
-
state.defaultAgent = resolveDefaultAgentFromConfig(c, claudeOn, cursorOn, codexOn, cccOn);
|
|
1863
|
+
var codexOn = isAgentEnabled(c.codex, CODEX_FALLBACK_KEYS);
|
|
1864
|
+
var cccOn = isAgentEnabled(c.ccc, CCC_FALLBACK_KEYS);
|
|
1865
|
+
state.agentsEnabled = { claude: claudeOn, cursor: cursorOn, codex: codexOn, ccc: cccOn };
|
|
1866
|
+
state.defaultAgent = resolveDefaultAgentFromConfig(c, claudeOn, cursorOn, codexOn, cccOn);
|
|
1864
1867
|
|
|
1865
1868
|
// 微信 iLink 平台开关:同步复选框和标签
|
|
1866
1869
|
var ilinkEnabled = c.platforms && c.platforms.ilink ? c.platforms.ilink.enabled !== false : true;
|
|
@@ -1872,14 +1875,14 @@ function updateDashboardUI() {
|
|
|
1872
1875
|
|
|
1873
1876
|
// 微信 iLink "忘记扫码" 按钮:仅在 ilink 已启用且有已保存 token 时显示
|
|
1874
1877
|
var ilinkForgetRow = document.getElementById('ilink-forget-row');
|
|
1875
|
-
if (ilinkForgetRow) {
|
|
1876
|
-
ilinkForgetRow.style.display = (ilinkEnabled && state.ilinkAuthExists) ? '' : 'none';
|
|
1877
|
-
}
|
|
1878
|
-
|
|
1879
|
-
var webUi = c.webUi || {};
|
|
1880
|
-
document.getElementById('cfg-WEB_UI_OPEN_ON_START').textContent = webUi.openOnStart !== false ? '已启用' : '已禁用';
|
|
1881
|
-
|
|
1882
|
-
var chromeDevtools = c.chromeDevtools || {};
|
|
1878
|
+
if (ilinkForgetRow) {
|
|
1879
|
+
ilinkForgetRow.style.display = (ilinkEnabled && state.ilinkAuthExists) ? '' : 'none';
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
var webUi = c.webUi || {};
|
|
1883
|
+
document.getElementById('cfg-WEB_UI_OPEN_ON_START').textContent = webUi.openOnStart !== false ? '已启用' : '已禁用';
|
|
1884
|
+
|
|
1885
|
+
var chromeDevtools = c.chromeDevtools || {};
|
|
1883
1886
|
var cdpPort = chromeDevtools.port || 15166;
|
|
1884
1887
|
document.getElementById('cfg-CHROME_DEVTOOLS_ENABLED').textContent = chromeDevtools.enabled ? '已启用' : '已禁用';
|
|
1885
1888
|
document.getElementById('cfg-CHROME_DEVTOOLS_PORT').textContent = String(cdpPort);
|
|
@@ -1891,12 +1894,12 @@ function updateDashboardUI() {
|
|
|
1891
1894
|
|
|
1892
1895
|
document.getElementById('dash-claude').style.display = claudeOn ? '' : 'none';
|
|
1893
1896
|
document.getElementById('dash-cursor').style.display = cursorOn ? '' : 'none';
|
|
1894
|
-
document.getElementById('dash-codex').style.display = codexOn ? '' : 'none';
|
|
1895
|
-
document.getElementById('dash-ccc').style.display = cccOn ? '' : 'none';
|
|
1897
|
+
document.getElementById('dash-codex').style.display = codexOn ? '' : 'none';
|
|
1898
|
+
document.getElementById('dash-ccc').style.display = cccOn ? '' : 'none';
|
|
1896
1899
|
updateDefaultAgentToggles();
|
|
1897
|
-
// 四个都未启用时给一个空态提示,引导用户去配置向导启用
|
|
1900
|
+
// 四个都未启用时给一个空态提示,引导用户去配置向导启用
|
|
1898
1901
|
var emptyHint = document.getElementById('dash-no-agent-hint');
|
|
1899
|
-
if (emptyHint) emptyHint.style.display = (!claudeOn && !cursorOn && !codexOn && !cccOn) ? '' : 'none';
|
|
1902
|
+
if (emptyHint) emptyHint.style.display = (!claudeOn && !cursorOn && !codexOn && !cccOn) ? '' : 'none';
|
|
1900
1903
|
|
|
1901
1904
|
document.getElementById('cfg-ANTHROPIC_MODEL').textContent = (c.claude && c.claude.model) || '(留空)';
|
|
1902
1905
|
document.getElementById('cfg-ANTHROPIC_SUBAGENT_MODEL').textContent = (c.claude && c.claude.subagentModel) || '(留空)';
|
|
@@ -1914,13 +1917,13 @@ function updateDashboardUI() {
|
|
|
1914
1917
|
document.getElementById('cfg-CURSOR_ON_DEMAND_MONTHLY_BUDGET').textContent = String((c.cursor && c.cursor.onDemandMonthlyBudget) || 1000);
|
|
1915
1918
|
document.getElementById('cfg-CODEX_PATH').textContent = (c.codex && (c.codex.path || c.codex.command)) || 'codex';
|
|
1916
1919
|
document.getElementById('cfg-CODEX_MODEL').textContent = (c.codex && c.codex.model) || '(留空)';
|
|
1917
|
-
document.getElementById('cfg-CODEX_ALTERNATIVE_MODEL').textContent = (c.codex && c.codex.alternativeModel) || '(留空)';
|
|
1918
|
-
document.getElementById('cfg-CODEX_EFFORT').textContent = (c.codex && c.codex.effort) || '(留空)';
|
|
1919
|
-
document.getElementById('cfg-CODEX_FAST_MODE').textContent = c.codex && c.codex.fastMode === true ? '已启用' : '已禁用';
|
|
1920
|
-
document.getElementById('cfg-CCC_API_KEY').textContent = (c.ccc && c.ccc.DEEPSEEK_API_KEY) ? '***已设置***' : '(留空)';
|
|
1921
|
-
document.getElementById('cfg-CCC_BASE_URL').textContent = (c.ccc && c.ccc.DEEPSEEK_BASE_URL) || '(留空)';
|
|
1922
|
-
document.getElementById('cfg-CCC_MODEL').textContent = (c.ccc && c.ccc.model) || '(留空)';
|
|
1923
|
-
document.getElementById('cfg-CCC_ALTERNATIVE_MODEL').textContent = (c.ccc && c.ccc.alternativeModel) || '(留空)';
|
|
1920
|
+
document.getElementById('cfg-CODEX_ALTERNATIVE_MODEL').textContent = (c.codex && c.codex.alternativeModel) || '(留空)';
|
|
1921
|
+
document.getElementById('cfg-CODEX_EFFORT').textContent = (c.codex && c.codex.effort) || '(留空)';
|
|
1922
|
+
document.getElementById('cfg-CODEX_FAST_MODE').textContent = c.codex && c.codex.fastMode === true ? '已启用' : '已禁用';
|
|
1923
|
+
document.getElementById('cfg-CCC_API_KEY').textContent = (c.ccc && c.ccc.DEEPSEEK_API_KEY) ? '***已设置***' : '(留空)';
|
|
1924
|
+
document.getElementById('cfg-CCC_BASE_URL').textContent = (c.ccc && c.ccc.DEEPSEEK_BASE_URL) || '(留空)';
|
|
1925
|
+
document.getElementById('cfg-CCC_MODEL').textContent = (c.ccc && c.ccc.model) || '(留空)';
|
|
1926
|
+
document.getElementById('cfg-CCC_ALTERNATIVE_MODEL').textContent = (c.ccc && c.ccc.alternativeModel) || '(留空)';
|
|
1924
1927
|
}
|
|
1925
1928
|
|
|
1926
1929
|
function pollStatus() {
|
|
@@ -1968,24 +1971,24 @@ async function pollUntilRunning() {
|
|
|
1968
1971
|
// ---- Edit Modal ----
|
|
1969
1972
|
var editSectionType = null;
|
|
1970
1973
|
|
|
1971
|
-
function editSection(section) {
|
|
1974
|
+
function editSection(section) {
|
|
1972
1975
|
editSectionType = section;
|
|
1973
|
-
var fields;
|
|
1974
|
-
if (section === 'feishu') fields = FEISHU_FIELDS;
|
|
1975
|
-
else if (section === 'webUi') fields = WEB_UI_FIELDS;
|
|
1976
|
-
else if (section === 'chromeDevtools') fields = CHROME_DEVTOOLS_FIELDS;
|
|
1976
|
+
var fields;
|
|
1977
|
+
if (section === 'feishu') fields = FEISHU_FIELDS;
|
|
1978
|
+
else if (section === 'webUi') fields = WEB_UI_FIELDS;
|
|
1979
|
+
else if (section === 'chromeDevtools') fields = CHROME_DEVTOOLS_FIELDS;
|
|
1977
1980
|
else fields = AGENT_FIELDS[section] || [];
|
|
1978
1981
|
|
|
1979
|
-
var titleMap = { feishu: '飞书', webUi: 'Web UI', chromeDevtools: 'Chrome CDP', claude: 'Claude Agent', cursor: 'Cursor Agent', codex: 'Codex Agent', ccc: 'CCC Agent' };
|
|
1982
|
+
var titleMap = { feishu: '飞书', webUi: 'Web UI', chromeDevtools: 'Chrome CDP', claude: 'Claude Agent', cursor: 'Cursor Agent', codex: 'Codex Agent', ccc: 'CCC Agent' };
|
|
1980
1983
|
document.getElementById('edit-modal-title').textContent = '编辑 ' + (titleMap[section] || section);
|
|
1981
1984
|
|
|
1982
|
-
document.getElementById('edit-modal-effect').textContent = configEffectHint(section);
|
|
1983
|
-
|
|
1984
|
-
var html = '';
|
|
1985
|
-
var labelMap = {
|
|
1986
|
-
'CHATCCC_APP_ID': 'App ID', 'CHATCCC_APP_SECRET': 'App Secret',
|
|
1987
|
-
'CHATCCC_WEB_UI_OPEN_ON_START': '直接启动 ChatCCC 时自动打开 Web UI',
|
|
1988
|
-
'CHATCCC_CHROME_DEVTOOLS_ENABLED': '启用常驻 Chrome CDP(选填)',
|
|
1985
|
+
document.getElementById('edit-modal-effect').textContent = configEffectHint(section);
|
|
1986
|
+
|
|
1987
|
+
var html = '';
|
|
1988
|
+
var labelMap = {
|
|
1989
|
+
'CHATCCC_APP_ID': 'App ID', 'CHATCCC_APP_SECRET': 'App Secret',
|
|
1990
|
+
'CHATCCC_WEB_UI_OPEN_ON_START': '直接启动 ChatCCC 时自动打开 Web UI',
|
|
1991
|
+
'CHATCCC_CHROME_DEVTOOLS_ENABLED': '启用常驻 Chrome CDP(选填)',
|
|
1989
1992
|
'CHATCCC_CHROME_DEVTOOLS_PORT': 'CDP 端口',
|
|
1990
1993
|
'CHATCCC_CHROME_DEVTOOLS_PATH': 'Chrome 路径(选填)',
|
|
1991
1994
|
'CHATCCC_ANTHROPIC_MODEL': '模型', 'CHATCCC_ANTHROPIC_SUBAGENT_MODEL': 'Subagent 模型', 'CHATCCC_ANTHROPIC_EFFORT': 'Effort',
|
|
@@ -1993,14 +1996,14 @@ function editSection(section) {
|
|
|
1993
1996
|
'CHATCCC_CURSOR_PATH': 'CLI 路径', 'CHATCCC_CURSOR_MODEL': '模型', 'CHATCCC_CURSOR_ALTERNATIVE_MODEL': '备选模型',
|
|
1994
1997
|
'CHATCCC_CURSOR_AVATAR_BATTERY_MODE': '头像电池电量',
|
|
1995
1998
|
'CHATCCC_CURSOR_ON_DEMAND_MONTHLY_BUDGET': '每月On demand use预算',
|
|
1996
|
-
'CHATCCC_CODEX_PATH': 'CLI 路径', 'CHATCCC_CODEX_MODEL': '模型', 'CHATCCC_CODEX_ALTERNATIVE_MODEL': '备选模型', 'CHATCCC_CODEX_EFFORT': 'Effort',
|
|
1997
|
-
'CHATCCC_CODEX_FAST_MODE': 'Fast 模式',
|
|
1998
|
-
'CHATCCC_CCC_API_KEY': 'API Key', 'CHATCCC_CCC_BASE_URL': 'Base URL',
|
|
1999
|
-
'CHATCCC_CCC_MODEL': '模型', 'CHATCCC_CCC_ALTERNATIVE_MODEL': '备选模型'
|
|
1999
|
+
'CHATCCC_CODEX_PATH': 'CLI 路径', 'CHATCCC_CODEX_MODEL': '模型', 'CHATCCC_CODEX_ALTERNATIVE_MODEL': '备选模型', 'CHATCCC_CODEX_EFFORT': 'Effort',
|
|
2000
|
+
'CHATCCC_CODEX_FAST_MODE': 'Fast 模式',
|
|
2001
|
+
'CHATCCC_CCC_API_KEY': 'API Key', 'CHATCCC_CCC_BASE_URL': 'Base URL',
|
|
2002
|
+
'CHATCCC_CCC_MODEL': '模型', 'CHATCCC_CCC_ALTERNATIVE_MODEL': '备选模型'
|
|
2000
2003
|
};
|
|
2001
|
-
var hintMap = {
|
|
2002
|
-
'CHATCCC_WEB_UI_OPEN_ON_START': '关闭后可继续手动访问 http://localhost:<端口>/;/restart、/update 和 Web UI 重启无论此项为何值都不会自动打开。',
|
|
2003
|
-
'CHATCCC_CHROME_DEVTOOLS_ENABLED': '依赖:本机 Google Chrome;ChatGPT 订阅到期查询需要在该 CDP Chrome 中登录 ChatGPT。',
|
|
2004
|
+
var hintMap = {
|
|
2005
|
+
'CHATCCC_WEB_UI_OPEN_ON_START': '关闭后可继续手动访问 http://localhost:<端口>/;/restart、/update 和 Web UI 重启无论此项为何值都不会自动打开。',
|
|
2006
|
+
'CHATCCC_CHROME_DEVTOOLS_ENABLED': '依赖:本机 Google Chrome;ChatGPT 订阅到期查询需要在该 CDP Chrome 中登录 ChatGPT。',
|
|
2004
2007
|
'CHATCCC_CHROME_DEVTOOLS_PORT': '默认 15166,健康检查端点为 http://127.0.0.1:15166/json/version。',
|
|
2005
2008
|
'CHATCCC_CHROME_DEVTOOLS_PATH': '选填。留空时自动探测 Google Chrome。'
|
|
2006
2009
|
};
|
|
@@ -2013,12 +2016,12 @@ function editSection(section) {
|
|
|
2013
2016
|
var val = state.config[key] || '';
|
|
2014
2017
|
// Also check nested config
|
|
2015
2018
|
if (!val) {
|
|
2016
|
-
if (section === 'feishu') {
|
|
2019
|
+
if (section === 'feishu') {
|
|
2017
2020
|
if (key === 'CHATCCC_APP_ID' && state.config.feishu) val = state.config.feishu.appId || '';
|
|
2018
2021
|
else if (key === 'CHATCCC_APP_SECRET' && state.config.feishu) val = state.config.feishu.appSecret || '';
|
|
2019
|
-
} else if (section === 'webUi') {
|
|
2020
|
-
val = !state.config.webUi || state.config.webUi.openOnStart !== false ? 'true' : 'false';
|
|
2021
|
-
} else if (section === 'chromeDevtools' && state.config.chromeDevtools) {
|
|
2022
|
+
} else if (section === 'webUi') {
|
|
2023
|
+
val = !state.config.webUi || state.config.webUi.openOnStart !== false ? 'true' : 'false';
|
|
2024
|
+
} else if (section === 'chromeDevtools' && state.config.chromeDevtools) {
|
|
2022
2025
|
if (key === 'CHATCCC_CHROME_DEVTOOLS_ENABLED') val = state.config.chromeDevtools.enabled === true ? 'true' : 'false';
|
|
2023
2026
|
else if (key === 'CHATCCC_CHROME_DEVTOOLS_PORT') val = state.config.chromeDevtools.port != null ? String(state.config.chromeDevtools.port) : '15166';
|
|
2024
2027
|
else if (key === 'CHATCCC_CHROME_DEVTOOLS_PATH') val = state.config.chromeDevtools.chromePath || '';
|
|
@@ -2035,24 +2038,24 @@ function editSection(section) {
|
|
|
2035
2038
|
else if (key === 'CHATCCC_CURSOR_ALTERNATIVE_MODEL') val = state.config.cursor.alternativeModel || '';
|
|
2036
2039
|
else if (key === 'CHATCCC_CURSOR_AVATAR_BATTERY_MODE') val = state.config.cursor.avatarBatteryMode || 'apiPercent';
|
|
2037
2040
|
else if (key === 'CHATCCC_CURSOR_ON_DEMAND_MONTHLY_BUDGET') val = (state.config.cursor.onDemandMonthlyBudget != null) ? String(state.config.cursor.onDemandMonthlyBudget) : '1000';
|
|
2038
|
-
} else if (section === 'codex' && state.config.codex) {
|
|
2041
|
+
} else if (section === 'codex' && state.config.codex) {
|
|
2039
2042
|
if (key === 'CHATCCC_CODEX_PATH') val = state.config.codex.path || state.config.codex.command || '';
|
|
2040
|
-
else if (key === 'CHATCCC_CODEX_MODEL') val = state.config.codex.model || '';
|
|
2041
|
-
else if (key === 'CHATCCC_CODEX_ALTERNATIVE_MODEL') val = state.config.codex.alternativeModel || '';
|
|
2042
|
-
else if (key === 'CHATCCC_CODEX_EFFORT') val = state.config.codex.effort || '';
|
|
2043
|
-
else if (key === 'CHATCCC_CODEX_FAST_MODE') val = state.config.codex.fastMode === true ? 'true' : 'false';
|
|
2044
|
-
} else if (section === 'ccc' && state.config.ccc) {
|
|
2045
|
-
if (key === 'CHATCCC_CCC_API_KEY') val = state.config.ccc.DEEPSEEK_API_KEY || '';
|
|
2046
|
-
else if (key === 'CHATCCC_CCC_BASE_URL') val = state.config.ccc.DEEPSEEK_BASE_URL || '';
|
|
2047
|
-
else if (key === 'CHATCCC_CCC_MODEL') val = state.config.ccc.model || '';
|
|
2048
|
-
else if (key === 'CHATCCC_CCC_ALTERNATIVE_MODEL') val = state.config.ccc.alternativeModel || '';
|
|
2049
|
-
}
|
|
2050
|
-
}
|
|
2051
|
-
var isSecret = key.includes('SECRET') || key.includes('API_KEY');
|
|
2052
|
-
if (key === 'CHATCCC_WEB_UI_OPEN_ON_START' || key === 'CHATCCC_CHROME_DEVTOOLS_ENABLED' || key === 'CHATCCC_CODEX_FAST_MODE') {
|
|
2053
|
-
var checked = val === true || val === 'true';
|
|
2054
|
-
var changeHandler = key === 'CHATCCC_CHROME_DEVTOOLS_ENABLED' ? ' onchange="toggleEditChromeDevtoolsFields(this.checked)"' : '';
|
|
2055
|
-
html += '<div class="form-group"><label style="display:flex;align-items:center;gap:8px"><input type="checkbox" id="edit-' + key + '"' + (checked ? ' checked' : '') + changeHandler + '> ' + (labelMap[key] || key) + '</label>';
|
|
2043
|
+
else if (key === 'CHATCCC_CODEX_MODEL') val = state.config.codex.model || '';
|
|
2044
|
+
else if (key === 'CHATCCC_CODEX_ALTERNATIVE_MODEL') val = state.config.codex.alternativeModel || '';
|
|
2045
|
+
else if (key === 'CHATCCC_CODEX_EFFORT') val = state.config.codex.effort || '';
|
|
2046
|
+
else if (key === 'CHATCCC_CODEX_FAST_MODE') val = state.config.codex.fastMode === true ? 'true' : 'false';
|
|
2047
|
+
} else if (section === 'ccc' && state.config.ccc) {
|
|
2048
|
+
if (key === 'CHATCCC_CCC_API_KEY') val = state.config.ccc.DEEPSEEK_API_KEY || '';
|
|
2049
|
+
else if (key === 'CHATCCC_CCC_BASE_URL') val = state.config.ccc.DEEPSEEK_BASE_URL || '';
|
|
2050
|
+
else if (key === 'CHATCCC_CCC_MODEL') val = state.config.ccc.model || '';
|
|
2051
|
+
else if (key === 'CHATCCC_CCC_ALTERNATIVE_MODEL') val = state.config.ccc.alternativeModel || '';
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
var isSecret = key.includes('SECRET') || key.includes('API_KEY');
|
|
2055
|
+
if (key === 'CHATCCC_WEB_UI_OPEN_ON_START' || key === 'CHATCCC_CHROME_DEVTOOLS_ENABLED' || key === 'CHATCCC_CODEX_FAST_MODE') {
|
|
2056
|
+
var checked = val === true || val === 'true';
|
|
2057
|
+
var changeHandler = key === 'CHATCCC_CHROME_DEVTOOLS_ENABLED' ? ' onchange="toggleEditChromeDevtoolsFields(this.checked)"' : '';
|
|
2058
|
+
html += '<div class="form-group"><label style="display:flex;align-items:center;gap:8px"><input type="checkbox" id="edit-' + key + '"' + (checked ? ' checked' : '') + changeHandler + '> ' + (labelMap[key] || key) + '</label>';
|
|
2056
2059
|
if (hintMap[key]) html += '<div class="hint" style="margin-top:6px;line-height:1.5">' + hintMap[key] + '</div>';
|
|
2057
2060
|
html += '</div>';
|
|
2058
2061
|
} else if (key === 'CHATCCC_CURSOR_AVATAR_BATTERY_MODE') {
|
|
@@ -2106,18 +2109,18 @@ function closeEditModal() {
|
|
|
2106
2109
|
editSectionType = null;
|
|
2107
2110
|
}
|
|
2108
2111
|
|
|
2109
|
-
async function saveEdit() {
|
|
2110
|
-
var fields;
|
|
2111
|
-
if (editSectionType === 'feishu') fields = FEISHU_FIELDS;
|
|
2112
|
-
else if (editSectionType === 'webUi') fields = WEB_UI_FIELDS;
|
|
2113
|
-
else if (editSectionType === 'chromeDevtools') fields = CHROME_DEVTOOLS_FIELDS;
|
|
2112
|
+
async function saveEdit() {
|
|
2113
|
+
var fields;
|
|
2114
|
+
if (editSectionType === 'feishu') fields = FEISHU_FIELDS;
|
|
2115
|
+
else if (editSectionType === 'webUi') fields = WEB_UI_FIELDS;
|
|
2116
|
+
else if (editSectionType === 'chromeDevtools') fields = CHROME_DEVTOOLS_FIELDS;
|
|
2114
2117
|
else fields = AGENT_FIELDS[editSectionType] || [];
|
|
2115
2118
|
|
|
2116
2119
|
var vars = {};
|
|
2117
2120
|
fields.forEach(function(key){
|
|
2118
2121
|
var el = document.getElementById('edit-' + key);
|
|
2119
2122
|
if (!el) return;
|
|
2120
|
-
if (key === 'CHATCCC_WEB_UI_OPEN_ON_START' || key === 'CHATCCC_CHROME_DEVTOOLS_ENABLED' || key === 'CHATCCC_CODEX_FAST_MODE') vars[key] = !!el.checked;
|
|
2123
|
+
if (key === 'CHATCCC_WEB_UI_OPEN_ON_START' || key === 'CHATCCC_CHROME_DEVTOOLS_ENABLED' || key === 'CHATCCC_CODEX_FAST_MODE') vars[key] = !!el.checked;
|
|
2121
2124
|
else vars[key] = el.value.trim();
|
|
2122
2125
|
});
|
|
2123
2126
|
if (editSectionType === 'chromeDevtools' && !vars.CHATCCC_CHROME_DEVTOOLS_PORT) {
|
|
@@ -2131,16 +2134,16 @@ async function saveEdit() {
|
|
|
2131
2134
|
var ptEl = document.getElementById('edit-CHATCCC_FEISHU_PLATFORM_TYPE');
|
|
2132
2135
|
if (ptEl && ptEl.value.trim()) vars['CHATCCC_FEISHU_PLATFORM_TYPE'] = ptEl.value.trim();
|
|
2133
2136
|
}
|
|
2134
|
-
var result = await saveConfig(vars, { quiet: true });
|
|
2135
|
-
if (result.ok !== true) return;
|
|
2136
|
-
try {
|
|
2137
|
-
var fresh = await api('/api/config');
|
|
2138
|
-
state.config = fresh.vars || state.config;
|
|
2139
|
-
} catch(e) {}
|
|
2140
|
-
closeEditModal();
|
|
2141
|
-
updateDashboardUI();
|
|
2142
|
-
toastConfigApplyResult(result, '修改已保存');
|
|
2143
|
-
}
|
|
2137
|
+
var result = await saveConfig(vars, { quiet: true });
|
|
2138
|
+
if (result.ok !== true) return;
|
|
2139
|
+
try {
|
|
2140
|
+
var fresh = await api('/api/config');
|
|
2141
|
+
state.config = fresh.vars || state.config;
|
|
2142
|
+
} catch(e) {}
|
|
2143
|
+
closeEditModal();
|
|
2144
|
+
updateDashboardUI();
|
|
2145
|
+
toastConfigApplyResult(result, '修改已保存');
|
|
2146
|
+
}
|
|
2144
2147
|
|
|
2145
2148
|
// ---- Other actions ----
|
|
2146
2149
|
function reconfigure() {
|
|
@@ -2173,10 +2176,10 @@ init();
|
|
|
2173
2176
|
// Router
|
|
2174
2177
|
// ---------------------------------------------------------------------------
|
|
2175
2178
|
|
|
2176
|
-
async function handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {
|
|
2177
|
-
const url = req.url ?? "/";
|
|
2178
|
-
const method = req.method ?? "GET";
|
|
2179
|
-
const pathname = url.split("?", 1)[0];
|
|
2179
|
+
async function handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {
|
|
2180
|
+
const url = req.url ?? "/";
|
|
2181
|
+
const method = req.method ?? "GET";
|
|
2182
|
+
const pathname = url.split("?", 1)[0];
|
|
2180
2183
|
|
|
2181
2184
|
if (extraApiHandler && await extraApiHandler(req, res)) return;
|
|
2182
2185
|
|
|
@@ -2189,13 +2192,13 @@ async function handleRequest(req: IncomingMessage, res: ServerResponse): Promise
|
|
|
2189
2192
|
if (url === "/api/stop" && method === "POST") return handleStopService(req, res);
|
|
2190
2193
|
if (url === "/api/restart" && method === "POST") return handleRestartService(req, res);
|
|
2191
2194
|
if (url === "/api/validate" && method === "POST") return handleValidate(req, res);
|
|
2192
|
-
if (url === "/api/ilink/forget" && method === "POST") return handleForgetIlink(req, res);
|
|
2193
|
-
|
|
2194
|
-
if (method === "GET" && (pathname === "/agent-team" || pathname === "/agent-team/")) {
|
|
2195
|
-
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
2196
|
-
res.end(AGENT_TEAM_PAGE_HTML);
|
|
2197
|
-
return;
|
|
2198
|
-
}
|
|
2195
|
+
if (url === "/api/ilink/forget" && method === "POST") return handleForgetIlink(req, res);
|
|
2196
|
+
|
|
2197
|
+
if (method === "GET" && (pathname === "/agent-team" || pathname === "/agent-team/")) {
|
|
2198
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
2199
|
+
res.end(AGENT_TEAM_PAGE_HTML);
|
|
2200
|
+
return;
|
|
2201
|
+
}
|
|
2199
2202
|
|
|
2200
2203
|
// Serve HTML page for all other GET requests
|
|
2201
2204
|
if (method === "GET") {
|
|
@@ -2220,7 +2223,7 @@ export function createUiRouter(): (req: IncomingMessage, res: ServerResponse) =>
|
|
|
2220
2223
|
// Setup mode entry point — called from index.ts when no credentials
|
|
2221
2224
|
// ---------------------------------------------------------------------------
|
|
2222
2225
|
|
|
2223
|
-
/**
|
|
2226
|
+
/**
|
|
2224
2227
|
* setup → service「在线切换」回调签名:
|
|
2225
2228
|
* - 入参 httpServer:setup 模式当前监听的 HTTP server,会被复用为 service 的
|
|
2226
2229
|
* relay server(避免 close + recreate 的端口竞态)。
|
|
@@ -2232,10 +2235,10 @@ export type SetupActivateHook = (
|
|
|
2232
2235
|
httpServer: ReturnType<typeof createServer>,
|
|
2233
2236
|
) => Promise<{ ok: true } | { ok: false; error: string }>;
|
|
2234
2237
|
|
|
2235
|
-
export interface StartSetupModeOptions {
|
|
2236
|
-
onActivate?: SetupActivateHook;
|
|
2237
|
-
/** 内部重启进入 setup 模式时设为 false,避免重复弹出系统浏览器。 */
|
|
2238
|
-
openBrowser?: boolean;
|
|
2238
|
+
export interface StartSetupModeOptions {
|
|
2239
|
+
onActivate?: SetupActivateHook;
|
|
2240
|
+
/** 内部重启进入 setup 模式时设为 false,避免重复弹出系统浏览器。 */
|
|
2241
|
+
openBrowser?: boolean;
|
|
2239
2242
|
}
|
|
2240
2243
|
|
|
2241
2244
|
// setup HTTP server + onActivate 回调通过模块级变量暴露给 handleStartService。
|
|
@@ -2266,10 +2269,10 @@ export function setExtraApiHandler(handler: ExtraApiHandler): void {
|
|
|
2266
2269
|
extraApiHandler = handler;
|
|
2267
2270
|
}
|
|
2268
2271
|
|
|
2269
|
-
export function startSetupMode(
|
|
2270
|
-
port: number,
|
|
2271
|
-
options: StartSetupModeOptions = {},
|
|
2272
|
-
): ReturnType<typeof createServer> {
|
|
2272
|
+
export function startSetupMode(
|
|
2273
|
+
port: number,
|
|
2274
|
+
options: StartSetupModeOptions = {},
|
|
2275
|
+
): ReturnType<typeof createServer> {
|
|
2273
2276
|
const router = createUiRouter();
|
|
2274
2277
|
const server = createServer(router);
|
|
2275
2278
|
setupHttpServer = server;
|
|
@@ -2285,25 +2288,25 @@ export function startSetupMode(
|
|
|
2285
2288
|
process.exit(1);
|
|
2286
2289
|
});
|
|
2287
2290
|
|
|
2288
|
-
server.listen(port, "127.0.0.1", () => {
|
|
2289
|
-
const url = buildWebUiUrl(port);
|
|
2291
|
+
server.listen(port, "127.0.0.1", () => {
|
|
2292
|
+
const url = buildWebUiUrl(port);
|
|
2290
2293
|
console.log("");
|
|
2291
2294
|
console.log("=".repeat(60));
|
|
2292
2295
|
console.log(" ChatCCC — 首次配置向导");
|
|
2293
2296
|
console.log("=".repeat(60));
|
|
2294
2297
|
console.log(" 未检测到已配置的飞书凭证,已启动配置界面。");
|
|
2295
|
-
if (options.openBrowser !== false) {
|
|
2296
|
-
console.log(` 已启用启动时自动打开浏览器: ${url}`);
|
|
2297
|
-
} else {
|
|
2298
|
-
console.log(` 内部重启不会自动打开浏览器,请按需访问: ${url}`);
|
|
2299
|
-
}
|
|
2298
|
+
if (options.openBrowser !== false) {
|
|
2299
|
+
console.log(` 已启用启动时自动打开浏览器: ${url}`);
|
|
2300
|
+
} else {
|
|
2301
|
+
console.log(` 内部重启不会自动打开浏览器,请按需访问: ${url}`);
|
|
2302
|
+
}
|
|
2300
2303
|
console.log(" 若浏览器未自动弹出,请手动访问上面的地址。");
|
|
2301
2304
|
console.log("");
|
|
2302
2305
|
console.log(" 在向导里填好 App ID / App Secret 后点「保存并启动」,");
|
|
2303
2306
|
console.log(" 服务会在当前进程内直接激活,不需要重新运行 chatccc。");
|
|
2304
2307
|
console.log("=".repeat(60));
|
|
2305
2308
|
console.log("");
|
|
2306
|
-
if (options.openBrowser !== false) openWebUiInDefaultBrowser(port);
|
|
2307
|
-
});
|
|
2308
|
-
return server;
|
|
2309
|
-
}
|
|
2309
|
+
if (options.openBrowser !== false) openWebUiInDefaultBrowser(port);
|
|
2310
|
+
});
|
|
2311
|
+
return server;
|
|
2312
|
+
}
|