ai-agent-router 0.1.0
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/.claude/commands/openspec/apply.md +23 -0
- package/.claude/commands/openspec/archive.md +27 -0
- package/.claude/commands/openspec/proposal.md +28 -0
- package/.claude/settings.local.json +12 -0
- package/.claude/skills/ui-ux-pro-max/SKILL.md +228 -0
- package/.claude/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/.claude/skills/ui-ux-pro-max/data/colors.csv +97 -0
- package/.claude/skills/ui-ux-pro-max/data/landing.csv +31 -0
- package/.claude/skills/ui-ux-pro-max/data/products.csv +97 -0
- package/.claude/skills/ui-ux-pro-max/data/prompts.csv +24 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/.claude/skills/ui-ux-pro-max/data/styles.csv +59 -0
- package/.claude/skills/ui-ux-pro-max/data/typography.csv +58 -0
- package/.claude/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/.claude/skills/ui-ux-pro-max/scripts/__pycache__/core.cpython-311.pyc +0 -0
- package/.claude/skills/ui-ux-pro-max/scripts/core.py +238 -0
- package/.claude/skills/ui-ux-pro-max/scripts/search.py +61 -0
- package/.cursor/commands/openspec-apply.md +23 -0
- package/.cursor/commands/openspec-archive.md +27 -0
- package/.cursor/commands/openspec-proposal.md +28 -0
- package/.cursor/commands/ui-ux-pro-max.md +226 -0
- package/.eslintrc.json +3 -0
- package/.shared/ui-ux-pro-max/data/charts.csv +26 -0
- package/.shared/ui-ux-pro-max/data/colors.csv +97 -0
- package/.shared/ui-ux-pro-max/data/landing.csv +31 -0
- package/.shared/ui-ux-pro-max/data/products.csv +97 -0
- package/.shared/ui-ux-pro-max/data/prompts.csv +24 -0
- package/.shared/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/.shared/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/.shared/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/.shared/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/.shared/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/.shared/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/.shared/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/.shared/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/.shared/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/.shared/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/.shared/ui-ux-pro-max/data/styles.csv +59 -0
- package/.shared/ui-ux-pro-max/data/typography.csv +58 -0
- package/.shared/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/.shared/ui-ux-pro-max/scripts/core.py +238 -0
- package/.shared/ui-ux-pro-max/scripts/search.py +61 -0
- package/AGENTS.md +18 -0
- package/CLAUDE.md +18 -0
- package/IMPLEMENTATION.md +157 -0
- package/LICENSE +21 -0
- package/README.md +165 -0
- package/dist/.next/types/app/api/config/route.js +52 -0
- package/dist/.next/types/app/api/gateway/[...path]/route.js +52 -0
- package/dist/.next/types/app/api/gateway/route.js +52 -0
- package/dist/.next/types/app/api/logs/route.js +52 -0
- package/dist/.next/types/app/api/models/route.js +52 -0
- package/dist/.next/types/app/api/providers/route.js +52 -0
- package/dist/.next/types/app/api/providers/test/route.js +52 -0
- package/dist/.next/types/app/api/service/start/route.js +52 -0
- package/dist/.next/types/app/api/service/status/route.js +52 -0
- package/dist/.next/types/app/api/service/stop/route.js +52 -0
- package/dist/.next/types/app/layout.js +22 -0
- package/dist/.next/types/app/logs/page.js +22 -0
- package/dist/.next/types/app/models/page.js +22 -0
- package/dist/.next/types/app/page.js +22 -0
- package/dist/.next/types/app/providers/page.js +22 -0
- package/dist/src/app/api/config/route.js +43 -0
- package/dist/src/app/api/gateway/[...path]/route.js +83 -0
- package/dist/src/app/api/gateway/route.js +63 -0
- package/dist/src/app/api/logs/route.js +34 -0
- package/dist/src/app/api/models/route.js +152 -0
- package/dist/src/app/api/providers/route.js +118 -0
- package/dist/src/app/api/providers/test/route.js +154 -0
- package/dist/src/app/api/service/start/route.js +55 -0
- package/dist/src/app/api/service/status/route.js +17 -0
- package/dist/src/app/api/service/stop/route.js +20 -0
- package/dist/src/app/components/ConfirmDialog.jsx +31 -0
- package/dist/src/app/components/Nav.jsx +45 -0
- package/dist/src/app/components/Toast.jsx +37 -0
- package/dist/src/app/components/ToastProvider.jsx +21 -0
- package/dist/src/app/layout.jsx +13 -0
- package/dist/src/app/logs/page.jsx +210 -0
- package/dist/src/app/models/page.jsx +291 -0
- package/dist/src/app/page.jsx +236 -0
- package/dist/src/app/providers/page.jsx +402 -0
- package/dist/src/cli/index.js +90 -0
- package/dist/src/db/database.js +69 -0
- package/dist/src/db/queries.js +261 -0
- package/dist/src/db/schema.js +67 -0
- package/dist/src/server/crypto.js +22 -0
- package/dist/src/server/gateway-server.js +200 -0
- package/dist/src/server/gateway.js +76 -0
- package/dist/src/server/logger.js +72 -0
- package/dist/src/server/providers/anthropic.js +52 -0
- package/dist/src/server/providers/gemini.js +64 -0
- package/dist/src/server/providers/index.js +16 -0
- package/dist/src/server/providers/openai.js +86 -0
- package/dist/src/server/providers/types.js +1 -0
- package/dist/src/server/service-manager.js +286 -0
- package/docs/TODO.md +19 -0
- package/next.config.js +7 -0
- package/openspec/AGENTS.md +456 -0
- package/openspec/changes/add-logging/proposal.md +18 -0
- package/openspec/changes/add-logging/specs/core/spec.md +21 -0
- package/openspec/changes/add-logging/tasks.md +16 -0
- package/openspec/changes/add-provider-test-connection/proposal.md +22 -0
- package/openspec/changes/add-provider-test-connection/specs/model-provider/spec.md +68 -0
- package/openspec/changes/add-provider-test-connection/tasks.md +31 -0
- package/openspec/changes/improve-gateway-startup/design.md +137 -0
- package/openspec/changes/improve-gateway-startup/proposal.md +33 -0
- package/openspec/changes/improve-gateway-startup/specs/api-gateway/spec.md +94 -0
- package/openspec/changes/improve-gateway-startup/specs/web-ui/spec.md +67 -0
- package/openspec/changes/improve-gateway-startup/tasks.md +47 -0
- package/openspec/changes/init-api-gateway/design.md +185 -0
- package/openspec/changes/init-api-gateway/proposal.md +30 -0
- package/openspec/changes/init-api-gateway/specs/api-gateway/spec.md +42 -0
- package/openspec/changes/init-api-gateway/specs/cli-tool/spec.md +40 -0
- package/openspec/changes/init-api-gateway/specs/model-management/spec.md +47 -0
- package/openspec/changes/init-api-gateway/specs/model-provider/spec.md +33 -0
- package/openspec/changes/init-api-gateway/specs/request-logging/spec.md +54 -0
- package/openspec/changes/init-api-gateway/specs/web-ui/spec.md +49 -0
- package/openspec/changes/init-api-gateway/tasks.md +84 -0
- package/openspec/project.md +58 -0
- package/package.json +51 -0
- package/postcss.config.js +6 -0
- package/src/app/api/config/route.ts +62 -0
- package/src/app/api/gateway/[...path]/route.ts +118 -0
- package/src/app/api/gateway/route.ts +77 -0
- package/src/app/api/logs/route.ts +48 -0
- package/src/app/api/models/route.ts +210 -0
- package/src/app/api/providers/route.ts +162 -0
- package/src/app/api/providers/test/route.ts +182 -0
- package/src/app/api/service/start/route.ts +73 -0
- package/src/app/api/service/status/route.ts +22 -0
- package/src/app/api/service/stop/route.ts +27 -0
- package/src/app/components/ConfirmDialog.tsx +63 -0
- package/src/app/components/Nav.tsx +66 -0
- package/src/app/components/Toast.tsx +61 -0
- package/src/app/components/ToastProvider.tsx +43 -0
- package/src/app/globals.css +71 -0
- package/src/app/layout.tsx +22 -0
- package/src/app/logs/page.tsx +261 -0
- package/src/app/models/page.tsx +500 -0
- package/src/app/page.tsx +742 -0
- package/src/app/providers/page.tsx +558 -0
- package/src/cli/index.ts +95 -0
- package/src/db/database.ts +125 -0
- package/src/db/queries.ts +339 -0
- package/src/db/schema.ts +117 -0
- package/src/server/crypto.ts +48 -0
- package/src/server/gateway-server.ts +306 -0
- package/src/server/gateway.ts +163 -0
- package/src/server/logger.ts +96 -0
- package/src/server/providers/anthropic.ts +121 -0
- package/src/server/providers/gemini.ts +112 -0
- package/src/server/providers/index.ts +20 -0
- package/src/server/providers/openai.ts +235 -0
- package/src/server/providers/types.ts +20 -0
- package/src/server/service-manager.ts +321 -0
- package/tailwind.config.js +16 -0
- package/tsconfig.json +29 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { getDatabase } from '@/db/database';
|
|
3
|
+
import { getRequestLogs, getRequestLogById, getRequestLogCount } from '@/db/queries';
|
|
4
|
+
|
|
5
|
+
// Ensure Node.js runtime (required for SQLite)
|
|
6
|
+
export const runtime = 'nodejs';
|
|
7
|
+
|
|
8
|
+
export async function GET(request: NextRequest) {
|
|
9
|
+
try {
|
|
10
|
+
getDatabase();
|
|
11
|
+
const { searchParams } = new URL(request.url);
|
|
12
|
+
const id = searchParams.get('id');
|
|
13
|
+
const limit = parseInt(searchParams.get('limit') || '100');
|
|
14
|
+
const offset = parseInt(searchParams.get('offset') || '0');
|
|
15
|
+
const modelId = searchParams.get('model_id');
|
|
16
|
+
|
|
17
|
+
if (id) {
|
|
18
|
+
const log = getRequestLogById(parseInt(id));
|
|
19
|
+
if (!log) {
|
|
20
|
+
return NextResponse.json(
|
|
21
|
+
{ error: 'Log not found' },
|
|
22
|
+
{ status: 404 }
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
return NextResponse.json(log);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const logs = getRequestLogs(
|
|
29
|
+
limit,
|
|
30
|
+
offset,
|
|
31
|
+
modelId ? parseInt(modelId) : undefined
|
|
32
|
+
);
|
|
33
|
+
const total = getRequestLogCount(modelId ? parseInt(modelId) : undefined);
|
|
34
|
+
|
|
35
|
+
return NextResponse.json({
|
|
36
|
+
logs,
|
|
37
|
+
total,
|
|
38
|
+
limit,
|
|
39
|
+
offset,
|
|
40
|
+
});
|
|
41
|
+
} catch (error: any) {
|
|
42
|
+
console.error('Logs API error:', error);
|
|
43
|
+
return NextResponse.json(
|
|
44
|
+
{ error: error.message || 'Internal server error', stack: process.env.NODE_ENV === 'development' ? error.stack : undefined },
|
|
45
|
+
{ status: 500 }
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { getDatabase } from '@/db/database';
|
|
3
|
+
import {
|
|
4
|
+
getAllModels,
|
|
5
|
+
createModel,
|
|
6
|
+
updateModel,
|
|
7
|
+
deleteModel,
|
|
8
|
+
getModelsByProvider,
|
|
9
|
+
getProviderById,
|
|
10
|
+
} from '@/db/queries';
|
|
11
|
+
import { getProviderAdapter } from '@/server/providers';
|
|
12
|
+
|
|
13
|
+
// Ensure Node.js runtime (required for SQLite)
|
|
14
|
+
export const runtime = 'nodejs';
|
|
15
|
+
|
|
16
|
+
export async function GET(request: NextRequest) {
|
|
17
|
+
try {
|
|
18
|
+
getDatabase();
|
|
19
|
+
const { searchParams } = new URL(request.url);
|
|
20
|
+
const providerId = searchParams.get('provider_id');
|
|
21
|
+
|
|
22
|
+
if (providerId) {
|
|
23
|
+
const models = getModelsByProvider(parseInt(providerId));
|
|
24
|
+
return NextResponse.json(models);
|
|
25
|
+
} else {
|
|
26
|
+
const models = getAllModels();
|
|
27
|
+
return NextResponse.json(models);
|
|
28
|
+
}
|
|
29
|
+
} catch (error: any) {
|
|
30
|
+
console.error('Models API error:', error);
|
|
31
|
+
return NextResponse.json(
|
|
32
|
+
{ error: error.message || 'Internal server error', stack: process.env.NODE_ENV === 'development' ? error.stack : undefined },
|
|
33
|
+
{ status: 500 }
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function POST(request: NextRequest) {
|
|
39
|
+
try {
|
|
40
|
+
getDatabase();
|
|
41
|
+
const body = await request.json();
|
|
42
|
+
const { provider_id, name, model_id, enabled } = body;
|
|
43
|
+
|
|
44
|
+
if (!provider_id || !name || !model_id) {
|
|
45
|
+
return NextResponse.json(
|
|
46
|
+
{ error: 'Missing required fields' },
|
|
47
|
+
{ status: 400 }
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const model = createModel({
|
|
52
|
+
provider_id,
|
|
53
|
+
name,
|
|
54
|
+
model_id,
|
|
55
|
+
enabled: enabled !== undefined ? enabled : true,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
return NextResponse.json(model);
|
|
59
|
+
} catch (error: any) {
|
|
60
|
+
console.error('Models API error:', error);
|
|
61
|
+
return NextResponse.json(
|
|
62
|
+
{ error: error.message || 'Internal server error', stack: process.env.NODE_ENV === 'development' ? error.stack : undefined },
|
|
63
|
+
{ status: 500 }
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function PUT(request: NextRequest) {
|
|
69
|
+
try {
|
|
70
|
+
getDatabase();
|
|
71
|
+
const body = await request.json();
|
|
72
|
+
const { id, name, model_id, enabled, provider_id } = body;
|
|
73
|
+
|
|
74
|
+
if (!id) {
|
|
75
|
+
return NextResponse.json(
|
|
76
|
+
{ error: 'Model ID is required' },
|
|
77
|
+
{ status: 400 }
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const updateData: any = {};
|
|
82
|
+
if (name !== undefined) updateData.name = name;
|
|
83
|
+
if (model_id !== undefined) updateData.model_id = model_id;
|
|
84
|
+
if (enabled !== undefined) updateData.enabled = enabled;
|
|
85
|
+
if (provider_id !== undefined) updateData.provider_id = provider_id;
|
|
86
|
+
|
|
87
|
+
const model = updateModel(id, updateData);
|
|
88
|
+
if (!model) {
|
|
89
|
+
return NextResponse.json(
|
|
90
|
+
{ error: 'Model not found' },
|
|
91
|
+
{ status: 404 }
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return NextResponse.json(model);
|
|
96
|
+
} catch (error: any) {
|
|
97
|
+
console.error('Models API error:', error);
|
|
98
|
+
return NextResponse.json(
|
|
99
|
+
{ error: error.message || 'Internal server error', stack: process.env.NODE_ENV === 'development' ? error.stack : undefined },
|
|
100
|
+
{ status: 500 }
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export async function DELETE(request: NextRequest) {
|
|
106
|
+
try {
|
|
107
|
+
getDatabase();
|
|
108
|
+
const { searchParams } = new URL(request.url);
|
|
109
|
+
const id = searchParams.get('id');
|
|
110
|
+
|
|
111
|
+
if (!id) {
|
|
112
|
+
return NextResponse.json(
|
|
113
|
+
{ error: 'Model ID is required' },
|
|
114
|
+
{ status: 400 }
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const success = deleteModel(parseInt(id));
|
|
119
|
+
if (!success) {
|
|
120
|
+
return NextResponse.json(
|
|
121
|
+
{ error: 'Model not found' },
|
|
122
|
+
{ status: 404 }
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return NextResponse.json({ success: true });
|
|
127
|
+
} catch (error: any) {
|
|
128
|
+
console.error('Models API error:', error);
|
|
129
|
+
return NextResponse.json(
|
|
130
|
+
{ error: error.message || 'Internal server error', stack: process.env.NODE_ENV === 'development' ? error.stack : undefined },
|
|
131
|
+
{ status: 500 }
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Fetch models from provider
|
|
137
|
+
export async function PATCH(request: NextRequest) {
|
|
138
|
+
try {
|
|
139
|
+
getDatabase();
|
|
140
|
+
const body = await request.json();
|
|
141
|
+
const { provider_id } = body;
|
|
142
|
+
|
|
143
|
+
if (!provider_id) {
|
|
144
|
+
return NextResponse.json(
|
|
145
|
+
{ error: 'Provider ID is required' },
|
|
146
|
+
{ status: 400 }
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const provider = getProviderById(provider_id);
|
|
151
|
+
if (!provider) {
|
|
152
|
+
return NextResponse.json(
|
|
153
|
+
{ error: 'Provider not found' },
|
|
154
|
+
{ status: 404 }
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
console.log('[Models API] Fetching models for provider:', {
|
|
159
|
+
providerId: provider_id,
|
|
160
|
+
providerName: provider.name,
|
|
161
|
+
protocol: provider.protocol,
|
|
162
|
+
baseUrl: provider.base_url,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const adapter = getProviderAdapter(provider.protocol);
|
|
166
|
+
const models = await adapter.listModels(provider);
|
|
167
|
+
|
|
168
|
+
console.log('[Models API] Fetched models:', models.length);
|
|
169
|
+
|
|
170
|
+
// Create or update models
|
|
171
|
+
const results = [];
|
|
172
|
+
for (const modelInfo of models) {
|
|
173
|
+
try {
|
|
174
|
+
const existing = getAllModels().find(
|
|
175
|
+
m => m.provider_id === provider_id && m.model_id === modelInfo.id
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
if (existing) {
|
|
179
|
+
updateModel(existing.id, {
|
|
180
|
+
name: modelInfo.name,
|
|
181
|
+
model_id: modelInfo.id,
|
|
182
|
+
});
|
|
183
|
+
results.push({ ...existing, name: modelInfo.name });
|
|
184
|
+
} else {
|
|
185
|
+
const newModel = createModel({
|
|
186
|
+
provider_id,
|
|
187
|
+
name: modelInfo.name,
|
|
188
|
+
model_id: modelInfo.id,
|
|
189
|
+
enabled: false, // 默认关闭
|
|
190
|
+
});
|
|
191
|
+
results.push(newModel);
|
|
192
|
+
}
|
|
193
|
+
} catch (error: any) {
|
|
194
|
+
console.error(`Failed to create/update model ${modelInfo.id}:`, error);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return NextResponse.json({
|
|
199
|
+
success: true,
|
|
200
|
+
models: results,
|
|
201
|
+
count: results.length,
|
|
202
|
+
});
|
|
203
|
+
} catch (error: any) {
|
|
204
|
+
console.error('Models API error:', error);
|
|
205
|
+
return NextResponse.json(
|
|
206
|
+
{ error: error.message || 'Internal server error', stack: process.env.NODE_ENV === 'development' ? error.stack : undefined },
|
|
207
|
+
{ status: 500 }
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { getDatabase } from '@/db/database';
|
|
3
|
+
import { getAllProviders, getProviderById, createProvider, updateProvider, deleteProvider } from '@/db/queries';
|
|
4
|
+
import { encryptApiKey, decryptApiKey } from '@/server/crypto';
|
|
5
|
+
|
|
6
|
+
// Ensure Node.js runtime (required for SQLite)
|
|
7
|
+
export const runtime = 'nodejs';
|
|
8
|
+
|
|
9
|
+
export async function GET(request: NextRequest) {
|
|
10
|
+
try {
|
|
11
|
+
getDatabase();
|
|
12
|
+
const { searchParams } = new URL(request.url);
|
|
13
|
+
const id = searchParams.get('id');
|
|
14
|
+
const includeKey = searchParams.get('includeKey') === 'true';
|
|
15
|
+
|
|
16
|
+
// If ID is provided, return single provider (for editing)
|
|
17
|
+
if (id) {
|
|
18
|
+
const provider = getProviderById(parseInt(id));
|
|
19
|
+
if (!provider) {
|
|
20
|
+
return NextResponse.json(
|
|
21
|
+
{ error: 'Provider not found' },
|
|
22
|
+
{ status: 404 }
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
// Return with decrypted API key if requested (for editing)
|
|
26
|
+
return NextResponse.json({
|
|
27
|
+
...provider,
|
|
28
|
+
api_key: includeKey ? decryptApiKey(provider.api_key) : '***',
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Return all providers
|
|
33
|
+
const providers = getAllProviders();
|
|
34
|
+
// Don't return API keys in list view
|
|
35
|
+
const sanitized = providers.map(p => ({
|
|
36
|
+
...p,
|
|
37
|
+
api_key: '***',
|
|
38
|
+
}));
|
|
39
|
+
return NextResponse.json(sanitized);
|
|
40
|
+
} catch (error: any) {
|
|
41
|
+
console.error('Providers API error:', error);
|
|
42
|
+
return NextResponse.json(
|
|
43
|
+
{ error: error.message || 'Internal server error', stack: process.env.NODE_ENV === 'development' ? error.stack : undefined },
|
|
44
|
+
{ status: 500 }
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function POST(request: NextRequest) {
|
|
50
|
+
try {
|
|
51
|
+
getDatabase();
|
|
52
|
+
const body = await request.json();
|
|
53
|
+
const { name, protocol, base_url, api_key } = body;
|
|
54
|
+
|
|
55
|
+
if (!name || !protocol || !base_url || !api_key) {
|
|
56
|
+
return NextResponse.json(
|
|
57
|
+
{ error: 'Missing required fields' },
|
|
58
|
+
{ status: 400 }
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const encryptedKey = encryptApiKey(api_key.trim());
|
|
63
|
+
const provider = createProvider({
|
|
64
|
+
name,
|
|
65
|
+
protocol,
|
|
66
|
+
base_url,
|
|
67
|
+
api_key: encryptedKey,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return NextResponse.json({
|
|
71
|
+
...provider,
|
|
72
|
+
api_key: '***',
|
|
73
|
+
});
|
|
74
|
+
} catch (error: any) {
|
|
75
|
+
console.error('Providers API error:', error);
|
|
76
|
+
return NextResponse.json(
|
|
77
|
+
{ error: error.message || 'Internal server error', stack: process.env.NODE_ENV === 'development' ? error.stack : undefined },
|
|
78
|
+
{ status: 500 }
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function PUT(request: NextRequest) {
|
|
84
|
+
try {
|
|
85
|
+
getDatabase();
|
|
86
|
+
const body = await request.json();
|
|
87
|
+
const { id, name, protocol, base_url, api_key } = body;
|
|
88
|
+
|
|
89
|
+
if (!id) {
|
|
90
|
+
return NextResponse.json(
|
|
91
|
+
{ error: 'Provider ID is required' },
|
|
92
|
+
{ status: 400 }
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const existingProvider = getProviderById(id);
|
|
97
|
+
if (!existingProvider) {
|
|
98
|
+
return NextResponse.json(
|
|
99
|
+
{ error: 'Provider not found' },
|
|
100
|
+
{ status: 404 }
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const updateData: any = {};
|
|
105
|
+
if (name !== undefined) updateData.name = name;
|
|
106
|
+
if (protocol !== undefined) updateData.protocol = protocol;
|
|
107
|
+
if (base_url !== undefined) updateData.base_url = base_url;
|
|
108
|
+
if (api_key !== undefined && api_key !== null && api_key.trim() !== '') {
|
|
109
|
+
updateData.api_key = encryptApiKey(api_key.trim());
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const provider = updateProvider(id, updateData);
|
|
113
|
+
if (!provider) {
|
|
114
|
+
return NextResponse.json(
|
|
115
|
+
{ error: 'Provider not found' },
|
|
116
|
+
{ status: 404 }
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return NextResponse.json({
|
|
121
|
+
...provider,
|
|
122
|
+
api_key: '***',
|
|
123
|
+
});
|
|
124
|
+
} catch (error: any) {
|
|
125
|
+
console.error('Providers API error:', error);
|
|
126
|
+
return NextResponse.json(
|
|
127
|
+
{ error: error.message || 'Internal server error', stack: process.env.NODE_ENV === 'development' ? error.stack : undefined },
|
|
128
|
+
{ status: 500 }
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export async function DELETE(request: NextRequest) {
|
|
134
|
+
try {
|
|
135
|
+
getDatabase();
|
|
136
|
+
const { searchParams } = new URL(request.url);
|
|
137
|
+
const id = searchParams.get('id');
|
|
138
|
+
|
|
139
|
+
if (!id) {
|
|
140
|
+
return NextResponse.json(
|
|
141
|
+
{ error: 'Provider ID is required' },
|
|
142
|
+
{ status: 400 }
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const success = deleteProvider(parseInt(id));
|
|
147
|
+
if (!success) {
|
|
148
|
+
return NextResponse.json(
|
|
149
|
+
{ error: 'Provider not found' },
|
|
150
|
+
{ status: 404 }
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return NextResponse.json({ success: true });
|
|
155
|
+
} catch (error: any) {
|
|
156
|
+
console.error('Providers API error:', error);
|
|
157
|
+
return NextResponse.json(
|
|
158
|
+
{ error: error.message || 'Internal server error', stack: process.env.NODE_ENV === 'development' ? error.stack : undefined },
|
|
159
|
+
{ status: 500 }
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { getDatabase } from '@/db/database';
|
|
3
|
+
import { getProviderById, getModelById } from '@/db/queries';
|
|
4
|
+
import { getProviderAdapter } from '@/server/providers';
|
|
5
|
+
import type { GatewayRequest } from '@/server/providers/types';
|
|
6
|
+
|
|
7
|
+
// Ensure Node.js runtime (required for SQLite)
|
|
8
|
+
export const runtime = 'nodejs';
|
|
9
|
+
|
|
10
|
+
export async function POST(request: NextRequest) {
|
|
11
|
+
try {
|
|
12
|
+
getDatabase();
|
|
13
|
+
const body = await request.json();
|
|
14
|
+
const { provider_id, model_id } = body;
|
|
15
|
+
|
|
16
|
+
if (!provider_id || !model_id) {
|
|
17
|
+
return NextResponse.json(
|
|
18
|
+
{ error: 'Provider ID and Model ID are required' },
|
|
19
|
+
{ status: 400 }
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Get provider and model
|
|
24
|
+
const provider = getProviderById(provider_id);
|
|
25
|
+
if (!provider) {
|
|
26
|
+
return NextResponse.json(
|
|
27
|
+
{ error: 'Provider not found' },
|
|
28
|
+
{ status: 404 }
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const model = getModelById(model_id);
|
|
33
|
+
if (!model || model.provider_id !== provider_id) {
|
|
34
|
+
return NextResponse.json(
|
|
35
|
+
{ error: 'Model not found or does not belong to this provider' },
|
|
36
|
+
{ status: 404 }
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Get provider adapter
|
|
41
|
+
const adapter = getProviderAdapter(provider.protocol);
|
|
42
|
+
|
|
43
|
+
// Create a test request based on protocol
|
|
44
|
+
const testRequest = createTestRequest(provider.protocol, model.model_id);
|
|
45
|
+
|
|
46
|
+
// Send test request with timeout
|
|
47
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
48
|
+
setTimeout(() => reject(new Error('连接超时,请检查网络或稍后重试')), 10000);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const testPromise = adapter.forwardRequest(
|
|
52
|
+
{ ...model, provider } as any,
|
|
53
|
+
testRequest
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const response = await Promise.race([testPromise, timeoutPromise]) as any;
|
|
57
|
+
|
|
58
|
+
// Check response status
|
|
59
|
+
if (response.status >= 200 && response.status < 300) {
|
|
60
|
+
return NextResponse.json({
|
|
61
|
+
success: true,
|
|
62
|
+
message: '连接成功,模型可用',
|
|
63
|
+
});
|
|
64
|
+
} else {
|
|
65
|
+
// Parse error message
|
|
66
|
+
let errorMessage = '连接失败';
|
|
67
|
+
if (response.body?.error?.message) {
|
|
68
|
+
errorMessage = response.body.error.message;
|
|
69
|
+
} else if (typeof response.body === 'string') {
|
|
70
|
+
errorMessage = response.body;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Map common error codes to friendly messages
|
|
74
|
+
if (response.status === 401 || response.status === 403) {
|
|
75
|
+
errorMessage = 'API Key 无效,请检查配置';
|
|
76
|
+
} else if (response.status === 404) {
|
|
77
|
+
errorMessage = '模型不存在或不可用';
|
|
78
|
+
} else if (response.status >= 500) {
|
|
79
|
+
errorMessage = '服务器错误,请稍后重试';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return NextResponse.json(
|
|
83
|
+
{
|
|
84
|
+
success: false,
|
|
85
|
+
error: errorMessage,
|
|
86
|
+
status: response.status,
|
|
87
|
+
},
|
|
88
|
+
{ status: 200 } // Return 200 so frontend can handle the error
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
} catch (error: any) {
|
|
92
|
+
console.error('Test connection error:', error);
|
|
93
|
+
|
|
94
|
+
// Handle specific error types
|
|
95
|
+
let errorMessage = '连接失败';
|
|
96
|
+
if (error.message.includes('超时')) {
|
|
97
|
+
errorMessage = error.message;
|
|
98
|
+
} else if (error.message.includes('fetch failed') || error.message.includes('ECONNREFUSED')) {
|
|
99
|
+
errorMessage = '无法连接到服务器,请检查 Base URL 和网络连接';
|
|
100
|
+
} else if (error.message.includes('ENOTFOUND') || error.message.includes('DNS')) {
|
|
101
|
+
errorMessage = 'DNS 解析失败,请检查 Base URL';
|
|
102
|
+
} else {
|
|
103
|
+
errorMessage = error.message || '连接失败,请检查配置';
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return NextResponse.json(
|
|
107
|
+
{
|
|
108
|
+
success: false,
|
|
109
|
+
error: errorMessage,
|
|
110
|
+
},
|
|
111
|
+
{ status: 200 } // Return 200 so frontend can handle the error
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function createTestRequest(protocol: string, modelId: string): GatewayRequest {
|
|
117
|
+
switch (protocol) {
|
|
118
|
+
case 'openai':
|
|
119
|
+
return {
|
|
120
|
+
method: 'POST',
|
|
121
|
+
path: '/v1/chat/completions',
|
|
122
|
+
headers: {
|
|
123
|
+
'Content-Type': 'application/json',
|
|
124
|
+
},
|
|
125
|
+
query: {},
|
|
126
|
+
body: {
|
|
127
|
+
model: modelId,
|
|
128
|
+
messages: [
|
|
129
|
+
{
|
|
130
|
+
role: 'user',
|
|
131
|
+
content: 'test',
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
max_tokens: 5,
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
case 'anthropic':
|
|
139
|
+
return {
|
|
140
|
+
method: 'POST',
|
|
141
|
+
path: '/v1/messages',
|
|
142
|
+
headers: {
|
|
143
|
+
'Content-Type': 'application/json',
|
|
144
|
+
},
|
|
145
|
+
query: {},
|
|
146
|
+
body: {
|
|
147
|
+
model: modelId,
|
|
148
|
+
max_tokens: 5,
|
|
149
|
+
messages: [
|
|
150
|
+
{
|
|
151
|
+
role: 'user',
|
|
152
|
+
content: 'test',
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
case 'gemini':
|
|
159
|
+
return {
|
|
160
|
+
method: 'POST',
|
|
161
|
+
path: `/v1/models/${modelId}:generateContent`,
|
|
162
|
+
headers: {
|
|
163
|
+
'Content-Type': 'application/json',
|
|
164
|
+
},
|
|
165
|
+
query: {},
|
|
166
|
+
body: {
|
|
167
|
+
contents: [
|
|
168
|
+
{
|
|
169
|
+
parts: [
|
|
170
|
+
{
|
|
171
|
+
text: 'test',
|
|
172
|
+
},
|
|
173
|
+
],
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
default:
|
|
180
|
+
throw new Error(`Unsupported protocol: ${protocol}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { getDatabase } from '@/db/database';
|
|
3
|
+
import { serviceManager } from '@/server/service-manager';
|
|
4
|
+
import { getConfig } from '@/db/queries';
|
|
5
|
+
import net from 'net';
|
|
6
|
+
|
|
7
|
+
// Ensure Node.js runtime (required for service manager)
|
|
8
|
+
export const runtime = 'nodejs';
|
|
9
|
+
|
|
10
|
+
export async function POST(request: NextRequest) {
|
|
11
|
+
try {
|
|
12
|
+
// Initialize database
|
|
13
|
+
getDatabase();
|
|
14
|
+
|
|
15
|
+
const body = await request.json().catch(() => ({}));
|
|
16
|
+
const port = body.port ? parseInt(body.port, 10) : null;
|
|
17
|
+
|
|
18
|
+
// Get port from config if not provided
|
|
19
|
+
let targetPort = port;
|
|
20
|
+
if (!targetPort) {
|
|
21
|
+
const portConfig = getConfig('port');
|
|
22
|
+
targetPort = portConfig ? parseInt(portConfig.value, 10) : 3000;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// In development, if port is 3000 (default dev server port), check for conflict
|
|
26
|
+
const isDev = process.env.NODE_ENV !== 'production';
|
|
27
|
+
if (isDev && targetPort === 3000) {
|
|
28
|
+
// Check if port 3000 is in use (likely by the dev server)
|
|
29
|
+
const testServer = net.createServer();
|
|
30
|
+
const portInUse = await new Promise<boolean>((resolve) => {
|
|
31
|
+
testServer.listen(3000, () => {
|
|
32
|
+
testServer.close(() => resolve(false));
|
|
33
|
+
});
|
|
34
|
+
testServer.on('error', () => resolve(true));
|
|
35
|
+
setTimeout(() => {
|
|
36
|
+
testServer.close(() => resolve(false));
|
|
37
|
+
}, 100);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
if (portInUse) {
|
|
41
|
+
return NextResponse.json(
|
|
42
|
+
{
|
|
43
|
+
status: 'stopped',
|
|
44
|
+
error: 'Port 3000 is already in use by the development server. Please configure a different port (e.g., 3001) in the settings above.'
|
|
45
|
+
},
|
|
46
|
+
{ status: 400 }
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Validate port
|
|
52
|
+
if (isNaN(targetPort) || targetPort < 1 || targetPort > 65535) {
|
|
53
|
+
return NextResponse.json(
|
|
54
|
+
{ status: 'stopped', error: 'Invalid port number' },
|
|
55
|
+
{ status: 400 }
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const result = await serviceManager.start(targetPort);
|
|
60
|
+
|
|
61
|
+
if (result.error) {
|
|
62
|
+
return NextResponse.json(result, { status: 400 });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return NextResponse.json(result);
|
|
66
|
+
} catch (error: any) {
|
|
67
|
+
console.error('Service start API error:', error);
|
|
68
|
+
return NextResponse.json(
|
|
69
|
+
{ status: 'stopped', error: error.message || 'Failed to start service' },
|
|
70
|
+
{ status: 500 }
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { getDatabase } from '@/db/database';
|
|
3
|
+
import { serviceManager } from '@/server/service-manager';
|
|
4
|
+
|
|
5
|
+
// Ensure Node.js runtime (required for service manager)
|
|
6
|
+
export const runtime = 'nodejs';
|
|
7
|
+
|
|
8
|
+
export async function GET(request: NextRequest) {
|
|
9
|
+
try {
|
|
10
|
+
// Initialize database
|
|
11
|
+
getDatabase();
|
|
12
|
+
|
|
13
|
+
const status = await serviceManager.getStatus();
|
|
14
|
+
return NextResponse.json(status);
|
|
15
|
+
} catch (error: any) {
|
|
16
|
+
console.error('Service status API error:', error);
|
|
17
|
+
return NextResponse.json(
|
|
18
|
+
{ status: 'stopped', error: error.message || 'Failed to get service status' },
|
|
19
|
+
{ status: 500 }
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
}
|