work-agent 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/README.md +234 -0
- package/app/(admin)/approvals/page.tsx +16 -0
- package/app/(admin)/audit/page.tsx +18 -0
- package/app/(admin)/layout.tsx +47 -0
- package/app/(admin)/scheduled-tasks/page.tsx +17 -0
- package/app/(admin)/settings/page.tsx +46 -0
- package/app/(admin)/skills/[name]/page.tsx +378 -0
- package/app/(admin)/skills/page.tsx +406 -0
- package/app/(admin)/statistics/page.tsx +416 -0
- package/app/(admin)/tickets/[id]/page.tsx +348 -0
- package/app/(admin)/tickets/new/page.tsx +309 -0
- package/app/(admin)/tickets/page.tsx +27 -0
- package/app/api/audit/route.ts +30 -0
- package/app/api/auth/feishu/callback/route.ts +72 -0
- package/app/api/auth/feishu/login/route.ts +17 -0
- package/app/api/auth/feishu/sso/route.ts +78 -0
- package/app/api/auth/login/route.ts +85 -0
- package/app/api/auth/oauth/route.ts +168 -0
- package/app/api/config/providers/route.ts +105 -0
- package/app/api/config/route.ts +115 -0
- package/app/api/config/status/route.ts +56 -0
- package/app/api/config/test/route.ts +212 -0
- package/app/api/documents/[id]/route.ts +88 -0
- package/app/api/documents/route.ts +53 -0
- package/app/api/health/route.ts +32 -0
- package/app/api/knowledge/[id]/route.ts +152 -0
- package/app/api/knowledge/from-session/route.ts +27 -0
- package/app/api/knowledge/route.ts +100 -0
- package/app/api/market/knowledge/[id]/route.ts +92 -0
- package/app/api/market/knowledge/route.ts +130 -0
- package/app/api/marketplace/skills/[id]/approve/route.ts +68 -0
- package/app/api/marketplace/skills/[id]/certify/route.ts +54 -0
- package/app/api/marketplace/skills/[id]/install/route.ts +180 -0
- package/app/api/marketplace/skills/[id]/promote-to-system/route.ts +219 -0
- package/app/api/marketplace/skills/[id]/rate/route.ts +90 -0
- package/app/api/marketplace/skills/[id]/ratings/route.ts +55 -0
- package/app/api/marketplace/skills/[id]/reject/route.ts +68 -0
- package/app/api/marketplace/skills/[id]/route.ts +177 -0
- package/app/api/marketplace/skills/route.ts +235 -0
- package/app/api/memory/route.ts +40 -0
- package/app/api/my/files/[id]/route.ts +52 -0
- package/app/api/my/files/route.ts +230 -0
- package/app/api/my/knowledge/route.ts +36 -0
- package/app/api/pi-chat/route.ts +443 -0
- package/app/api/recommend/route.ts +38 -0
- package/app/api/scheduled-tasks/[id]/execute/route.ts +132 -0
- package/app/api/scheduled-tasks/[id]/route.ts +165 -0
- package/app/api/scheduled-tasks/[id]/toggle/route.ts +53 -0
- package/app/api/scheduled-tasks/route.ts +101 -0
- package/app/api/sessions/[id]/messages/route.ts +212 -0
- package/app/api/sessions/route.ts +101 -0
- package/app/api/share/file/[id]/route.ts +37 -0
- package/app/api/skills/[name]/execute/route.ts +121 -0
- package/app/api/skills/[name]/route.ts +167 -0
- package/app/api/skills/create/route.ts +65 -0
- package/app/api/skills/generate/route.ts +405 -0
- package/app/api/skills/installed/route.ts +151 -0
- package/app/api/skills/route.ts +174 -0
- package/app/api/skills/translate/route.ts +40 -0
- package/app/api/skills/user/[name]/route.ts +159 -0
- package/app/api/skills/user/route.ts +90 -0
- package/app/api/statistics/route.ts +94 -0
- package/app/api/task-executions/[id]/route.ts +34 -0
- package/app/api/task-executions/route.ts +29 -0
- package/app/api/tickets/[id]/approve/route.ts +129 -0
- package/app/api/tickets/[id]/execute/route.ts +201 -0
- package/app/api/tickets/[id]/route.ts +127 -0
- package/app/api/tickets/route.ts +103 -0
- package/app/api/user/skills/route.ts +175 -0
- package/app/api/users/route.ts +80 -0
- package/app/chat/page.tsx +5 -0
- package/app/globals.css +84 -0
- package/app/h5/layout.tsx +5 -0
- package/app/h5/mobile-approvals-page.tsx +167 -0
- package/app/h5/mobile-chat-page.tsx +951 -0
- package/app/h5/mobile-profile-page.tsx +147 -0
- package/app/h5/mobile-tickets-page.tsx +121 -0
- package/app/h5/page.tsx +23 -0
- package/app/h5/ticket-action-buttons.tsx +80 -0
- package/app/layout.tsx +26 -0
- package/app/login/page.tsx +318 -0
- package/app/market/knowledge/[id]/page.tsx +77 -0
- package/app/market/knowledge/page.tsx +358 -0
- package/app/market/layout.tsx +29 -0
- package/app/market/page.tsx +18 -0
- package/app/market/skills/page.tsx +397 -0
- package/app/my/files/page.tsx +511 -0
- package/app/my/knowledge/[id]/page.tsx +271 -0
- package/app/my/knowledge/new/page.tsx +234 -0
- package/app/my/knowledge/page.tsx +248 -0
- package/app/my/layout.tsx +32 -0
- package/app/my/memory/page.tsx +164 -0
- package/app/my/page.tsx +18 -0
- package/app/my/scheduled-tasks/[id]/edit/page.tsx +290 -0
- package/app/my/scheduled-tasks/[id]/executions/page.tsx +275 -0
- package/app/my/scheduled-tasks/[id]/page.tsx +284 -0
- package/app/my/scheduled-tasks/new/page.tsx +230 -0
- package/app/my/scheduled-tasks/page.tsx +27 -0
- package/app/my/skills/[name]/page.tsx +320 -0
- package/app/my/skills/new/page.tsx +394 -0
- package/app/my/skills/page.tsx +303 -0
- package/app/page.tsx +2288 -0
- package/app/share/[sessionId]/page.tsx +226 -0
- package/app/share/file/[id]/page.tsx +140 -0
- package/bin/README.md +63 -0
- package/bin/generate-api-system +300 -0
- package/bin/postinstall.js +95 -0
- package/bin/work-agent.js +173 -0
- package/components/ai-elements/agent.tsx +142 -0
- package/components/ai-elements/artifact.tsx +149 -0
- package/components/ai-elements/attachments.tsx +427 -0
- package/components/ai-elements/audio-player.tsx +232 -0
- package/components/ai-elements/canvas.tsx +26 -0
- package/components/ai-elements/chain-of-thought.tsx +223 -0
- package/components/ai-elements/checkpoint.tsx +72 -0
- package/components/ai-elements/code-block.tsx +555 -0
- package/components/ai-elements/commit.tsx +449 -0
- package/components/ai-elements/confirmation.tsx +173 -0
- package/components/ai-elements/connection.tsx +28 -0
- package/components/ai-elements/context.tsx +410 -0
- package/components/ai-elements/controls.tsx +19 -0
- package/components/ai-elements/conversation.tsx +167 -0
- package/components/ai-elements/edge.tsx +144 -0
- package/components/ai-elements/environment-variables.tsx +325 -0
- package/components/ai-elements/file-tree.tsx +298 -0
- package/components/ai-elements/image.tsx +25 -0
- package/components/ai-elements/inline-citation.tsx +294 -0
- package/components/ai-elements/jsx-preview.tsx +250 -0
- package/components/ai-elements/message.tsx +367 -0
- package/components/ai-elements/mic-selector.tsx +372 -0
- package/components/ai-elements/model-selector.tsx +214 -0
- package/components/ai-elements/node.tsx +72 -0
- package/components/ai-elements/open-in-chat.tsx +367 -0
- package/components/ai-elements/package-info.tsx +235 -0
- package/components/ai-elements/panel.tsx +16 -0
- package/components/ai-elements/persona.tsx +280 -0
- package/components/ai-elements/plan.tsx +144 -0
- package/components/ai-elements/prompt-input.tsx +1341 -0
- package/components/ai-elements/queue.tsx +275 -0
- package/components/ai-elements/reasoning.tsx +355 -0
- package/components/ai-elements/sandbox.tsx +133 -0
- package/components/ai-elements/schema-display.tsx +473 -0
- package/components/ai-elements/shimmer.tsx +78 -0
- package/components/ai-elements/snippet.tsx +141 -0
- package/components/ai-elements/sources.tsx +78 -0
- package/components/ai-elements/speech-input.tsx +324 -0
- package/components/ai-elements/stack-trace.tsx +531 -0
- package/components/ai-elements/suggestion.tsx +58 -0
- package/components/ai-elements/task.tsx +88 -0
- package/components/ai-elements/terminal.tsx +277 -0
- package/components/ai-elements/test-results.tsx +497 -0
- package/components/ai-elements/tool.tsx +174 -0
- package/components/ai-elements/toolbar.tsx +17 -0
- package/components/ai-elements/transcription.tsx +126 -0
- package/components/ai-elements/voice-selector.tsx +525 -0
- package/components/ai-elements/web-preview.tsx +282 -0
- package/components/audit-log-list.tsx +114 -0
- package/components/chat/EmptyPreviewState.tsx +12 -0
- package/components/chat/KnowledgePickerDialog.tsx +464 -0
- package/components/chat/KnowledgePreview.tsx +70 -0
- package/components/chat/KnowledgePreviewPanel.tsx +86 -0
- package/components/chat/MentionInput.tsx +309 -0
- package/components/chat/OrganizeDialog.tsx +258 -0
- package/components/chat/RecommendationBanner.tsx +94 -0
- package/components/chat/SaveToKnowledgeDialog.tsx +193 -0
- package/components/chat/SkillSelector.tsx +305 -0
- package/components/chat/SkillSwitcher.tsx +163 -0
- package/components/client-layout.tsx +15 -0
- package/components/knowledge/KnowledgeMetadataPanel.tsx +293 -0
- package/components/layout-wrapper.tsx +18 -0
- package/components/mobile-layout.tsx +62 -0
- package/components/scheduled-task-list.tsx +356 -0
- package/components/setup-guide.tsx +484 -0
- package/components/sub-nav.tsx +54 -0
- package/components/ticket-detail-content.tsx +383 -0
- package/components/ticket-list.tsx +366 -0
- package/components/top-nav.tsx +132 -0
- package/components/ui/accordion.tsx +58 -0
- package/components/ui/alert.tsx +59 -0
- package/components/ui/avatar.tsx +50 -0
- package/components/ui/badge.tsx +36 -0
- package/components/ui/button-group.tsx +83 -0
- package/components/ui/button.tsx +57 -0
- package/components/ui/card.tsx +91 -0
- package/components/ui/carousel.tsx +262 -0
- package/components/ui/collapsible.tsx +11 -0
- package/components/ui/command.tsx +153 -0
- package/components/ui/dialog.tsx +122 -0
- package/components/ui/dropdown-menu.tsx +200 -0
- package/components/ui/hover-card.tsx +29 -0
- package/components/ui/input-group.tsx +170 -0
- package/components/ui/input.tsx +22 -0
- package/components/ui/label.tsx +26 -0
- package/components/ui/popover.tsx +31 -0
- package/components/ui/progress.tsx +28 -0
- package/components/ui/scroll-area.tsx +48 -0
- package/components/ui/select.tsx +174 -0
- package/components/ui/separator.tsx +31 -0
- package/components/ui/spinner.tsx +16 -0
- package/components/ui/switch.tsx +29 -0
- package/components/ui/table.tsx +120 -0
- package/components/ui/tabs.tsx +55 -0
- package/components/ui/textarea.tsx +22 -0
- package/components/ui/tooltip.tsx +30 -0
- package/components/welcome-guide.tsx +182 -0
- package/components.json +24 -0
- package/lib/command-parser.ts +331 -0
- package/lib/dangerous-commands.ts +672 -0
- package/lib/db.ts +2250 -0
- package/lib/feishu-auth.ts +135 -0
- package/lib/file-storage.ts +306 -0
- package/lib/file-tool.ts +583 -0
- package/lib/knowledge-tool.ts +152 -0
- package/lib/knowledge-types.ts +66 -0
- package/lib/market-client.ts +313 -0
- package/lib/market-db.ts +736 -0
- package/lib/market-types.ts +51 -0
- package/lib/memory-tool.ts +211 -0
- package/lib/memory.ts +197 -0
- package/lib/pi-config.ts +436 -0
- package/lib/pi-session.ts +799 -0
- package/lib/pinyin.ts +13 -0
- package/lib/recommendation.ts +227 -0
- package/lib/risk-estimator.ts +350 -0
- package/lib/scheduled-task-tool.ts +184 -0
- package/lib/scheduler-init.ts +43 -0
- package/lib/scheduler.ts +416 -0
- package/lib/secure-bash-tool.ts +413 -0
- package/lib/skill-engine.ts +396 -0
- package/lib/skill-generator.ts +269 -0
- package/lib/skill-loader.ts +234 -0
- package/lib/skill-tool.ts +188 -0
- package/lib/skill-types.ts +82 -0
- package/lib/skills-init.ts +58 -0
- package/lib/ticket-tool.ts +246 -0
- package/lib/user-skill-types.ts +30 -0
- package/lib/user-skills.ts +362 -0
- package/lib/utils.ts +6 -0
- package/lib/workflow.ts +154 -0
- package/lib/zip-tool.ts +191 -0
- package/next.config.js +8 -0
- package/package.json +106 -0
- package/public/.gitkeep +1 -0
- package/public/icon.svg +1 -0
- package/tsconfig.json +42 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 审计日志 API - GET
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
6
|
+
import { getAuditLogs } from '@/lib/db';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* GET /api/audit - 获取审计日志
|
|
10
|
+
*/
|
|
11
|
+
export async function GET(request: NextRequest) {
|
|
12
|
+
try {
|
|
13
|
+
const { searchParams } = new URL(request.url);
|
|
14
|
+
const filters = {
|
|
15
|
+
userId: searchParams.get('userId') ?? undefined,
|
|
16
|
+
ticketId: searchParams.get('ticketId') ?? undefined,
|
|
17
|
+
sessionId: searchParams.get('sessionId') ?? undefined,
|
|
18
|
+
limit: searchParams.get('limit') ? parseInt(searchParams.get('limit')!) : undefined,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const logs = await getAuditLogs(filters);
|
|
22
|
+
|
|
23
|
+
return NextResponse.json({ logs });
|
|
24
|
+
} catch (error) {
|
|
25
|
+
return NextResponse.json(
|
|
26
|
+
{ error: error instanceof Error ? error.message : '获取审计日志失败' },
|
|
27
|
+
{ status: 500 },
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { getFeishuAccessToken, getFeishuUserInfo } from '@/lib/feishu-auth';
|
|
3
|
+
import {
|
|
4
|
+
getUserByFeishuUnionId,
|
|
5
|
+
createUser,
|
|
6
|
+
updateUserFeishuInfo,
|
|
7
|
+
updateUserLogin,
|
|
8
|
+
getBuiltinSkills,
|
|
9
|
+
installUserSkill,
|
|
10
|
+
getUserSkills
|
|
11
|
+
} from '@/lib/db';
|
|
12
|
+
|
|
13
|
+
const BASE_URL = process.env.FEISHU_REDIRECT_URI?.replace('/api/auth/feishu/callback', '') ||
|
|
14
|
+
process.env.NEXT_PUBLIC_BASE_URL ||
|
|
15
|
+
'http://work-agent.agent-platform.dev.aimstek.cn';
|
|
16
|
+
|
|
17
|
+
export async function GET(request: NextRequest) {
|
|
18
|
+
try {
|
|
19
|
+
const { searchParams } = new URL(request.url);
|
|
20
|
+
const code = searchParams.get('code');
|
|
21
|
+
const error = searchParams.get('error');
|
|
22
|
+
|
|
23
|
+
if (error) {
|
|
24
|
+
return NextResponse.redirect(new URL(`/login?error=feishu_auth_failed`, BASE_URL));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!code) {
|
|
28
|
+
return NextResponse.redirect(new URL('/login?error=no_code', BASE_URL));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const tokenData = await getFeishuAccessToken(code);
|
|
32
|
+
const userInfo = await getFeishuUserInfo(tokenData.access_token);
|
|
33
|
+
|
|
34
|
+
let user = getUserByFeishuUnionId(userInfo.union_id);
|
|
35
|
+
|
|
36
|
+
if (!user) {
|
|
37
|
+
const newUser = createUser(userInfo.union_id, 'guest');
|
|
38
|
+
|
|
39
|
+
const builtinSkills = getBuiltinSkills().filter(s => s.enabled);
|
|
40
|
+
const userSkills = getUserSkills(newUser.id);
|
|
41
|
+
for (const skill of builtinSkills) {
|
|
42
|
+
const alreadyInstalled = userSkills.some(s => s.skillName === skill.skillName);
|
|
43
|
+
if (!alreadyInstalled) {
|
|
44
|
+
installUserSkill(newUser.id, skill.skillName, 'builtin');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
user = newUser;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
updateUserFeishuInfo(user.id, {
|
|
51
|
+
unionId: userInfo.union_id,
|
|
52
|
+
openId: userInfo.open_id,
|
|
53
|
+
avatar: userInfo.avatar_url || userInfo.avatar_thumb,
|
|
54
|
+
displayName: userInfo.name,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
updateUserLogin(user.id);
|
|
58
|
+
|
|
59
|
+
const loginUrl = new URL('/login', BASE_URL);
|
|
60
|
+
loginUrl.searchParams.set('feishu_login', 'true');
|
|
61
|
+
loginUrl.searchParams.set('userId', user.id);
|
|
62
|
+
loginUrl.searchParams.set('username', userInfo.name);
|
|
63
|
+
loginUrl.searchParams.set('role', user.role);
|
|
64
|
+
|
|
65
|
+
const response = NextResponse.redirect(loginUrl);
|
|
66
|
+
|
|
67
|
+
return response;
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.error('飞书登录回调失败:', error);
|
|
70
|
+
return NextResponse.redirect(new URL('/login?error=feishu_login_failed', BASE_URL));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { getFeishuLoginUrl } from '@/lib/feishu-auth';
|
|
3
|
+
|
|
4
|
+
export async function GET(request: NextRequest) {
|
|
5
|
+
try {
|
|
6
|
+
const state = crypto.randomUUID();
|
|
7
|
+
const loginUrl = getFeishuLoginUrl(state);
|
|
8
|
+
|
|
9
|
+
return NextResponse.json({ url: loginUrl });
|
|
10
|
+
} catch (error) {
|
|
11
|
+
console.error('飞书登录失败:', error);
|
|
12
|
+
return NextResponse.json(
|
|
13
|
+
{ error: '生成飞书登录链接失败' },
|
|
14
|
+
{ status: 500 }
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { getFeishuAccessToken, getFeishuUserInfo } from '@/lib/feishu-auth';
|
|
3
|
+
import {
|
|
4
|
+
getUserByFeishuUnionId,
|
|
5
|
+
createUser,
|
|
6
|
+
updateUserFeishuInfo,
|
|
7
|
+
updateUserLogin,
|
|
8
|
+
getBuiltinSkills,
|
|
9
|
+
installUserSkill,
|
|
10
|
+
getUserSkills
|
|
11
|
+
} from '@/lib/db';
|
|
12
|
+
|
|
13
|
+
export async function POST(request: NextRequest) {
|
|
14
|
+
try {
|
|
15
|
+
const body = await request.json();
|
|
16
|
+
const { code } = body;
|
|
17
|
+
|
|
18
|
+
if (!code) {
|
|
19
|
+
return NextResponse.json(
|
|
20
|
+
{ error: '缺少授权码' },
|
|
21
|
+
{ status: 400 }
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const tokenData = await getFeishuAccessToken(code);
|
|
26
|
+
const userInfo = await getFeishuUserInfo(tokenData.access_token);
|
|
27
|
+
|
|
28
|
+
let user = getUserByFeishuUnionId(userInfo.union_id);
|
|
29
|
+
|
|
30
|
+
if (!user) {
|
|
31
|
+
const newUser = createUser(userInfo.union_id, 'guest');
|
|
32
|
+
|
|
33
|
+
const builtinSkills = getBuiltinSkills().filter(s => s.enabled);
|
|
34
|
+
const userSkills = getUserSkills(newUser.id);
|
|
35
|
+
for (const skill of builtinSkills) {
|
|
36
|
+
const alreadyInstalled = userSkills.some(s => s.skillName === skill.skillName);
|
|
37
|
+
if (!alreadyInstalled) {
|
|
38
|
+
installUserSkill(newUser.id, skill.skillName, 'builtin');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
user = newUser;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
updateUserFeishuInfo(user.id, {
|
|
45
|
+
unionId: userInfo.union_id,
|
|
46
|
+
openId: userInfo.open_id,
|
|
47
|
+
avatar: userInfo.avatar_url || userInfo.avatar_thumb,
|
|
48
|
+
displayName: userInfo.name,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
updateUserLogin(user.id);
|
|
52
|
+
|
|
53
|
+
const response = NextResponse.json({
|
|
54
|
+
user: {
|
|
55
|
+
id: user.id,
|
|
56
|
+
username: user.username,
|
|
57
|
+
role: user.role,
|
|
58
|
+
displayName: userInfo.name,
|
|
59
|
+
avatar: userInfo.avatar_url || userInfo.avatar_thumb,
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
response.cookies.set('userId', user.id, {
|
|
64
|
+
httpOnly: true,
|
|
65
|
+
secure: process.env.NODE_ENV === 'production',
|
|
66
|
+
sameSite: 'lax',
|
|
67
|
+
maxAge: 60 * 60 * 24 * 7,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return response;
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error('飞书免登录失败:', error);
|
|
73
|
+
return NextResponse.json(
|
|
74
|
+
{ error: '飞书登录失败' },
|
|
75
|
+
{ status: 500 }
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { createUser, getUserByUsername, getUserById, updateUserLogin, updateUserRole, getBuiltinSkills, installUserSkill, getUserSkills } from '@/lib/db';
|
|
3
|
+
|
|
4
|
+
const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || 'admin123';
|
|
5
|
+
|
|
6
|
+
export async function POST(request: NextRequest) {
|
|
7
|
+
try {
|
|
8
|
+
const body = await request.json();
|
|
9
|
+
const { username, password, mode } = body;
|
|
10
|
+
|
|
11
|
+
if (!username || typeof username !== 'string' || username.trim().length === 0) {
|
|
12
|
+
return NextResponse.json(
|
|
13
|
+
{ error: '用户名不能为空' },
|
|
14
|
+
{ status: 400 }
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const trimmedUsername = username.trim().toLowerCase();
|
|
19
|
+
|
|
20
|
+
if (mode === 'admin') {
|
|
21
|
+
if (!password) {
|
|
22
|
+
return NextResponse.json(
|
|
23
|
+
{ error: '请输入管理员密码' },
|
|
24
|
+
{ status: 400 }
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (password !== ADMIN_PASSWORD) {
|
|
29
|
+
return NextResponse.json(
|
|
30
|
+
{ error: '管理员密码错误' },
|
|
31
|
+
{ status: 401 }
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let user = getUserByUsername(trimmedUsername);
|
|
36
|
+
if (!user) {
|
|
37
|
+
user = createUser(trimmedUsername, 'admin');
|
|
38
|
+
} else if (user.role !== 'admin') {
|
|
39
|
+
updateUserRole(user.id, 'admin');
|
|
40
|
+
user = getUserById(user.id);
|
|
41
|
+
}
|
|
42
|
+
updateUserLogin(user!.id);
|
|
43
|
+
|
|
44
|
+
return NextResponse.json({
|
|
45
|
+
user: {
|
|
46
|
+
id: user!.id,
|
|
47
|
+
username: user!.username,
|
|
48
|
+
role: user!.role,
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let user = getUserByUsername(trimmedUsername);
|
|
54
|
+
if (!user) {
|
|
55
|
+
user = createUser(trimmedUsername, 'guest');
|
|
56
|
+
|
|
57
|
+
const builtinSkills = getBuiltinSkills().filter(s => s.enabled);
|
|
58
|
+
const userSkills = getUserSkills(user.id);
|
|
59
|
+
for (const skill of builtinSkills) {
|
|
60
|
+
const alreadyInstalled = userSkills.some(s => s.skillName === skill.skillName);
|
|
61
|
+
if (!alreadyInstalled) {
|
|
62
|
+
installUserSkill(user.id, skill.skillName, 'builtin');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
} else if (user.role === 'admin') {
|
|
66
|
+
updateUserRole(user.id, 'guest');
|
|
67
|
+
user = getUserById(user.id);
|
|
68
|
+
}
|
|
69
|
+
updateUserLogin(user!.id);
|
|
70
|
+
|
|
71
|
+
return NextResponse.json({
|
|
72
|
+
user: {
|
|
73
|
+
id: user!.id,
|
|
74
|
+
username: user!.username,
|
|
75
|
+
role: user!.role,
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.error('Login failed:', error);
|
|
80
|
+
return NextResponse.json(
|
|
81
|
+
{ error: '登录失败' },
|
|
82
|
+
{ status: 500 }
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { getUserByUsername, createUser, updateUserLogin } from '@/lib/db';
|
|
3
|
+
|
|
4
|
+
const FEISHU_APP_ID = process.env.FEISHU_APP_ID;
|
|
5
|
+
const FEISHU_APP_SECRET = process.env.FEISHU_APP_SECRET;
|
|
6
|
+
|
|
7
|
+
const GITHUB_CLIENT_ID = process.env.GITHUB_CLIENT_ID;
|
|
8
|
+
const GITHUB_CLIENT_SECRET = process.env.GITHUB_CLIENT_SECRET;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 飞书 OAuth 登录
|
|
12
|
+
*/
|
|
13
|
+
export async function POST(request: NextRequest) {
|
|
14
|
+
try {
|
|
15
|
+
const body = await request.json();
|
|
16
|
+
const { code, provider } = body;
|
|
17
|
+
|
|
18
|
+
if (!provider || !code) {
|
|
19
|
+
return NextResponse.json(
|
|
20
|
+
{ error: '缺少必要参数: provider, code' },
|
|
21
|
+
{ status: 400 }
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let userId: string;
|
|
26
|
+
let username: string;
|
|
27
|
+
let email: string = '';
|
|
28
|
+
|
|
29
|
+
if (provider === 'feishu') {
|
|
30
|
+
if (!FEISHU_APP_ID || !FEISHU_APP_SECRET) {
|
|
31
|
+
return NextResponse.json(
|
|
32
|
+
{ error: '飞书登录未配置' },
|
|
33
|
+
{ status: 500 }
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 使用 code 换取用户信息
|
|
38
|
+
const tokenResponse = await fetch('https://open.feishu.cn/open-apis/authen/v1/access_token', {
|
|
39
|
+
method: 'POST',
|
|
40
|
+
headers: { 'Content-Type': 'application/json' },
|
|
41
|
+
body: JSON.stringify({
|
|
42
|
+
grant_type: 'authorization_code',
|
|
43
|
+
client_id: FEISHU_APP_ID,
|
|
44
|
+
client_secret: FEISHU_APP_SECRET,
|
|
45
|
+
code,
|
|
46
|
+
}),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const tokenData = await tokenResponse.json();
|
|
50
|
+
if (tokenData.code !== 0) {
|
|
51
|
+
return NextResponse.json(
|
|
52
|
+
{ error: '飞书授权失败' },
|
|
53
|
+
{ status: 400 }
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const accessToken = tokenData.access_token;
|
|
58
|
+
|
|
59
|
+
// 获取用户信息
|
|
60
|
+
const userResponse = await fetch('https://open.feishu.cn/open-apis/authen/v1/user_info', {
|
|
61
|
+
headers: { Authorization: `Bearer ${accessToken}` },
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const userData = await userResponse.json();
|
|
65
|
+
if (userData.code !== 0) {
|
|
66
|
+
return NextResponse.json(
|
|
67
|
+
{ error: '获取飞书用户信息失败' },
|
|
68
|
+
{ status: 400 }
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
userId = `feishu-${userData.data.user_id}`;
|
|
73
|
+
username = userData.data.name || userData.data.nick_name || '飞书用户';
|
|
74
|
+
email = userData.data.email || '';
|
|
75
|
+
} else if (provider === 'github') {
|
|
76
|
+
if (!GITHUB_CLIENT_ID || !GITHUB_CLIENT_SECRET) {
|
|
77
|
+
return NextResponse.json(
|
|
78
|
+
{ error: 'GitHub登录未配置' },
|
|
79
|
+
{ status: 500 }
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 使用 code 换取 access_token
|
|
84
|
+
const tokenResponse = await fetch('https://github.com/login/oauth/access_token', {
|
|
85
|
+
method: 'POST',
|
|
86
|
+
headers: {
|
|
87
|
+
'Content-Type': 'application/json',
|
|
88
|
+
Accept: 'application/json',
|
|
89
|
+
},
|
|
90
|
+
body: JSON.stringify({
|
|
91
|
+
client_id: GITHUB_CLIENT_ID,
|
|
92
|
+
client_secret: GITHUB_CLIENT_SECRET,
|
|
93
|
+
code,
|
|
94
|
+
}),
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const tokenData = await tokenResponse.json();
|
|
98
|
+
if (!tokenData.access_token) {
|
|
99
|
+
return NextResponse.json(
|
|
100
|
+
{ error: 'GitHub授权失败' },
|
|
101
|
+
{ status: 400 }
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 获取用户信息
|
|
106
|
+
const userResponse = await fetch('https://api.github.com/user', {
|
|
107
|
+
headers: { Authorization: `Bearer ${tokenData.access_token}` },
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const userData = await userResponse.json();
|
|
111
|
+
userId = `github-${userData.id}`;
|
|
112
|
+
username = userData.login || 'GitHub用户';
|
|
113
|
+
email = userData.email || '';
|
|
114
|
+
} else {
|
|
115
|
+
return NextResponse.json(
|
|
116
|
+
{ error: '不支持的登录提供商' },
|
|
117
|
+
{ status: 400 }
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// 查找或创建用户
|
|
122
|
+
let user = getUserByUsername(username);
|
|
123
|
+
if (!user) {
|
|
124
|
+
user = createUser(username);
|
|
125
|
+
} else {
|
|
126
|
+
updateUserLogin(user.id);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return NextResponse.json({
|
|
130
|
+
user: {
|
|
131
|
+
id: user.id,
|
|
132
|
+
username: user.username,
|
|
133
|
+
email,
|
|
134
|
+
provider,
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.error('OAuth login failed:', error);
|
|
139
|
+
return NextResponse.json(
|
|
140
|
+
{ error: '登录失败' },
|
|
141
|
+
{ status: 500 }
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* 获取OAuth登录URL(用于前端跳转)
|
|
148
|
+
*/
|
|
149
|
+
export async function GET(request: NextRequest) {
|
|
150
|
+
const provider = request.nextUrl.searchParams.get('provider');
|
|
151
|
+
|
|
152
|
+
if (provider === 'feishu') {
|
|
153
|
+
const redirectUri = process.env.FEISHU_REDIRECT_URI || 'http://localhost:3000/api/auth/callback/feishu';
|
|
154
|
+
const url = `https://open.feishu.cn/open-apis/authen/v1/authorize?app_id=${FEISHU_APP_ID}&redirect_uri=${encodeURIComponent(redirectUri)}`;
|
|
155
|
+
return NextResponse.json({ url });
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (provider === 'github') {
|
|
159
|
+
const redirectUri = process.env.GITHUB_REDIRECT_URI || 'http://localhost:3000/api/auth/callback/github';
|
|
160
|
+
const url = `https://github.com/login/oauth/authorize?client_id=${GITHUB_CLIENT_ID}&redirect_uri=${encodeURIComponent(redirectUri)}&scope=read:user`;
|
|
161
|
+
return NextResponse.json({ url });
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return NextResponse.json(
|
|
165
|
+
{ error: '不支持的登录提供商' },
|
|
166
|
+
{ status: 400 }
|
|
167
|
+
);
|
|
168
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration Providers API
|
|
3
|
+
*
|
|
4
|
+
* Manages AI provider configurations.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
8
|
+
import {
|
|
9
|
+
loadModelsConfig,
|
|
10
|
+
saveModelsConfig,
|
|
11
|
+
upsertProvider,
|
|
12
|
+
deleteProvider,
|
|
13
|
+
validateProvider,
|
|
14
|
+
getAvailableProviders,
|
|
15
|
+
ModelsConfig,
|
|
16
|
+
} from '@/lib/pi-config';
|
|
17
|
+
|
|
18
|
+
export const runtime = 'nodejs';
|
|
19
|
+
export const dynamic = 'force-dynamic';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* GET /api/config/providers - List all providers
|
|
23
|
+
*/
|
|
24
|
+
export async function GET() {
|
|
25
|
+
try {
|
|
26
|
+
const providers = getAvailableProviders();
|
|
27
|
+
return NextResponse.json({ providers });
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.error('[config/providers] Error:', error);
|
|
30
|
+
return NextResponse.json(
|
|
31
|
+
{ error: 'Failed to load providers' },
|
|
32
|
+
{ status: 500 }
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* POST /api/config/providers - Add or update a provider
|
|
39
|
+
*/
|
|
40
|
+
export async function POST(request: NextRequest) {
|
|
41
|
+
try {
|
|
42
|
+
const body = await request.json();
|
|
43
|
+
const { id, ...provider } = body;
|
|
44
|
+
|
|
45
|
+
if (!id) {
|
|
46
|
+
return NextResponse.json(
|
|
47
|
+
{ error: 'Provider ID is required' },
|
|
48
|
+
{ status: 400 }
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Validate provider config
|
|
53
|
+
const validation = validateProvider(provider);
|
|
54
|
+
if (!validation.valid) {
|
|
55
|
+
return NextResponse.json(
|
|
56
|
+
{ error: 'Invalid provider config', details: validation.errors },
|
|
57
|
+
{ status: 400 }
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Save provider
|
|
62
|
+
const config = upsertProvider(id, provider);
|
|
63
|
+
|
|
64
|
+
return NextResponse.json({
|
|
65
|
+
success: true,
|
|
66
|
+
providers: getAvailableProviders(),
|
|
67
|
+
});
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.error('[config/providers] Error saving:', error);
|
|
70
|
+
return NextResponse.json(
|
|
71
|
+
{ error: error instanceof Error ? error.message : 'Failed to save provider' },
|
|
72
|
+
{ status: 500 }
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* DELETE /api/config/providers - Delete a provider
|
|
79
|
+
*/
|
|
80
|
+
export async function DELETE(request: NextRequest) {
|
|
81
|
+
try {
|
|
82
|
+
const { searchParams } = new URL(request.url);
|
|
83
|
+
const id = searchParams.get('id');
|
|
84
|
+
|
|
85
|
+
if (!id) {
|
|
86
|
+
return NextResponse.json(
|
|
87
|
+
{ error: 'Provider ID is required' },
|
|
88
|
+
{ status: 400 }
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const config = deleteProvider(id);
|
|
93
|
+
|
|
94
|
+
return NextResponse.json({
|
|
95
|
+
success: true,
|
|
96
|
+
providers: getAvailableProviders(),
|
|
97
|
+
});
|
|
98
|
+
} catch (error) {
|
|
99
|
+
console.error('[config/providers] Error deleting:', error);
|
|
100
|
+
return NextResponse.json(
|
|
101
|
+
{ error: 'Failed to delete provider' },
|
|
102
|
+
{ status: 500 }
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration API - GET/PUT
|
|
3
|
+
*
|
|
4
|
+
* Manages PI Agent model provider configuration.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
8
|
+
import {
|
|
9
|
+
getAvailableProviders,
|
|
10
|
+
getDefaultProvider,
|
|
11
|
+
getProviderById,
|
|
12
|
+
loadModelsConfig,
|
|
13
|
+
saveModelsConfig,
|
|
14
|
+
upsertProvider,
|
|
15
|
+
deleteProvider,
|
|
16
|
+
validateProvider,
|
|
17
|
+
} from '@/lib/pi-config';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* GET /api/config - Get current configuration
|
|
21
|
+
*/
|
|
22
|
+
export async function GET(request: NextRequest) {
|
|
23
|
+
try {
|
|
24
|
+
const providers = getAvailableProviders();
|
|
25
|
+
const defaultProvider = getDefaultProvider();
|
|
26
|
+
const rawConfig = loadModelsConfig();
|
|
27
|
+
|
|
28
|
+
return NextResponse.json({
|
|
29
|
+
providers,
|
|
30
|
+
defaultProvider: defaultProvider?.id || null,
|
|
31
|
+
rawConfig,
|
|
32
|
+
});
|
|
33
|
+
} catch (error) {
|
|
34
|
+
return NextResponse.json(
|
|
35
|
+
{ error: error instanceof Error ? error.message : 'Failed to load configuration' },
|
|
36
|
+
{ status: 500 },
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* PUT /api/config - Update configuration
|
|
43
|
+
*/
|
|
44
|
+
export async function PUT(request: NextRequest) {
|
|
45
|
+
try {
|
|
46
|
+
const body = await request.json();
|
|
47
|
+
const { action, provider, providerId } = body;
|
|
48
|
+
|
|
49
|
+
switch (action) {
|
|
50
|
+
case 'add':
|
|
51
|
+
case 'update': {
|
|
52
|
+
// Validate provider data
|
|
53
|
+
const validation = validateProvider(provider);
|
|
54
|
+
if (!validation.valid) {
|
|
55
|
+
return NextResponse.json(
|
|
56
|
+
{ error: 'Invalid provider configuration', errors: validation.errors },
|
|
57
|
+
{ status: 400 },
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Provider ID is required
|
|
62
|
+
const id = providerId || provider.id;
|
|
63
|
+
if (!id) {
|
|
64
|
+
return NextResponse.json(
|
|
65
|
+
{ error: 'providerId is required' },
|
|
66
|
+
{ status: 400 },
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Save provider
|
|
71
|
+
const config = upsertProvider(id, provider);
|
|
72
|
+
|
|
73
|
+
return NextResponse.json({
|
|
74
|
+
success: true,
|
|
75
|
+
message: `Provider "${id}" ${action === 'add' ? 'added' : 'updated'}`,
|
|
76
|
+
config,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
case 'delete': {
|
|
81
|
+
if (!providerId) {
|
|
82
|
+
return NextResponse.json(
|
|
83
|
+
{ error: 'providerId is required for delete action' },
|
|
84
|
+
{ status: 400 },
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const config = deleteProvider(providerId);
|
|
89
|
+
if (!config) {
|
|
90
|
+
return NextResponse.json(
|
|
91
|
+
{ error: `Provider "${providerId}" not found` },
|
|
92
|
+
{ status: 404 },
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return NextResponse.json({
|
|
97
|
+
success: true,
|
|
98
|
+
message: `Provider "${providerId}" deleted`,
|
|
99
|
+
config,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
default:
|
|
104
|
+
return NextResponse.json(
|
|
105
|
+
{ error: `Unknown action: ${action}` },
|
|
106
|
+
{ status: 400 },
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
} catch (error) {
|
|
110
|
+
return NextResponse.json(
|
|
111
|
+
{ error: error instanceof Error ? error.message : 'Failed to update configuration' },
|
|
112
|
+
{ status: 500 },
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
}
|