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,100 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { getDocuments, createDocument, updateKnowledgeActive } from '@/lib/db';
|
|
3
|
+
import type { KnowledgeType, KnowledgeSourceType } from '@/lib/db';
|
|
4
|
+
|
|
5
|
+
// GET /api/knowledge - 获取知识列表
|
|
6
|
+
export async function GET(request: NextRequest) {
|
|
7
|
+
try {
|
|
8
|
+
const searchParams = request.nextUrl.searchParams;
|
|
9
|
+
const category = searchParams.get('category') || undefined;
|
|
10
|
+
const author = searchParams.get('author') || undefined;
|
|
11
|
+
const limit = searchParams.get('limit') ? parseInt(searchParams.get('limit')!) : undefined;
|
|
12
|
+
const offset = searchParams.get('offset') ? parseInt(searchParams.get('offset')!) : undefined;
|
|
13
|
+
const isActive = searchParams.get('isActive') ? searchParams.get('isActive') === 'true' : undefined;
|
|
14
|
+
const type = searchParams.get('type') || undefined;
|
|
15
|
+
const search = searchParams.get('search') || undefined;
|
|
16
|
+
|
|
17
|
+
// 如果有搜索或分页参数,使用分页模式
|
|
18
|
+
if (search || offset !== undefined) {
|
|
19
|
+
const result = await getDocuments({ category, author, limit, offset, isActive, type, search });
|
|
20
|
+
return NextResponse.json(result);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const documents = await getDocuments({ category, author, limit, isActive, type });
|
|
24
|
+
|
|
25
|
+
return NextResponse.json({ documents });
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error('Failed to get knowledge:', error);
|
|
28
|
+
return NextResponse.json(
|
|
29
|
+
{ error: 'Failed to get knowledge' },
|
|
30
|
+
{ status: 500 }
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// POST /api/knowledge - 创建新知识
|
|
36
|
+
export async function POST(request: NextRequest) {
|
|
37
|
+
try {
|
|
38
|
+
const body = await request.json();
|
|
39
|
+
const { title, content, category, tags, author, type, sourceType, sessionId, isActive } = body;
|
|
40
|
+
|
|
41
|
+
if (!title || !author) {
|
|
42
|
+
return NextResponse.json(
|
|
43
|
+
{ error: 'Title and author are required' },
|
|
44
|
+
{ status: 400 }
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const document = await createDocument({
|
|
49
|
+
title,
|
|
50
|
+
content: content || '',
|
|
51
|
+
category,
|
|
52
|
+
tags,
|
|
53
|
+
author,
|
|
54
|
+
type: type as KnowledgeType,
|
|
55
|
+
sourceType: sourceType as KnowledgeSourceType,
|
|
56
|
+
sessionId,
|
|
57
|
+
isActive: isActive !== false,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return NextResponse.json({ document }, { status: 201 });
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error('Failed to create knowledge:', error);
|
|
63
|
+
return NextResponse.json(
|
|
64
|
+
{ error: 'Failed to create knowledge' },
|
|
65
|
+
{ status: 500 }
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// PATCH /api/knowledge - 批量更新激活状态
|
|
71
|
+
export async function PATCH(request: NextRequest) {
|
|
72
|
+
try {
|
|
73
|
+
const body = await request.json();
|
|
74
|
+
const { id, isActive } = body;
|
|
75
|
+
|
|
76
|
+
if (!id || isActive === undefined) {
|
|
77
|
+
return NextResponse.json(
|
|
78
|
+
{ error: 'id and isActive are required' },
|
|
79
|
+
{ status: 400 }
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const document = await updateKnowledgeActive(id, isActive);
|
|
84
|
+
|
|
85
|
+
if (!document) {
|
|
86
|
+
return NextResponse.json(
|
|
87
|
+
{ error: 'Knowledge not found' },
|
|
88
|
+
{ status: 404 }
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return NextResponse.json({ document });
|
|
93
|
+
} catch (error) {
|
|
94
|
+
console.error('Failed to update knowledge:', error);
|
|
95
|
+
return NextResponse.json(
|
|
96
|
+
{ error: 'Failed to update knowledge' },
|
|
97
|
+
{ status: 500 }
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { getDocument, updateDocument } from '@/lib/db';
|
|
3
|
+
import { getMarketClient } from '@/lib/market-client';
|
|
4
|
+
|
|
5
|
+
// DELETE /api/market/knowledge/[id] - 从市场中移除知识
|
|
6
|
+
export async function DELETE(
|
|
7
|
+
request: NextRequest,
|
|
8
|
+
{ params }: { params: Promise<{ id: string }> }
|
|
9
|
+
) {
|
|
10
|
+
try {
|
|
11
|
+
const { id } = await params;
|
|
12
|
+
const { searchParams } = new URL(request.url);
|
|
13
|
+
const userId = searchParams.get('userId');
|
|
14
|
+
const role = searchParams.get('role');
|
|
15
|
+
|
|
16
|
+
// 权限检查:需要管理员权限才能删除市场内容
|
|
17
|
+
if (!userId || role !== 'admin') {
|
|
18
|
+
return NextResponse.json(
|
|
19
|
+
{ error: '需要管理员权限' },
|
|
20
|
+
{ status: 403 }
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const client = getMarketClient();
|
|
25
|
+
const document = getDocument(id); // 本地可能不存在
|
|
26
|
+
|
|
27
|
+
if (client.isRemoteMode()) {
|
|
28
|
+
// 远程模式:直接调用远程市场 API 删除(即使本地不存在也可以删除)
|
|
29
|
+
try {
|
|
30
|
+
const response = await fetch(
|
|
31
|
+
`${client['baseUrl']}/api/market/knowledge/${id}?api_key=${client['apiKey']}`,
|
|
32
|
+
{ method: 'DELETE' }
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
if (!response.ok) {
|
|
36
|
+
const errorText = await response.text();
|
|
37
|
+
console.error('[market/knowledge] Remote delete failed:', errorText);
|
|
38
|
+
|
|
39
|
+
// 如果远程返回404,说明已经不存在了,也算成功
|
|
40
|
+
if (response.status === 404) {
|
|
41
|
+
console.log('[market/knowledge] Already removed from remote market:', id);
|
|
42
|
+
} else {
|
|
43
|
+
throw new Error(`Remote market error: ${response.status}`);
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
console.log('[market/knowledge] Removed from remote market:', id);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 如果本地存在,更新本地的 market_status
|
|
50
|
+
if (document) {
|
|
51
|
+
await updateDocument(id, { marketStatus: undefined } as any);
|
|
52
|
+
console.log('[market/knowledge] Cleared local market_status:', id);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return NextResponse.json({
|
|
56
|
+
success: true,
|
|
57
|
+
message: '已从远程市场移除'
|
|
58
|
+
});
|
|
59
|
+
} catch (error: any) {
|
|
60
|
+
console.error('[market/knowledge] Failed to remove from remote:', error.message);
|
|
61
|
+
return NextResponse.json(
|
|
62
|
+
{ error: '从远程市场移除失败,请稍后重试' },
|
|
63
|
+
{ status: 500 }
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
// 本地模式:需要检查本地是否存在
|
|
68
|
+
if (!document) {
|
|
69
|
+
return NextResponse.json(
|
|
70
|
+
{ error: '知识不存在' },
|
|
71
|
+
{ status: 404 }
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 只清除本地 market_status
|
|
76
|
+
const updated = await updateDocument(id, { marketStatus: undefined } as any);
|
|
77
|
+
console.log('[market/knowledge] Removed from local market:', id);
|
|
78
|
+
|
|
79
|
+
return NextResponse.json({
|
|
80
|
+
success: true,
|
|
81
|
+
knowledge: updated,
|
|
82
|
+
message: '已从本地市场移除'
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.error('Failed to remove knowledge from market:', error);
|
|
87
|
+
return NextResponse.json(
|
|
88
|
+
{ error: '从市场中移除知识失败' },
|
|
89
|
+
{ status: 500 }
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { getMarketKnowledge, getDocument } from '@/lib/db';
|
|
3
|
+
import { getMarketClient } from '@/lib/market-client';
|
|
4
|
+
|
|
5
|
+
// 获取市场知识列表
|
|
6
|
+
export async function GET(request: NextRequest) {
|
|
7
|
+
try {
|
|
8
|
+
const category = request.nextUrl.searchParams.get('category') || undefined;
|
|
9
|
+
const search = request.nextUrl.searchParams.get('search') || undefined;
|
|
10
|
+
const limit = request.nextUrl.searchParams.get('limit');
|
|
11
|
+
const offset = request.nextUrl.searchParams.get('offset');
|
|
12
|
+
const id = request.nextUrl.searchParams.get('id');
|
|
13
|
+
|
|
14
|
+
const client = getMarketClient();
|
|
15
|
+
|
|
16
|
+
// 获取单个知识详情
|
|
17
|
+
if (id) {
|
|
18
|
+
if (client.isRemoteMode()) {
|
|
19
|
+
// 远程模式:调用远程 API
|
|
20
|
+
const knowledge = await client.getKnowledge({ limit: 1000 });
|
|
21
|
+
const doc = knowledge.find((k: any) => k.id === id);
|
|
22
|
+
if (!doc) {
|
|
23
|
+
return NextResponse.json(
|
|
24
|
+
{ error: '知识不存在' },
|
|
25
|
+
{ status: 404 }
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
return NextResponse.json({ knowledge: doc, source: 'remote' });
|
|
29
|
+
} else {
|
|
30
|
+
// 本地模式:查询本地数据库
|
|
31
|
+
const doc = getDocument(id);
|
|
32
|
+
if (!doc) {
|
|
33
|
+
return NextResponse.json(
|
|
34
|
+
{ error: '知识不存在' },
|
|
35
|
+
{ status: 404 }
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
return NextResponse.json({ knowledge: doc, source: 'local' });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 获取知识列表
|
|
43
|
+
if (client.isRemoteMode()) {
|
|
44
|
+
// 远程模式:调用远程 API(不降级)
|
|
45
|
+
const knowledge = await client.getKnowledge({
|
|
46
|
+
category,
|
|
47
|
+
search,
|
|
48
|
+
limit: limit ? parseInt(limit) : undefined,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return NextResponse.json({
|
|
52
|
+
knowledge,
|
|
53
|
+
total: knowledge.length,
|
|
54
|
+
source: 'remote',
|
|
55
|
+
});
|
|
56
|
+
} else {
|
|
57
|
+
// 本地模式:查询本地已发布知识
|
|
58
|
+
if (offset) {
|
|
59
|
+
const result = getMarketKnowledge({
|
|
60
|
+
category,
|
|
61
|
+
search,
|
|
62
|
+
limit: limit ? parseInt(limit) : 10,
|
|
63
|
+
offset: parseInt(offset),
|
|
64
|
+
});
|
|
65
|
+
return NextResponse.json({ ...result, source: 'local' });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const knowledge = getMarketKnowledge({
|
|
69
|
+
category,
|
|
70
|
+
search,
|
|
71
|
+
limit: limit ? parseInt(limit) : undefined,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return NextResponse.json({ knowledge, source: 'local' });
|
|
75
|
+
}
|
|
76
|
+
} catch (error: any) {
|
|
77
|
+
console.error('Get market knowledge failed:', error);
|
|
78
|
+
|
|
79
|
+
// 返回明确的错误信息
|
|
80
|
+
return NextResponse.json(
|
|
81
|
+
{
|
|
82
|
+
error: error.message || '获取市场知识失败',
|
|
83
|
+
code: 'MARKET_UNAVAILABLE'
|
|
84
|
+
},
|
|
85
|
+
{ status: 503 }
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 发布知识到市场
|
|
91
|
+
export async function POST(request: NextRequest) {
|
|
92
|
+
try {
|
|
93
|
+
const body = await request.json();
|
|
94
|
+
const { id, author } = body;
|
|
95
|
+
|
|
96
|
+
if (!id || !author) {
|
|
97
|
+
return NextResponse.json(
|
|
98
|
+
{ error: '缺少必要参数: id, author' },
|
|
99
|
+
{ status: 400 }
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const client = getMarketClient();
|
|
104
|
+
|
|
105
|
+
// 发布知识(本地模式或远程模式)
|
|
106
|
+
const knowledge = await client.publishKnowledge(id, author);
|
|
107
|
+
|
|
108
|
+
if (!knowledge) {
|
|
109
|
+
return NextResponse.json(
|
|
110
|
+
{ error: '知识不存在' },
|
|
111
|
+
{ status: 404 }
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return NextResponse.json({
|
|
116
|
+
knowledge,
|
|
117
|
+
mode: client.isRemoteMode() ? 'remote' : 'local'
|
|
118
|
+
}, { status: 201 });
|
|
119
|
+
} catch (error: any) {
|
|
120
|
+
console.error('Publish knowledge failed:', error);
|
|
121
|
+
|
|
122
|
+
return NextResponse.json(
|
|
123
|
+
{
|
|
124
|
+
error: error.message || '发布失败',
|
|
125
|
+
code: 'PUBLISH_FAILED'
|
|
126
|
+
},
|
|
127
|
+
{ status: 500 }
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PUT /api/marketplace/skills/:id/approve - 管理员审核通过技能
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
6
|
+
import { reviewSkill } from '@/lib/market-db';
|
|
7
|
+
import { getUserById } from '@/lib/db';
|
|
8
|
+
|
|
9
|
+
type RouteContext = {
|
|
10
|
+
params: Promise<{ id: string }>;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 检查用户是否是管理员
|
|
15
|
+
*/
|
|
16
|
+
function isAdmin(userId: string): boolean {
|
|
17
|
+
const user = getUserById(userId);
|
|
18
|
+
return user?.role === 'admin';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function PUT(request: NextRequest, context: RouteContext) {
|
|
22
|
+
try {
|
|
23
|
+
const { id } = await context.params;
|
|
24
|
+
const body = await request.json();
|
|
25
|
+
const { reviewerId, reviewerName, comment } = body;
|
|
26
|
+
|
|
27
|
+
// 验证必填字段
|
|
28
|
+
if (!reviewerId) {
|
|
29
|
+
return NextResponse.json(
|
|
30
|
+
{ error: '缺少审核人ID' },
|
|
31
|
+
{ status: 400 },
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 验证管理员权限
|
|
36
|
+
if (!isAdmin(reviewerId)) {
|
|
37
|
+
return NextResponse.json(
|
|
38
|
+
{ error: '需要管理员权限' },
|
|
39
|
+
{ status: 403 },
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 获取审核人信息
|
|
44
|
+
const reviewer = getUserById(reviewerId);
|
|
45
|
+
if (!reviewer) {
|
|
46
|
+
return NextResponse.json(
|
|
47
|
+
{ error: '审核人不存在' },
|
|
48
|
+
{ status: 404 },
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 审核通过
|
|
53
|
+
const skill = reviewSkill(id, {
|
|
54
|
+
reviewerId,
|
|
55
|
+
reviewerName: reviewerName || reviewer.username,
|
|
56
|
+
approved: true,
|
|
57
|
+
comment,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return NextResponse.json({ skill });
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error('[marketplace/skills/:id/approve] PUT failed:', error);
|
|
63
|
+
return NextResponse.json(
|
|
64
|
+
{ error: error instanceof Error ? error.message : '审核失败' },
|
|
65
|
+
{ status: 500 },
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PUT /api/marketplace/skills/:id/certify - 管理员设置/取消官方认证
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
6
|
+
import { certifySkill } from '@/lib/market-db';
|
|
7
|
+
import { getUserById } from '@/lib/db';
|
|
8
|
+
|
|
9
|
+
type RouteContext = {
|
|
10
|
+
params: Promise<{ id: string }>;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 检查用户是否是管理员
|
|
15
|
+
*/
|
|
16
|
+
function isAdmin(userId: string): boolean {
|
|
17
|
+
const user = getUserById(userId);
|
|
18
|
+
return user?.role === 'admin';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function PUT(request: NextRequest, context: RouteContext) {
|
|
22
|
+
try {
|
|
23
|
+
const { id } = await context.params;
|
|
24
|
+
const body = await request.json();
|
|
25
|
+
const { reviewerId, isOfficial } = body;
|
|
26
|
+
|
|
27
|
+
// 验证必填字段
|
|
28
|
+
if (reviewerId === undefined || isOfficial === undefined) {
|
|
29
|
+
return NextResponse.json(
|
|
30
|
+
{ error: '缺少必要参数: reviewerId, isOfficial' },
|
|
31
|
+
{ status: 400 },
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 验证管理员权限
|
|
36
|
+
if (!isAdmin(reviewerId)) {
|
|
37
|
+
return NextResponse.json(
|
|
38
|
+
{ error: '需要管理员权限' },
|
|
39
|
+
{ status: 403 },
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 设置/取消官方认证
|
|
44
|
+
const skill = certifySkill(id, isOfficial, reviewerId);
|
|
45
|
+
|
|
46
|
+
return NextResponse.json({ skill });
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.error('[marketplace/skills/:id/certify] PUT failed:', error);
|
|
49
|
+
return NextResponse.json(
|
|
50
|
+
{ error: error instanceof Error ? error.message : '设置认证失败' },
|
|
51
|
+
{ status: 500 },
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 技能安装 API - 安装市场技能到用户私有目录
|
|
3
|
+
*
|
|
4
|
+
* POST /api/marketplace/skills/:id/install - 安装技能
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
8
|
+
import {
|
|
9
|
+
getMarketSkillById,
|
|
10
|
+
getMarketSkillDetailById,
|
|
11
|
+
isSkillInstalledByUser,
|
|
12
|
+
addUserInstalledSkill,
|
|
13
|
+
incrementDownloadCount,
|
|
14
|
+
} from '@/lib/market-db';
|
|
15
|
+
import { unpackSkillFromZip } from '@/lib/zip-tool';
|
|
16
|
+
import { getUserById, getDb } from '@/lib/db';
|
|
17
|
+
import { getMarketClient } from '@/lib/market-client';
|
|
18
|
+
import { mkdirSync, existsSync, rmSync } from 'fs';
|
|
19
|
+
import { join } from 'path';
|
|
20
|
+
|
|
21
|
+
// 用户技能目录
|
|
22
|
+
const USER_SKILLS_BASE = join(process.cwd(), 'data', 'skills', 'users');
|
|
23
|
+
|
|
24
|
+
type RouteContext = {
|
|
25
|
+
params: Promise<{ id: string }>;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* POST /api/marketplace/skills/:id/install
|
|
30
|
+
*
|
|
31
|
+
* 请求体:
|
|
32
|
+
* - userId: 用户ID
|
|
33
|
+
*
|
|
34
|
+
* 流程:
|
|
35
|
+
* 1. 验证用户身份
|
|
36
|
+
* 2. 验证技能存在且已审核
|
|
37
|
+
* 3. 检查是否已安装
|
|
38
|
+
* 4. 从远程市场服务下载并安装(优先)或从本地ZIP安装(降级)
|
|
39
|
+
* 5. 记录安装到数据库
|
|
40
|
+
* 6. 增加下载计数
|
|
41
|
+
*/
|
|
42
|
+
export async function POST(
|
|
43
|
+
request: NextRequest,
|
|
44
|
+
context: RouteContext
|
|
45
|
+
) {
|
|
46
|
+
try {
|
|
47
|
+
const { id: skillId } = await context.params;
|
|
48
|
+
const body = await request.json();
|
|
49
|
+
const { userId } = body;
|
|
50
|
+
|
|
51
|
+
// 验证必填字段
|
|
52
|
+
if (!userId) {
|
|
53
|
+
return NextResponse.json(
|
|
54
|
+
{ error: '缺少必要参数: userId' },
|
|
55
|
+
{ status: 400 },
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 验证用户存在
|
|
60
|
+
const user = getUserById(userId);
|
|
61
|
+
if (!user) {
|
|
62
|
+
return NextResponse.json(
|
|
63
|
+
{ error: '用户不存在' },
|
|
64
|
+
{ status: 404 },
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 获取技能信息
|
|
69
|
+
const skill = getMarketSkillById(skillId);
|
|
70
|
+
if (!skill) {
|
|
71
|
+
return NextResponse.json(
|
|
72
|
+
{ error: '技能不存在' },
|
|
73
|
+
{ status: 404 },
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 只能安装已审核通过的技能
|
|
78
|
+
if (skill.status !== 'approved') {
|
|
79
|
+
return NextResponse.json(
|
|
80
|
+
{ error: `技能状态为 ${skill.status},无法安装` },
|
|
81
|
+
{ status: 400 },
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 检查是否已安装
|
|
86
|
+
if (isSkillInstalledByUser(userId, skillId)) {
|
|
87
|
+
return NextResponse.json(
|
|
88
|
+
{ error: '技能已安装', skillId },
|
|
89
|
+
{ status: 409 },
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const client = getMarketClient();
|
|
94
|
+
|
|
95
|
+
// 如果启用了远程服务,优先从远程安装
|
|
96
|
+
if (client.isRemoteEnabled()) {
|
|
97
|
+
try {
|
|
98
|
+
const result = await client.installSkill(userId, skillId);
|
|
99
|
+
return NextResponse.json({
|
|
100
|
+
...result,
|
|
101
|
+
source: 'remote',
|
|
102
|
+
});
|
|
103
|
+
} catch (error) {
|
|
104
|
+
console.warn('[marketplace/skills/:id/install] Remote install failed, falling back to local:', error);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 降级逻辑:使用本地安装
|
|
109
|
+
const skillDetail = getMarketSkillDetailById(skillId);
|
|
110
|
+
if (!skillDetail) {
|
|
111
|
+
return NextResponse.json(
|
|
112
|
+
{ error: '无法读取技能详情' },
|
|
113
|
+
{ status: 500 },
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 获取ZIP文件路径(从数据库查询)
|
|
118
|
+
const db = getDb();
|
|
119
|
+
const row = db
|
|
120
|
+
.prepare('SELECT file_path FROM skill_market WHERE id = ?')
|
|
121
|
+
.get(skillId) as any;
|
|
122
|
+
const zipPath = row?.file_path;
|
|
123
|
+
|
|
124
|
+
if (!zipPath || !existsSync(zipPath)) {
|
|
125
|
+
return NextResponse.json(
|
|
126
|
+
{ error: '技能文件不存在' },
|
|
127
|
+
{ status: 404 },
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 确保用户技能目录存在
|
|
132
|
+
const userSkillDir = join(USER_SKILLS_BASE, userId, skill.name);
|
|
133
|
+
if (!existsSync(join(USER_SKILLS_BASE, userId))) {
|
|
134
|
+
mkdirSync(join(USER_SKILLS_BASE, userId), { recursive: true });
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// 检查是否已存在同名技能
|
|
138
|
+
if (existsSync(userSkillDir)) {
|
|
139
|
+
return NextResponse.json(
|
|
140
|
+
{ error: `用户目录下已存在同名技能 "${skill.name}",请先删除` },
|
|
141
|
+
{ status: 409 },
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// 解压技能到用户私有目录
|
|
146
|
+
try {
|
|
147
|
+
unpackSkillFromZip(zipPath, userSkillDir);
|
|
148
|
+
} catch (error) {
|
|
149
|
+
console.error('[marketplace/skills/:id/install] Failed to unpack skill:', error);
|
|
150
|
+
return NextResponse.json(
|
|
151
|
+
{ error: '解压技能失败' },
|
|
152
|
+
{ status: 500 },
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// 记录安装到数据库
|
|
157
|
+
addUserInstalledSkill(userId, skillId);
|
|
158
|
+
|
|
159
|
+
// 增加下载计数
|
|
160
|
+
incrementDownloadCount(skillId);
|
|
161
|
+
|
|
162
|
+
return NextResponse.json(
|
|
163
|
+
{
|
|
164
|
+
success: true,
|
|
165
|
+
message: '技能安装成功',
|
|
166
|
+
skillId,
|
|
167
|
+
skillName: skill.name,
|
|
168
|
+
installedAt: new Date().toISOString(),
|
|
169
|
+
source: 'local',
|
|
170
|
+
},
|
|
171
|
+
{ status: 200 },
|
|
172
|
+
);
|
|
173
|
+
} catch (error) {
|
|
174
|
+
console.error('[marketplace/skills/:id/install] POST failed:', error);
|
|
175
|
+
return NextResponse.json(
|
|
176
|
+
{ error: error instanceof Error ? error.message : '安装技能失败' },
|
|
177
|
+
{ status: 500 },
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
}
|