ai-agent-router 0.2.1 → 0.2.2
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/.next/BUILD_ID +1 -1
- package/.next/build-manifest.json +2 -2
- package/.next/fallback-build-manifest.json +2 -2
- package/.next/next-server.js.nft.json +1 -1
- package/.next/prerender-manifest.json +3 -3
- package/.next/required-server-files.js +4 -4
- package/.next/required-server-files.json +4 -4
- package/.next/server/app/_global-error.html +2 -2
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page.js +1 -1
- package/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +2 -2
- package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/api/config/route.js.nft.json +1 -1
- package/.next/server/app/api/gateway/[...path]/route.js.nft.json +1 -1
- package/.next/server/app/api/gateway/models/route.js.nft.json +1 -1
- package/.next/server/app/api/gateway/route.js.nft.json +1 -1
- package/.next/server/app/api/ide/claude/apply/route.js.nft.json +1 -1
- package/.next/server/app/api/ide/claude/available-models/route.js.nft.json +1 -1
- package/.next/server/app/api/ide/claude/restore/route.js.nft.json +1 -1
- package/.next/server/app/api/ide/claude/save/route.js.nft.json +1 -1
- package/.next/server/app/api/ide/claude/status/route.js.nft.json +1 -1
- package/.next/server/app/api/ide/claude/test/route.js.nft.json +1 -1
- package/.next/server/app/api/ide/openclaw/apply/route.js.nft.json +1 -1
- package/.next/server/app/api/ide/openclaw/available-models/route.js.nft.json +1 -1
- package/.next/server/app/api/ide/openclaw/preview/route.js.nft.json +1 -1
- package/.next/server/app/api/ide/openclaw/save/route.js.nft.json +1 -1
- package/.next/server/app/api/ide/openclaw/status/route.js.nft.json +1 -1
- package/.next/server/app/api/ide/opencode/apply/route.js.nft.json +1 -1
- package/.next/server/app/api/ide/opencode/available-models/route.js.nft.json +1 -1
- package/.next/server/app/api/ide/opencode/preview/route.js.nft.json +1 -1
- package/.next/server/app/api/ide/opencode/save/route.js.nft.json +1 -1
- package/.next/server/app/api/ide/opencode/status/route.js.nft.json +1 -1
- package/.next/server/app/api/logs/route.js.nft.json +1 -1
- package/.next/server/app/api/models/route.js.nft.json +1 -1
- package/.next/server/app/api/providers/route.js.nft.json +1 -1
- package/.next/server/app/api/providers/test/route.js.nft.json +1 -1
- package/.next/server/app/api/service/force-stop/route.js.nft.json +1 -1
- package/.next/server/app/api/service/start/route.js.nft.json +1 -1
- package/.next/server/app/api/service/status/route.js.nft.json +1 -1
- package/.next/server/app/api/service/stop/route.js.nft.json +1 -1
- package/.next/server/app/ide/page.js +1 -1
- package/.next/server/app/ide/page.js.nft.json +1 -1
- package/.next/server/app/ide/page_client-reference-manifest.js +1 -1
- package/.next/server/app/ide.html +1 -1
- package/.next/server/app/ide.rsc +3 -3
- package/.next/server/app/ide.segments/_full.segment.rsc +3 -3
- package/.next/server/app/ide.segments/_head.segment.rsc +1 -1
- package/.next/server/app/ide.segments/_index.segment.rsc +2 -2
- package/.next/server/app/ide.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/ide.segments/ide/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/ide.segments/ide.segment.rsc +1 -1
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +2 -2
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/index.segments/_full.segment.rsc +2 -2
- package/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/logs/page.js +1 -1
- package/.next/server/app/logs/page.js.nft.json +1 -1
- package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
- package/.next/server/app/logs.html +1 -1
- package/.next/server/app/logs.rsc +2 -2
- package/.next/server/app/logs.segments/_full.segment.rsc +2 -2
- package/.next/server/app/logs.segments/_head.segment.rsc +1 -1
- package/.next/server/app/logs.segments/_index.segment.rsc +2 -2
- package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/logs.segments/logs.segment.rsc +1 -1
- package/.next/server/app/models/page.js +1 -1
- package/.next/server/app/models/page.js.nft.json +1 -1
- package/.next/server/app/models/page_client-reference-manifest.js +1 -1
- package/.next/server/app/models.html +1 -1
- package/.next/server/app/models.rsc +2 -2
- package/.next/server/app/models.segments/_full.segment.rsc +2 -2
- package/.next/server/app/models.segments/_head.segment.rsc +1 -1
- package/.next/server/app/models.segments/_index.segment.rsc +2 -2
- package/.next/server/app/models.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/models.segments/models/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/models.segments/models.segment.rsc +1 -1
- package/.next/server/app/page.js +1 -1
- package/.next/server/app/page.js.nft.json +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/providers/page.js +1 -1
- package/.next/server/app/providers/page.js.nft.json +1 -1
- package/.next/server/app/providers/page_client-reference-manifest.js +1 -1
- package/.next/server/app/providers.html +1 -1
- package/.next/server/app/providers.rsc +2 -2
- package/.next/server/app/providers.segments/_full.segment.rsc +2 -2
- package/.next/server/app/providers.segments/_head.segment.rsc +1 -1
- package/.next/server/app/providers.segments/_index.segment.rsc +2 -2
- package/.next/server/app/providers.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/providers.segments/providers/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/providers.segments/providers.segment.rsc +1 -1
- package/.next/server/chunks/[root-of-the-server]__3a204d25._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__3a204d25._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__4a8f9bc7._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__4a8f9bc7._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__5e8276bc._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__5e8276bc._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__c1b4b601._.js +16 -16
- package/.next/server/chunks/[root-of-the-server]__c1b4b601._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__f0461b8d._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__f0461b8d._.js.map +1 -1
- package/.next/server/chunks/ssr/_69468f4c._.js +3 -0
- package/.next/server/chunks/ssr/_69468f4c._.js.map +1 -0
- package/.next/server/chunks/ssr/src_app_ide_page_tsx_8962793b._.js +1 -1
- package/.next/server/chunks/ssr/src_app_ide_page_tsx_8962793b._.js.map +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +2 -2
- package/.next/server/server-reference-manifest.js +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/6992685fe009e8fd.css +1 -0
- package/.next/static/chunks/8ccc14e022ff6de3.js +1 -0
- package/README.md +31 -4
- package/dist/package.json +55 -0
- package/dist/src/app/api/config/route.js +17 -0
- package/dist/src/app/api/ide/claude/apply/route.js +84 -0
- package/dist/src/app/api/ide/claude/restore/route.js +30 -0
- package/dist/src/app/api/ide/claude/save/route.js +79 -0
- package/dist/src/app/components/Footer.js +11 -0
- package/dist/src/app/ide/page.js +62 -2
- package/dist/src/app/layout.js +5 -1
- package/dist/src/cli/index.js +0 -0
- package/dist/src/db/database.js +21 -2
- package/package.json +1 -1
- package/src/app/api/config/route.ts +17 -0
- package/src/app/api/ide/claude/apply/route.ts +94 -0
- package/src/app/api/ide/claude/restore/route.ts +36 -1
- package/src/app/api/ide/claude/save/route.ts +91 -1
- package/src/app/components/Footer.tsx +9 -0
- package/src/app/ide/page.tsx +71 -5
- package/src/app/layout.tsx +3 -1
- package/src/db/database.ts +27 -2
- package/.next/server/chunks/ssr/src_app_b2b1d928._.js +0 -3
- package/.next/server/chunks/ssr/src_app_b2b1d928._.js.map +0 -1
- package/.next/static/chunks/0f120c117962200b.css +0 -1
- package/.next/static/chunks/8ed839b2e4948968.js +0 -1
- /package/.next/static/{dYin74gcpdlg8TGoGv-_d → cf2SWIkI5HVbnDjLExI42}/_buildManifest.js +0 -0
- /package/.next/static/{dYin74gcpdlg8TGoGv-_d → cf2SWIkI5HVbnDjLExI42}/_clientMiddlewareManifest.json +0 -0
- /package/.next/static/{dYin74gcpdlg8TGoGv-_d → cf2SWIkI5HVbnDjLExI42}/_ssgManifest.js +0 -0
|
@@ -11,6 +11,10 @@ const queries_1 = require("@/db/queries");
|
|
|
11
11
|
const CLAUDE_DIR = (0, path_1.join)((0, os_1.homedir)(), '.claude');
|
|
12
12
|
const SETTINGS_FILE = (0, path_1.join)(CLAUDE_DIR, 'settings.json');
|
|
13
13
|
const BACKUP_FILE = (0, path_1.join)(CLAUDE_DIR, 'settings.json.aar.bak');
|
|
14
|
+
// Claude 登录配置文件(用于 hasCompletedOnboarding)
|
|
15
|
+
const CLAUDE_JSON_FILE = (0, path_1.join)((0, os_1.homedir)(), '.claude.json');
|
|
16
|
+
// AAR 备份目录
|
|
17
|
+
const AAR_BACKUP_DIR = (0, path_1.join)((0, os_1.homedir)(), '.aar', 'backups');
|
|
14
18
|
// 默认模型映射
|
|
15
19
|
const DEFAULT_MODEL_MAPPING = {
|
|
16
20
|
haiku: 'GLM-4.5-air',
|
|
@@ -34,6 +38,15 @@ async function POST(request) {
|
|
|
34
38
|
const gatewayApiKey = await getGatewayApiKey();
|
|
35
39
|
const modelMapping = { haiku, sonnet, opus, default: defaultModel, reasoning: reasoningModel };
|
|
36
40
|
const aarEnv = buildAarEnv(gatewayAddress, gatewayApiKey, modelMapping);
|
|
41
|
+
// 在修改配置前,先备份到 ~/.aar/backups 目录
|
|
42
|
+
const settingsBackup = backupToAar(SETTINGS_FILE, 'settings');
|
|
43
|
+
const claudeJsonBackup = backupToAar(CLAUDE_JSON_FILE, 'claude');
|
|
44
|
+
if (!settingsBackup.success) {
|
|
45
|
+
console.warn('[Claude Apply] Settings backup warning:', settingsBackup.error);
|
|
46
|
+
}
|
|
47
|
+
if (!claudeJsonBackup.success) {
|
|
48
|
+
console.warn('[Claude Apply] Claude.json backup warning:', claudeJsonBackup.error);
|
|
49
|
+
}
|
|
37
50
|
const backupResult = backupOriginalConfig();
|
|
38
51
|
if (!backupResult.success) {
|
|
39
52
|
return server_1.NextResponse.json({ error: backupResult.error }, { status: 500 });
|
|
@@ -62,12 +75,22 @@ async function POST(request) {
|
|
|
62
75
|
if (!validationResult.success) {
|
|
63
76
|
return server_1.NextResponse.json({ error: validationResult.error }, { status: 500 });
|
|
64
77
|
}
|
|
78
|
+
// 处理 ~/.claude.json,确保包含 hasCompletedOnboarding: true
|
|
79
|
+
const onboardingResult = ensureClaudeOnboarding();
|
|
80
|
+
if (!onboardingResult.success) {
|
|
81
|
+
console.warn('[Claude Apply] Onboarding update warning:', onboardingResult.error);
|
|
82
|
+
}
|
|
65
83
|
return server_1.NextResponse.json({
|
|
66
84
|
success: true,
|
|
67
85
|
message: 'Configuration applied successfully',
|
|
68
86
|
config: configToWrite,
|
|
69
87
|
backup: backupResult.existed ? 'Created backup' : 'No existing config to backup',
|
|
70
88
|
previousWasFromAar: backupResult.existed && backupResult.isFromAar,
|
|
89
|
+
onboarding: onboardingResult.created ? 'Created ~/.claude.json' : 'Updated ~/.claude.json',
|
|
90
|
+
aarBackups: {
|
|
91
|
+
settings: settingsBackup.backupPath,
|
|
92
|
+
claudeJson: claudeJsonBackup.backupPath,
|
|
93
|
+
},
|
|
71
94
|
});
|
|
72
95
|
}
|
|
73
96
|
catch (error) {
|
|
@@ -168,3 +191,64 @@ function validateAndWriteConfig(config) {
|
|
|
168
191
|
};
|
|
169
192
|
}
|
|
170
193
|
}
|
|
194
|
+
/**
|
|
195
|
+
* 备份文件到 ~/.aar/backups 目录
|
|
196
|
+
*/
|
|
197
|
+
function backupToAar(filePath, backupName) {
|
|
198
|
+
try {
|
|
199
|
+
if (!(0, fs_1.existsSync)(filePath)) {
|
|
200
|
+
return { success: true }; // 文件不存在,无需备份
|
|
201
|
+
}
|
|
202
|
+
if (!(0, fs_1.existsSync)(AAR_BACKUP_DIR)) {
|
|
203
|
+
(0, fs_1.mkdirSync)(AAR_BACKUP_DIR, { recursive: true });
|
|
204
|
+
}
|
|
205
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
206
|
+
const backupPath = (0, path_1.join)(AAR_BACKUP_DIR, `${backupName}-${timestamp}.json`);
|
|
207
|
+
(0, fs_1.copyFileSync)(filePath, backupPath);
|
|
208
|
+
return { success: true, backupPath };
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
return {
|
|
212
|
+
success: false,
|
|
213
|
+
error: `Backup to AAR failed: ${error.message}`,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* 确保 ~/.claude.json 文件中包含 hasCompletedOnboarding: true
|
|
219
|
+
* 这允许用户跳过 Claude 登录使用自定义模型
|
|
220
|
+
*/
|
|
221
|
+
function ensureClaudeOnboarding() {
|
|
222
|
+
try {
|
|
223
|
+
let config = {};
|
|
224
|
+
let created = false;
|
|
225
|
+
if ((0, fs_1.existsSync)(CLAUDE_JSON_FILE)) {
|
|
226
|
+
try {
|
|
227
|
+
const content = (0, fs_1.readFileSync)(CLAUDE_JSON_FILE, 'utf-8');
|
|
228
|
+
config = JSON.parse(content);
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
// 文件存在但解析失败,使用空对象
|
|
232
|
+
config = {};
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
created = true;
|
|
237
|
+
}
|
|
238
|
+
// 检查是否已经有 hasCompletedOnboarding
|
|
239
|
+
if (config.hasCompletedOnboarding === true) {
|
|
240
|
+
return { success: true, created: false };
|
|
241
|
+
}
|
|
242
|
+
// 添加 hasCompletedOnboarding
|
|
243
|
+
config.hasCompletedOnboarding = true;
|
|
244
|
+
(0, fs_1.writeFileSync)(CLAUDE_JSON_FILE, JSON.stringify(config, null, 2), 'utf-8');
|
|
245
|
+
return { success: true, created };
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
return {
|
|
249
|
+
success: false,
|
|
250
|
+
created: false,
|
|
251
|
+
error: `Failed to update claude.json: ${error.message}`,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
}
|
|
@@ -10,6 +10,8 @@ const os_1 = require("os");
|
|
|
10
10
|
const CLAUDE_DIR = (0, path_1.join)((0, os_1.homedir)(), '.claude');
|
|
11
11
|
const SETTINGS_FILE = (0, path_1.join)(CLAUDE_DIR, 'settings.json');
|
|
12
12
|
const BACKUP_FILE = (0, path_1.join)(CLAUDE_DIR, 'settings.json.aar.bak');
|
|
13
|
+
// AAR 备份目录
|
|
14
|
+
const AAR_BACKUP_DIR = (0, path_1.join)((0, os_1.homedir)(), '.aar', 'backups');
|
|
13
15
|
/**
|
|
14
16
|
* 还原 Claude IDE 配置
|
|
15
17
|
*/
|
|
@@ -26,6 +28,11 @@ async function POST(request) {
|
|
|
26
28
|
if (!(0, fs_1.existsSync)(CLAUDE_DIR)) {
|
|
27
29
|
return server_1.NextResponse.json({ error: 'Claude config directory does not exist' }, { status: 500 });
|
|
28
30
|
}
|
|
31
|
+
// 在还原前,先备份当前配置到 ~/.aar/backups 目录
|
|
32
|
+
const currentBackup = backupToAar(SETTINGS_FILE, 'settings-before-restore');
|
|
33
|
+
if (!currentBackup.success) {
|
|
34
|
+
console.warn('[IDE Restore] Current config backup warning:', currentBackup.error);
|
|
35
|
+
}
|
|
29
36
|
// 将备份内容复制到原配置文件
|
|
30
37
|
(0, fs_1.copyFileSync)(BACKUP_FILE, SETTINGS_FILE);
|
|
31
38
|
// 尝试解析备份内容以验证格式
|
|
@@ -71,3 +78,26 @@ async function DELETE(request) {
|
|
|
71
78
|
return server_1.NextResponse.json({ error: error.message || 'Internal server error', stack: process.env.NODE_ENV === 'development' ? error.stack : undefined }, { status: 500 });
|
|
72
79
|
}
|
|
73
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* 备份文件到 ~/.aar/backups 目录
|
|
83
|
+
*/
|
|
84
|
+
function backupToAar(filePath, backupName) {
|
|
85
|
+
try {
|
|
86
|
+
if (!(0, fs_1.existsSync)(filePath)) {
|
|
87
|
+
return { success: true }; // 文件不存在,无需备份
|
|
88
|
+
}
|
|
89
|
+
if (!(0, fs_1.existsSync)(AAR_BACKUP_DIR)) {
|
|
90
|
+
(0, fs_1.mkdirSync)(AAR_BACKUP_DIR, { recursive: true });
|
|
91
|
+
}
|
|
92
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
93
|
+
const backupPath = (0, path_1.join)(AAR_BACKUP_DIR, `${backupName}-${timestamp}.json`);
|
|
94
|
+
(0, fs_1.copyFileSync)(filePath, backupPath);
|
|
95
|
+
return { success: true, backupPath };
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
return {
|
|
99
|
+
success: false,
|
|
100
|
+
error: `Backup to AAR failed: ${error.message}`,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -10,9 +10,13 @@ const queries_1 = require("@/db/queries");
|
|
|
10
10
|
// Claude settings.json 文件路径
|
|
11
11
|
const CLAUDE_DIR = (0, path_1.join)((0, os_1.homedir)(), '.claude');
|
|
12
12
|
const SETTINGS_FILE = (0, path_1.join)(CLAUDE_DIR, 'settings.json');
|
|
13
|
+
// Claude 登录配置文件(用于 hasCompletedOnboarding)
|
|
14
|
+
const CLAUDE_JSON_FILE = (0, path_1.join)((0, os_1.homedir)(), '.claude.json');
|
|
13
15
|
// AAR 临时配置目录和文件
|
|
14
16
|
const AAR_DIR = (0, path_1.join)((0, os_1.homedir)(), '.aar');
|
|
15
17
|
const TEMP_SETTINGS_FILE = (0, path_1.join)(AAR_DIR, 'settings.tmp.json');
|
|
18
|
+
// AAR 备份目录
|
|
19
|
+
const AAR_BACKUP_DIR = (0, path_1.join)(AAR_DIR, 'backups');
|
|
16
20
|
/**
|
|
17
21
|
* 保存 IDE 配置
|
|
18
22
|
* - 如果当前已应用配置( ~/.claude/settings.json 存在且匹配当前网关),直接更新 settings.json
|
|
@@ -47,6 +51,15 @@ async function POST(request) {
|
|
|
47
51
|
// 已应用当前网关配置,更新 settings.json
|
|
48
52
|
savePath = SETTINGS_FILE;
|
|
49
53
|
saveType = 'applied';
|
|
54
|
+
// 备份到 ~/.aar/backups 目录
|
|
55
|
+
const settingsBackup = backupToAar(SETTINGS_FILE, 'settings');
|
|
56
|
+
const claudeJsonBackup = backupToAar(CLAUDE_JSON_FILE, 'claude');
|
|
57
|
+
if (!settingsBackup.success) {
|
|
58
|
+
console.warn('[IDE Save] Settings backup warning:', settingsBackup.error);
|
|
59
|
+
}
|
|
60
|
+
if (!claudeJsonBackup.success) {
|
|
61
|
+
console.warn('[IDE Save] Claude.json backup warning:', claudeJsonBackup.error);
|
|
62
|
+
}
|
|
50
63
|
mergedConfig = generateClaudeConfig(gatewayAddress, gatewayApiKey, {
|
|
51
64
|
haiku: haiku,
|
|
52
65
|
sonnet: sonnet,
|
|
@@ -54,6 +67,11 @@ async function POST(request) {
|
|
|
54
67
|
default: defaultModel,
|
|
55
68
|
reasoning: reasoningModel
|
|
56
69
|
});
|
|
70
|
+
// 确保 ~/.claude.json 包含 hasCompletedOnboarding: true
|
|
71
|
+
const onboardingResult = ensureClaudeOnboarding();
|
|
72
|
+
if (!onboardingResult.success) {
|
|
73
|
+
console.warn('[IDE Save] Onboarding update warning:', onboardingResult.error);
|
|
74
|
+
}
|
|
57
75
|
}
|
|
58
76
|
else {
|
|
59
77
|
// 未应用网络关配置,保存到临时文件 ~/.aar/settings.tmp.json
|
|
@@ -149,3 +167,64 @@ function generateClaudeConfig(gatewayAddress, gatewayApiKey, modelMapping) {
|
|
|
149
167
|
},
|
|
150
168
|
};
|
|
151
169
|
}
|
|
170
|
+
/**
|
|
171
|
+
* 备份文件到 ~/.aar/backups 目录
|
|
172
|
+
*/
|
|
173
|
+
function backupToAar(filePath, backupName) {
|
|
174
|
+
try {
|
|
175
|
+
if (!(0, fs_1.existsSync)(filePath)) {
|
|
176
|
+
return { success: true }; // 文件不存在,无需备份
|
|
177
|
+
}
|
|
178
|
+
if (!(0, fs_1.existsSync)(AAR_BACKUP_DIR)) {
|
|
179
|
+
(0, fs_1.mkdirSync)(AAR_BACKUP_DIR, { recursive: true });
|
|
180
|
+
}
|
|
181
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
182
|
+
const backupPath = (0, path_1.join)(AAR_BACKUP_DIR, `${backupName}-${timestamp}.json`);
|
|
183
|
+
(0, fs_1.copyFileSync)(filePath, backupPath);
|
|
184
|
+
return { success: true, backupPath };
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
return {
|
|
188
|
+
success: false,
|
|
189
|
+
error: `Backup to AAR failed: ${error.message}`,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* 确保 ~/.claude.json 文件中包含 hasCompletedOnboarding: true
|
|
195
|
+
* 这允许用户跳过 Claude 登录使用自定义模型
|
|
196
|
+
*/
|
|
197
|
+
function ensureClaudeOnboarding() {
|
|
198
|
+
try {
|
|
199
|
+
let config = {};
|
|
200
|
+
let created = false;
|
|
201
|
+
if ((0, fs_1.existsSync)(CLAUDE_JSON_FILE)) {
|
|
202
|
+
try {
|
|
203
|
+
const content = (0, fs_1.readFileSync)(CLAUDE_JSON_FILE, 'utf-8');
|
|
204
|
+
config = JSON.parse(content);
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
// 文件存在但解析失败,使用空对象
|
|
208
|
+
config = {};
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
created = true;
|
|
213
|
+
}
|
|
214
|
+
// 检查是否已经有 hasCompletedOnboarding
|
|
215
|
+
if (config.hasCompletedOnboarding === true) {
|
|
216
|
+
return { success: true, created: false };
|
|
217
|
+
}
|
|
218
|
+
// 添加 hasCompletedOnboarding
|
|
219
|
+
config.hasCompletedOnboarding = true;
|
|
220
|
+
(0, fs_1.writeFileSync)(CLAUDE_JSON_FILE, JSON.stringify(config, null, 2), 'utf-8');
|
|
221
|
+
return { success: true, created };
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
return {
|
|
225
|
+
success: false,
|
|
226
|
+
created: false,
|
|
227
|
+
error: `Failed to update claude.json: ${error.message}`,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.default = Footer;
|
|
7
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
8
|
+
const package_json_1 = __importDefault(require("../../../package.json"));
|
|
9
|
+
function Footer() {
|
|
10
|
+
return ((0, jsx_runtime_1.jsx)("footer", { className: "fixed bottom-0 left-0 right-0 py-2 text-center text-xs text-gray-400 bg-gray-50 border-t border-gray-200", children: (0, jsx_runtime_1.jsxs)("span", { children: ["AAR v", package_json_1.default.version] }) }));
|
|
11
|
+
}
|
package/dist/src/app/ide/page.js
CHANGED
|
@@ -263,6 +263,66 @@ function IDEConfigPage() {
|
|
|
263
263
|
document.addEventListener('mousedown', onDocClick);
|
|
264
264
|
return () => document.removeEventListener('mousedown', onDocClick);
|
|
265
265
|
}, [openClawDropdown]);
|
|
266
|
+
// 同步 Claude 下拉框默认值:确保状态值在可用模型列表中
|
|
267
|
+
(0, react_1.useEffect)(() => {
|
|
268
|
+
const allModels = Object.values(models).flat();
|
|
269
|
+
if (allModels.length === 0)
|
|
270
|
+
return;
|
|
271
|
+
const modelIds = allModels.map(m => m.model_id);
|
|
272
|
+
const firstModelId = allModels[0].model_id;
|
|
273
|
+
// 如果当前状态值不在可用模型列表中,更新为第一个可用模型
|
|
274
|
+
if (!modelIds.includes(haikuModel)) {
|
|
275
|
+
setHaikuModel(firstModelId);
|
|
276
|
+
}
|
|
277
|
+
if (!modelIds.includes(sonnetModel)) {
|
|
278
|
+
setSonnetModel(firstModelId);
|
|
279
|
+
}
|
|
280
|
+
if (!modelIds.includes(opusModel)) {
|
|
281
|
+
setOpusModel(firstModelId);
|
|
282
|
+
}
|
|
283
|
+
if (!modelIds.includes(defaultModel)) {
|
|
284
|
+
setDefaultModel(firstModelId);
|
|
285
|
+
}
|
|
286
|
+
if (!modelIds.includes(reasoningModel)) {
|
|
287
|
+
setReasoningModel(firstModelId);
|
|
288
|
+
}
|
|
289
|
+
}, [models]);
|
|
290
|
+
// 同步 OpenCode 下拉框默认值
|
|
291
|
+
(0, react_1.useEffect)(() => {
|
|
292
|
+
const allModels = Object.values(opencodeModels).flat();
|
|
293
|
+
if (allModels.length === 0)
|
|
294
|
+
return;
|
|
295
|
+
const modelIds = allModels.map(m => m.model_id);
|
|
296
|
+
const firstModelId = allModels[0].model_id;
|
|
297
|
+
if (!modelIds.includes(opencodeDefaultModel)) {
|
|
298
|
+
setOpencodeDefaultModel(firstModelId);
|
|
299
|
+
}
|
|
300
|
+
}, [opencodeModels]);
|
|
301
|
+
// 同步 OpenClaw 下拉框默认值
|
|
302
|
+
(0, react_1.useEffect)(() => {
|
|
303
|
+
const allModels = Object.values(openclawModels).flat();
|
|
304
|
+
if (allModels.length === 0)
|
|
305
|
+
return;
|
|
306
|
+
const modelIds = allModels.map(m => m.model_id);
|
|
307
|
+
const firstModelId = allModels[0].model_id;
|
|
308
|
+
if (!modelIds.includes(openclawDefaultModel)) {
|
|
309
|
+
setOpenclawDefaultModel(firstModelId);
|
|
310
|
+
}
|
|
311
|
+
// 过滤无效的 fallback 模型
|
|
312
|
+
const validFallbacks = openclawDefaultFallbacks.filter(id => modelIds.includes(id));
|
|
313
|
+
if (validFallbacks.length !== openclawDefaultFallbacks.length) {
|
|
314
|
+
setOpenclawDefaultFallbacks(validFallbacks);
|
|
315
|
+
}
|
|
316
|
+
// 检查 image model
|
|
317
|
+
if (openclawImageModel && !modelIds.includes(openclawImageModel)) {
|
|
318
|
+
setOpenclawImageModel('');
|
|
319
|
+
}
|
|
320
|
+
// 过滤无效的 image fallback 模型
|
|
321
|
+
const validImageFallbacks = openclawImageFallbacks.filter(id => modelIds.includes(id));
|
|
322
|
+
if (validImageFallbacks.length !== openclawImageFallbacks.length) {
|
|
323
|
+
setOpenclawImageFallbacks(validImageFallbacks);
|
|
324
|
+
}
|
|
325
|
+
}, [openclawModels]);
|
|
266
326
|
// Claude Handlers
|
|
267
327
|
const handleApply = async () => {
|
|
268
328
|
setApplying(true);
|
|
@@ -645,7 +705,7 @@ function IDEConfigPage() {
|
|
|
645
705
|
? 'text-slate-900'
|
|
646
706
|
: 'text-slate-400 hover:text-slate-600'}`, children: ["OpenCode", activeTab === 'opencode' && ((0, jsx_runtime_1.jsx)("span", { className: "absolute bottom-0 left-4 right-4 h-[2px] bg-slate-900 rounded-full" }))] }), (0, jsx_runtime_1.jsxs)("button", { onClick: () => setActiveTab('openclaw'), className: `relative px-4 py-3.5 text-sm font-medium transition-all duration-200 ${activeTab === 'openclaw'
|
|
647
707
|
? 'text-slate-900'
|
|
648
|
-
: 'text-slate-400 hover:text-slate-600'}`, children: ["OpenClaw", activeTab === 'openclaw' && ((0, jsx_runtime_1.jsx)("span", { className: "absolute bottom-0 left-4 right-4 h-[2px] bg-slate-900 rounded-full" }))] }), (0, jsx_runtime_1.jsx)("button", { onClick: () => setActiveTab('
|
|
708
|
+
: 'text-slate-400 hover:text-slate-600'}`, children: ["OpenClaw", activeTab === 'openclaw' && ((0, jsx_runtime_1.jsx)("span", { className: "absolute bottom-0 left-4 right-4 h-[2px] bg-slate-900 rounded-full" }))] }), (0, jsx_runtime_1.jsx)("button", { onClick: () => setActiveTab('picoclaw'), disabled: true, className: "relative px-4 py-3.5 text-sm font-medium text-slate-300 disabled:cursor-not-allowed", children: (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: ["PicoClaw", (0, jsx_runtime_1.jsx)("span", { className: "px-2 py-0.5 text-[10px] font-medium text-slate-400 bg-slate-100/60 rounded-full border border-slate-200/50", children: "\u5373\u5C06\u63A8\u51FA" })] }) })] }) }), (0, jsx_runtime_1.jsxs)("div", { className: "p-5 sm:p-6", children: [activeTab === 'claude' && ((0, jsx_runtime_1.jsxs)("div", { className: "space-y-6", children: [status && ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between py-3 px-4 bg-white/60 rounded-lg border border-slate-200/50", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3", children: [(0, jsx_runtime_1.jsx)("div", { className: `w-6 h-6 rounded-full flex items-center justify-center ${status.applied
|
|
649
709
|
? 'bg-emerald-100 text-emerald-600'
|
|
650
710
|
: 'bg-slate-100 text-slate-400'}`, children: status.applied ? ((0, jsx_runtime_1.jsx)("svg", { className: "w-3.5 h-3.5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2.5, d: "M5 13l4 4L19 7" }) })) : ((0, jsx_runtime_1.jsx)("svg", { className: "w-3.5 h-3.5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 9v2m0 4h.01" }) })) }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2.5", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-sm font-medium text-slate-700", children: status.applied ? '已配置' : '未配置' }), status.applied && status.routerProvider === 'aar' && status.matchCurrentGateway !== undefined && ((0, jsx_runtime_1.jsx)("span", { className: `inline-flex items-center px-2 py-0.5 text-[10px] font-medium rounded-full border ${status.matchCurrentGateway
|
|
651
711
|
? 'bg-emerald-50/80 text-emerald-700 border-emerald-200/60'
|
|
@@ -794,5 +854,5 @@ function IDEConfigPage() {
|
|
|
794
854
|
? 'bg-white text-slate-800 font-semibold shadow-sm'
|
|
795
855
|
: 'text-slate-500 font-medium hover:bg-slate-200/80 hover:text-slate-600'}`, children: "\u9884\u89C8" }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setOpenclawConfigSubTab('current'), className: `flex-1 px-4 py-2.5 text-[11px] transition-all ${openclawConfigSubTab === 'current'
|
|
796
856
|
? 'bg-white text-slate-800 font-semibold shadow-sm'
|
|
797
|
-
: 'text-slate-500 font-medium hover:bg-slate-200/80 hover:text-slate-600'}`, children: "\u751F\u6548\u4E2D" })] }), (0, jsx_runtime_1.jsx)("div", { className: "relative overflow-hidden rounded-b-lg bg-slate-950 border border-slate-200 border-t-0", children: openclawConfigSubTab === 'preview' ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1.5 px-3 py-2 bg-slate-900 border-b border-slate-800", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex gap-1.5", children: [(0, jsx_runtime_1.jsx)("div", { className: "w-2.5 h-2.5 rounded-full bg-slate-600" }), (0, jsx_runtime_1.jsx)("div", { className: "w-2.5 h-2.5 rounded-full bg-slate-600" }), (0, jsx_runtime_1.jsx)("div", { className: "w-2.5 h-2.5 rounded-full bg-slate-600" })] }), (0, jsx_runtime_1.jsx)("span", { className: "text-[10px] font-medium text-slate-400 ml-2", children: "openclaw.json\uFF08\u5E94\u7528\u540E\u5C06\u5199\u5165\u7684\u5408\u5E76\u7ED3\u679C\uFF09" })] }), openclawPreviewLoading ? ((0, jsx_runtime_1.jsx)("div", { className: "p-3.5 text-[10px] text-slate-400 font-mono", children: "\u52A0\u8F7D\u4E2D..." })) : ((0, jsx_runtime_1.jsx)("textarea", { className: "openclaw-preview-textarea w-full min-h-[12rem] p-3.5 text-[10px] text-slate-300 font-mono bg-slate-950 border-0 focus:ring-0 focus:ring-offset-0 focus:outline-none resize-y max-h-96 overflow-y-auto placeholder:text-slate-500\n [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-track]:bg-slate-900 [&::-webkit-scrollbar-thumb]:bg-slate-600 [&::-webkit-scrollbar-thumb]:rounded-full", placeholder: "\u6682\u65E0\u9884\u89C8", spellCheck: false, value: openclawPreviewText, onChange: (e) => setOpenclawPreviewText(e.target.value) }))] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1.5 px-3 py-2 bg-slate-900 border-b border-slate-800", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex gap-1.5", children: [(0, jsx_runtime_1.jsx)("div", { className: "w-2.5 h-2.5 rounded-full bg-slate-600" }), (0, jsx_runtime_1.jsx)("div", { className: "w-2.5 h-2.5 rounded-full bg-slate-600" }), (0, jsx_runtime_1.jsx)("div", { className: "w-2.5 h-2.5 rounded-full bg-slate-600" })] }), (0, jsx_runtime_1.jsx)("span", { className: "text-[10px] font-medium text-slate-400 ml-2", children: "openclaw.json\uFF08\u5F53\u524D\u78C1\u76D8\u5185\u5BB9\uFF0C\u8FD8\u539F\u540E\u5373\u66F4\u65B0\uFF09" })] }), openclawStatus?.config != null ? ((0, jsx_runtime_1.jsx)("pre", { className: "w-full min-h-[12rem] p-3.5 text-[10px] text-slate-300 font-mono overflow-auto max-h-96 [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-track]:bg-slate-900 [&::-webkit-scrollbar-thumb]:bg-slate-600 [&::-webkit-scrollbar-thumb]:rounded-full", children: JSON.stringify(openclawStatus.config, null, 2) })) : ((0, jsx_runtime_1.jsx)("div", { className: "p-3.5 text-[10px] text-slate-500 font-mono", children: "\u6682\u65E0\u914D\u7F6E\u6587\u4EF6" }))] })) })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "rounded-lg border border-slate-200/50 bg-slate-50/60 px-4 py-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2 mb-2.5", children: [(0, jsx_runtime_1.jsx)("svg", { className: "w-4 h-4 text-slate-600", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" }) }), (0, jsx_runtime_1.jsx)("h3", { className: "text-xs font-semibold text-slate-700", children: "\u4F7F\u7528\u8BF4\u660E" })] }), (0, jsx_runtime_1.jsxs)("ul", { className: "space-y-1.5 text-[10px] text-slate-600", children: [(0, jsx_runtime_1.jsxs)("li", { className: "flex gap-2", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-slate-400", children: "1." }), (0, jsx_runtime_1.jsx)("span", { children: "\u300C\u751F\u6548\u4E2D\u300D\u4E3A\u5F53\u524D\u78C1\u76D8\u4E0A\u7684 openclaw.json\uFF1B\u300C\u9884\u89C8\u300D\u4E3A\u5E94\u7528\u540E\u5C06\u5199\u5165\u7684\u5408\u5E76\u7ED3\u679C\uFF08\u542B AAR \u7F51\u5173\uFF09" })] }), (0, jsx_runtime_1.jsxs)("li", { className: "flex gap-2", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-slate-400", children: "2." }), (0, jsx_runtime_1.jsx)("span", { children: "\u9009\u62E9\u9ED8\u8BA4\u6A21\u578B\u7B49\u540E\uFF0C\u5728\u300C\u9884\u89C8\u300D\u4E2D\u67E5\u770B\u5408\u5E76\u7ED3\u679C\uFF0C\u70B9\u51FB\u300C\u5E94\u7528\u914D\u7F6E\u300D\u5199\u5165" })] }), (0, jsx_runtime_1.jsxs)("li", { className: "flex gap-2", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-slate-400", children: "3." }), (0, jsx_runtime_1.jsx)("span", { children: "\u914D\u7F6E\u6587\u4EF6\u4F4D\u4E8E ~/.openclaw/openclaw.json" })] }), (0, jsx_runtime_1.jsxs)("li", { className: "flex gap-2", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-slate-400", children: "4." }), (0, jsx_runtime_1.jsx)("span", { children: "\u300C\u8FD8\u539F\u914D\u7F6E\u300D\u6062\u590D\u4E3A\u5E94\u7528\u524D\u7684\u5907\u4EFD\uFF1B\u8FD8\u539F\u540E\u8BF7\u5728\u300C\u751F\u6548\u4E2D\u300D\u67E5\u770B\u5F53\u524D\u5185\u5BB9" })] })] })] })] })), activeTab === '
|
|
857
|
+
: 'text-slate-500 font-medium hover:bg-slate-200/80 hover:text-slate-600'}`, children: "\u751F\u6548\u4E2D" })] }), (0, jsx_runtime_1.jsx)("div", { className: "relative overflow-hidden rounded-b-lg bg-slate-950 border border-slate-200 border-t-0", children: openclawConfigSubTab === 'preview' ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1.5 px-3 py-2 bg-slate-900 border-b border-slate-800", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex gap-1.5", children: [(0, jsx_runtime_1.jsx)("div", { className: "w-2.5 h-2.5 rounded-full bg-slate-600" }), (0, jsx_runtime_1.jsx)("div", { className: "w-2.5 h-2.5 rounded-full bg-slate-600" }), (0, jsx_runtime_1.jsx)("div", { className: "w-2.5 h-2.5 rounded-full bg-slate-600" })] }), (0, jsx_runtime_1.jsx)("span", { className: "text-[10px] font-medium text-slate-400 ml-2", children: "openclaw.json\uFF08\u5E94\u7528\u540E\u5C06\u5199\u5165\u7684\u5408\u5E76\u7ED3\u679C\uFF09" })] }), openclawPreviewLoading ? ((0, jsx_runtime_1.jsx)("div", { className: "p-3.5 text-[10px] text-slate-400 font-mono", children: "\u52A0\u8F7D\u4E2D..." })) : ((0, jsx_runtime_1.jsx)("textarea", { className: "openclaw-preview-textarea w-full min-h-[12rem] p-3.5 text-[10px] text-slate-300 font-mono bg-slate-950 border-0 focus:ring-0 focus:ring-offset-0 focus:outline-none resize-y max-h-96 overflow-y-auto placeholder:text-slate-500\n [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-track]:bg-slate-900 [&::-webkit-scrollbar-thumb]:bg-slate-600 [&::-webkit-scrollbar-thumb]:rounded-full", placeholder: "\u6682\u65E0\u9884\u89C8", spellCheck: false, value: openclawPreviewText, onChange: (e) => setOpenclawPreviewText(e.target.value) }))] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1.5 px-3 py-2 bg-slate-900 border-b border-slate-800", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex gap-1.5", children: [(0, jsx_runtime_1.jsx)("div", { className: "w-2.5 h-2.5 rounded-full bg-slate-600" }), (0, jsx_runtime_1.jsx)("div", { className: "w-2.5 h-2.5 rounded-full bg-slate-600" }), (0, jsx_runtime_1.jsx)("div", { className: "w-2.5 h-2.5 rounded-full bg-slate-600" })] }), (0, jsx_runtime_1.jsx)("span", { className: "text-[10px] font-medium text-slate-400 ml-2", children: "openclaw.json\uFF08\u5F53\u524D\u78C1\u76D8\u5185\u5BB9\uFF0C\u8FD8\u539F\u540E\u5373\u66F4\u65B0\uFF09" })] }), openclawStatus?.config != null ? ((0, jsx_runtime_1.jsx)("pre", { className: "w-full min-h-[12rem] p-3.5 text-[10px] text-slate-300 font-mono overflow-auto max-h-96 [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-track]:bg-slate-900 [&::-webkit-scrollbar-thumb]:bg-slate-600 [&::-webkit-scrollbar-thumb]:rounded-full", children: JSON.stringify(openclawStatus.config, null, 2) })) : ((0, jsx_runtime_1.jsx)("div", { className: "p-3.5 text-[10px] text-slate-500 font-mono", children: "\u6682\u65E0\u914D\u7F6E\u6587\u4EF6" }))] })) })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "rounded-lg border border-slate-200/50 bg-slate-50/60 px-4 py-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2 mb-2.5", children: [(0, jsx_runtime_1.jsx)("svg", { className: "w-4 h-4 text-slate-600", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" }) }), (0, jsx_runtime_1.jsx)("h3", { className: "text-xs font-semibold text-slate-700", children: "\u4F7F\u7528\u8BF4\u660E" })] }), (0, jsx_runtime_1.jsxs)("ul", { className: "space-y-1.5 text-[10px] text-slate-600", children: [(0, jsx_runtime_1.jsxs)("li", { className: "flex gap-2", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-slate-400", children: "1." }), (0, jsx_runtime_1.jsx)("span", { children: "\u300C\u751F\u6548\u4E2D\u300D\u4E3A\u5F53\u524D\u78C1\u76D8\u4E0A\u7684 openclaw.json\uFF1B\u300C\u9884\u89C8\u300D\u4E3A\u5E94\u7528\u540E\u5C06\u5199\u5165\u7684\u5408\u5E76\u7ED3\u679C\uFF08\u542B AAR \u7F51\u5173\uFF09" })] }), (0, jsx_runtime_1.jsxs)("li", { className: "flex gap-2", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-slate-400", children: "2." }), (0, jsx_runtime_1.jsx)("span", { children: "\u9009\u62E9\u9ED8\u8BA4\u6A21\u578B\u7B49\u540E\uFF0C\u5728\u300C\u9884\u89C8\u300D\u4E2D\u67E5\u770B\u5408\u5E76\u7ED3\u679C\uFF0C\u70B9\u51FB\u300C\u5E94\u7528\u914D\u7F6E\u300D\u5199\u5165" })] }), (0, jsx_runtime_1.jsxs)("li", { className: "flex gap-2", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-slate-400", children: "3." }), (0, jsx_runtime_1.jsx)("span", { children: "\u914D\u7F6E\u6587\u4EF6\u4F4D\u4E8E ~/.openclaw/openclaw.json" })] }), (0, jsx_runtime_1.jsxs)("li", { className: "flex gap-2", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-slate-400", children: "4." }), (0, jsx_runtime_1.jsx)("span", { children: "\u300C\u8FD8\u539F\u914D\u7F6E\u300D\u6062\u590D\u4E3A\u5E94\u7528\u524D\u7684\u5907\u4EFD\uFF1B\u8FD8\u539F\u540E\u8BF7\u5728\u300C\u751F\u6548\u4E2D\u300D\u67E5\u770B\u5F53\u524D\u5185\u5BB9" })] })] })] })] })), activeTab === 'picoclaw' && ((0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col items-center justify-center py-12 text-center", children: [(0, jsx_runtime_1.jsx)("div", { className: "w-16 h-16 bg-slate-100 rounded-full flex items-center justify-center mb-4", children: (0, jsx_runtime_1.jsx)("svg", { className: "w-8 h-8 text-slate-400", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" }) }) }), (0, jsx_runtime_1.jsx)("h3", { className: "text-sm font-medium text-slate-900 mb-1", children: "PicoClaw \u652F\u6301\u5373\u5C06\u63A8\u51FA" }), (0, jsx_runtime_1.jsx)("p", { className: "text-xs text-slate-500 max-w-xs", children: "\u6211\u4EEC\u5C06\u5F88\u5FEB\u6DFB\u52A0\u5BF9 PicoClaw \u7684\u652F\u6301\uFF0C\u656C\u8BF7\u671F\u5F85\u3002" })] }))] })] })] })] }));
|
|
798
858
|
}
|
package/dist/src/app/layout.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.metadata = void 0;
|
|
4
7
|
exports.default = RootLayout;
|
|
5
8
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
6
9
|
require("./globals.css");
|
|
7
10
|
const ToastProvider_1 = require("./components/ToastProvider");
|
|
11
|
+
const Footer_1 = __importDefault(require("./components/Footer"));
|
|
8
12
|
exports.metadata = {
|
|
9
13
|
title: 'AAR - AI Agent Router',
|
|
10
14
|
description: 'Unified API gateway for managing multiple AI model providers',
|
|
11
15
|
};
|
|
12
16
|
function RootLayout({ children, }) {
|
|
13
|
-
return ((0, jsx_runtime_1.jsx)("html", { lang: "zh-CN", suppressHydrationWarning: true, children: (0, jsx_runtime_1.
|
|
17
|
+
return ((0, jsx_runtime_1.jsx)("html", { lang: "zh-CN", suppressHydrationWarning: true, children: (0, jsx_runtime_1.jsxs)("body", { className: "pb-10", children: [(0, jsx_runtime_1.jsx)(ToastProvider_1.ToastProvider, { children: children }), (0, jsx_runtime_1.jsx)(Footer_1.default, {})] }) }));
|
|
14
18
|
}
|
package/dist/src/cli/index.js
CHANGED
|
File without changes
|
package/dist/src/db/database.js
CHANGED
|
@@ -14,7 +14,14 @@ const schema_1 = require("./schema");
|
|
|
14
14
|
const path_1 = __importDefault(require("path"));
|
|
15
15
|
const fs_1 = __importDefault(require("fs"));
|
|
16
16
|
const os_1 = __importDefault(require("os"));
|
|
17
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
17
18
|
const DB_PATH = process.env.DB_PATH || path_1.default.join(os_1.default.homedir(), '.aar', 'gateway.db');
|
|
19
|
+
/**
|
|
20
|
+
* 生成随机的 API Key(用于网关密码默认值)
|
|
21
|
+
*/
|
|
22
|
+
function generateRandomApiKey() {
|
|
23
|
+
return `aar-${crypto_1.default.randomBytes(16).toString('hex')}`;
|
|
24
|
+
}
|
|
18
25
|
// Use hardcoded path for the built/distributed WASM file
|
|
19
26
|
// Next.js copies sql-wasm.wasm to its chunks directory
|
|
20
27
|
function findSqlJsWasmPath() {
|
|
@@ -104,6 +111,7 @@ async function getDatabase() {
|
|
|
104
111
|
if (!dbInstance) {
|
|
105
112
|
const engine = await initSqlJsEngine();
|
|
106
113
|
const loaded = loadDatabase();
|
|
114
|
+
let isNewDatabase = false;
|
|
107
115
|
if (loaded) {
|
|
108
116
|
dbInstance = loaded;
|
|
109
117
|
if (fs_1.default.existsSync(DB_PATH))
|
|
@@ -112,7 +120,9 @@ async function getDatabase() {
|
|
|
112
120
|
else {
|
|
113
121
|
dbInstance = new engine.Database();
|
|
114
122
|
lastLoadMtimeMs = 0;
|
|
123
|
+
isNewDatabase = true; // 标记为新数据库
|
|
115
124
|
}
|
|
125
|
+
let migrationPerformed = false;
|
|
116
126
|
try {
|
|
117
127
|
dbInstance.run(schema_1.CREATE_TABLES_SQL);
|
|
118
128
|
// Migration: Allow NULL model_id in request_logs table
|
|
@@ -144,7 +154,7 @@ async function getDatabase() {
|
|
|
144
154
|
dbInstance.run(`INSERT INTO request_logs_new SELECT * FROM request_logs;`);
|
|
145
155
|
dbInstance.run(`DROP TABLE request_logs;`);
|
|
146
156
|
dbInstance.run(`ALTER TABLE request_logs_new RENAME TO request_logs;`);
|
|
147
|
-
|
|
157
|
+
migrationPerformed = true;
|
|
148
158
|
console.log('[Database] Migration completed successfully');
|
|
149
159
|
}
|
|
150
160
|
}
|
|
@@ -157,7 +167,16 @@ async function getDatabase() {
|
|
|
157
167
|
console.warn('[Database] Migration warning (non-critical):', migrationError.message);
|
|
158
168
|
}
|
|
159
169
|
}
|
|
160
|
-
|
|
170
|
+
// 只在新数据库创建或执行了 migration 时才保存,避免覆盖现有数据
|
|
171
|
+
if (isNewDatabase || migrationPerformed) {
|
|
172
|
+
// 为新数据库初始化默认的 api_key
|
|
173
|
+
if (isNewDatabase) {
|
|
174
|
+
const defaultApiKey = generateRandomApiKey();
|
|
175
|
+
dbInstance.run(`INSERT OR IGNORE INTO config (key, value, updated_at) VALUES ('api_key', ?, datetime('now'))`, [defaultApiKey]);
|
|
176
|
+
console.log('[Database] Generated default API key for new database');
|
|
177
|
+
}
|
|
178
|
+
saveDatabase(dbInstance);
|
|
179
|
+
}
|
|
161
180
|
}
|
|
162
181
|
catch (schemaError) {
|
|
163
182
|
if (!schemaError.message.includes('already exists') &&
|
package/package.json
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
2
|
import { getDatabase } from '@/db/database';
|
|
3
3
|
import { getConfig, setConfig, getAllConfig } from '@/db/queries';
|
|
4
|
+
import crypto from 'crypto';
|
|
4
5
|
|
|
5
6
|
// Ensure Node.js runtime (required for SQLite)
|
|
6
7
|
export const runtime = 'nodejs';
|
|
7
8
|
|
|
9
|
+
/**
|
|
10
|
+
* 生成随机的 API Key(用于网关密码默认值)
|
|
11
|
+
*/
|
|
12
|
+
function generateRandomApiKey(): string {
|
|
13
|
+
return `aar-${crypto.randomBytes(16).toString('hex')}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
8
16
|
export async function GET(request: NextRequest) {
|
|
9
17
|
try {
|
|
10
18
|
// Initialize database on request (safer than module load)
|
|
@@ -25,6 +33,15 @@ export async function GET(request: NextRequest) {
|
|
|
25
33
|
}
|
|
26
34
|
|
|
27
35
|
const configs = await getAllConfig();
|
|
36
|
+
|
|
37
|
+
// 如果 api_key 为空,生成一个随机默认值
|
|
38
|
+
if (!configs.api_key) {
|
|
39
|
+
const defaultApiKey = generateRandomApiKey();
|
|
40
|
+
await setConfig('api_key', defaultApiKey);
|
|
41
|
+
configs.api_key = defaultApiKey;
|
|
42
|
+
console.log('[Config API] Generated default API key');
|
|
43
|
+
}
|
|
44
|
+
|
|
28
45
|
return NextResponse.json(configs);
|
|
29
46
|
} catch (error: any) {
|
|
30
47
|
console.error('Config API error:', error);
|
|
@@ -10,6 +10,12 @@ const CLAUDE_DIR = join(homedir(), '.claude');
|
|
|
10
10
|
const SETTINGS_FILE = join(CLAUDE_DIR, 'settings.json');
|
|
11
11
|
const BACKUP_FILE = join(CLAUDE_DIR, 'settings.json.aar.bak');
|
|
12
12
|
|
|
13
|
+
// Claude 登录配置文件(用于 hasCompletedOnboarding)
|
|
14
|
+
const CLAUDE_JSON_FILE = join(homedir(), '.claude.json');
|
|
15
|
+
|
|
16
|
+
// AAR 备份目录
|
|
17
|
+
const AAR_BACKUP_DIR = join(homedir(), '.aar', 'backups');
|
|
18
|
+
|
|
13
19
|
// 默认模型映射
|
|
14
20
|
const DEFAULT_MODEL_MAPPING = {
|
|
15
21
|
haiku: 'GLM-4.5-air',
|
|
@@ -41,6 +47,17 @@ export async function POST(request: NextRequest) {
|
|
|
41
47
|
const modelMapping = { haiku, sonnet, opus, default: defaultModel, reasoning: reasoningModel };
|
|
42
48
|
const aarEnv = buildAarEnv(gatewayAddress, gatewayApiKey, modelMapping);
|
|
43
49
|
|
|
50
|
+
// 在修改配置前,先备份到 ~/.aar/backups 目录
|
|
51
|
+
const settingsBackup = backupToAar(SETTINGS_FILE, 'settings');
|
|
52
|
+
const claudeJsonBackup = backupToAar(CLAUDE_JSON_FILE, 'claude');
|
|
53
|
+
|
|
54
|
+
if (!settingsBackup.success) {
|
|
55
|
+
console.warn('[Claude Apply] Settings backup warning:', settingsBackup.error);
|
|
56
|
+
}
|
|
57
|
+
if (!claudeJsonBackup.success) {
|
|
58
|
+
console.warn('[Claude Apply] Claude.json backup warning:', claudeJsonBackup.error);
|
|
59
|
+
}
|
|
60
|
+
|
|
44
61
|
const backupResult = backupOriginalConfig();
|
|
45
62
|
if (!backupResult.success) {
|
|
46
63
|
return NextResponse.json(
|
|
@@ -77,12 +94,23 @@ export async function POST(request: NextRequest) {
|
|
|
77
94
|
);
|
|
78
95
|
}
|
|
79
96
|
|
|
97
|
+
// 处理 ~/.claude.json,确保包含 hasCompletedOnboarding: true
|
|
98
|
+
const onboardingResult = ensureClaudeOnboarding();
|
|
99
|
+
if (!onboardingResult.success) {
|
|
100
|
+
console.warn('[Claude Apply] Onboarding update warning:', onboardingResult.error);
|
|
101
|
+
}
|
|
102
|
+
|
|
80
103
|
return NextResponse.json({
|
|
81
104
|
success: true,
|
|
82
105
|
message: 'Configuration applied successfully',
|
|
83
106
|
config: configToWrite,
|
|
84
107
|
backup: backupResult.existed ? 'Created backup' : 'No existing config to backup',
|
|
85
108
|
previousWasFromAar: backupResult.existed && backupResult.isFromAar,
|
|
109
|
+
onboarding: onboardingResult.created ? 'Created ~/.claude.json' : 'Updated ~/.claude.json',
|
|
110
|
+
aarBackups: {
|
|
111
|
+
settings: settingsBackup.backupPath,
|
|
112
|
+
claudeJson: claudeJsonBackup.backupPath,
|
|
113
|
+
},
|
|
86
114
|
});
|
|
87
115
|
} catch (error: any) {
|
|
88
116
|
console.error('IDE Apply API error:', error);
|
|
@@ -204,3 +232,69 @@ function validateAndWriteConfig(config: any): { success: boolean; error?: string
|
|
|
204
232
|
};
|
|
205
233
|
}
|
|
206
234
|
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* 备份文件到 ~/.aar/backups 目录
|
|
238
|
+
*/
|
|
239
|
+
function backupToAar(filePath: string, backupName: string): { success: boolean; backupPath?: string; error?: string } {
|
|
240
|
+
try {
|
|
241
|
+
if (!existsSync(filePath)) {
|
|
242
|
+
return { success: true }; // 文件不存在,无需备份
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (!existsSync(AAR_BACKUP_DIR)) {
|
|
246
|
+
mkdirSync(AAR_BACKUP_DIR, { recursive: true });
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
250
|
+
const backupPath = join(AAR_BACKUP_DIR, `${backupName}-${timestamp}.json`);
|
|
251
|
+
copyFileSync(filePath, backupPath);
|
|
252
|
+
|
|
253
|
+
return { success: true, backupPath };
|
|
254
|
+
} catch (error: any) {
|
|
255
|
+
return {
|
|
256
|
+
success: false,
|
|
257
|
+
error: `Backup to AAR failed: ${error.message}`,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* 确保 ~/.claude.json 文件中包含 hasCompletedOnboarding: true
|
|
264
|
+
* 这允许用户跳过 Claude 登录使用自定义模型
|
|
265
|
+
*/
|
|
266
|
+
function ensureClaudeOnboarding(): { success: boolean; created: boolean; error?: string } {
|
|
267
|
+
try {
|
|
268
|
+
let config: any = {};
|
|
269
|
+
let created = false;
|
|
270
|
+
|
|
271
|
+
if (existsSync(CLAUDE_JSON_FILE)) {
|
|
272
|
+
try {
|
|
273
|
+
const content = readFileSync(CLAUDE_JSON_FILE, 'utf-8');
|
|
274
|
+
config = JSON.parse(content);
|
|
275
|
+
} catch {
|
|
276
|
+
// 文件存在但解析失败,使用空对象
|
|
277
|
+
config = {};
|
|
278
|
+
}
|
|
279
|
+
} else {
|
|
280
|
+
created = true;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// 检查是否已经有 hasCompletedOnboarding
|
|
284
|
+
if (config.hasCompletedOnboarding === true) {
|
|
285
|
+
return { success: true, created: false };
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// 添加 hasCompletedOnboarding
|
|
289
|
+
config.hasCompletedOnboarding = true;
|
|
290
|
+
writeFileSync(CLAUDE_JSON_FILE, JSON.stringify(config, null, 2), 'utf-8');
|
|
291
|
+
|
|
292
|
+
return { success: true, created };
|
|
293
|
+
} catch (error: any) {
|
|
294
|
+
return {
|
|
295
|
+
success: false,
|
|
296
|
+
created: false,
|
|
297
|
+
error: `Failed to update claude.json: ${error.message}`,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
}
|