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.
Files changed (245) hide show
  1. package/README.md +234 -0
  2. package/app/(admin)/approvals/page.tsx +16 -0
  3. package/app/(admin)/audit/page.tsx +18 -0
  4. package/app/(admin)/layout.tsx +47 -0
  5. package/app/(admin)/scheduled-tasks/page.tsx +17 -0
  6. package/app/(admin)/settings/page.tsx +46 -0
  7. package/app/(admin)/skills/[name]/page.tsx +378 -0
  8. package/app/(admin)/skills/page.tsx +406 -0
  9. package/app/(admin)/statistics/page.tsx +416 -0
  10. package/app/(admin)/tickets/[id]/page.tsx +348 -0
  11. package/app/(admin)/tickets/new/page.tsx +309 -0
  12. package/app/(admin)/tickets/page.tsx +27 -0
  13. package/app/api/audit/route.ts +30 -0
  14. package/app/api/auth/feishu/callback/route.ts +72 -0
  15. package/app/api/auth/feishu/login/route.ts +17 -0
  16. package/app/api/auth/feishu/sso/route.ts +78 -0
  17. package/app/api/auth/login/route.ts +85 -0
  18. package/app/api/auth/oauth/route.ts +168 -0
  19. package/app/api/config/providers/route.ts +105 -0
  20. package/app/api/config/route.ts +115 -0
  21. package/app/api/config/status/route.ts +56 -0
  22. package/app/api/config/test/route.ts +212 -0
  23. package/app/api/documents/[id]/route.ts +88 -0
  24. package/app/api/documents/route.ts +53 -0
  25. package/app/api/health/route.ts +32 -0
  26. package/app/api/knowledge/[id]/route.ts +152 -0
  27. package/app/api/knowledge/from-session/route.ts +27 -0
  28. package/app/api/knowledge/route.ts +100 -0
  29. package/app/api/market/knowledge/[id]/route.ts +92 -0
  30. package/app/api/market/knowledge/route.ts +130 -0
  31. package/app/api/marketplace/skills/[id]/approve/route.ts +68 -0
  32. package/app/api/marketplace/skills/[id]/certify/route.ts +54 -0
  33. package/app/api/marketplace/skills/[id]/install/route.ts +180 -0
  34. package/app/api/marketplace/skills/[id]/promote-to-system/route.ts +219 -0
  35. package/app/api/marketplace/skills/[id]/rate/route.ts +90 -0
  36. package/app/api/marketplace/skills/[id]/ratings/route.ts +55 -0
  37. package/app/api/marketplace/skills/[id]/reject/route.ts +68 -0
  38. package/app/api/marketplace/skills/[id]/route.ts +177 -0
  39. package/app/api/marketplace/skills/route.ts +235 -0
  40. package/app/api/memory/route.ts +40 -0
  41. package/app/api/my/files/[id]/route.ts +52 -0
  42. package/app/api/my/files/route.ts +230 -0
  43. package/app/api/my/knowledge/route.ts +36 -0
  44. package/app/api/pi-chat/route.ts +443 -0
  45. package/app/api/recommend/route.ts +38 -0
  46. package/app/api/scheduled-tasks/[id]/execute/route.ts +132 -0
  47. package/app/api/scheduled-tasks/[id]/route.ts +165 -0
  48. package/app/api/scheduled-tasks/[id]/toggle/route.ts +53 -0
  49. package/app/api/scheduled-tasks/route.ts +101 -0
  50. package/app/api/sessions/[id]/messages/route.ts +212 -0
  51. package/app/api/sessions/route.ts +101 -0
  52. package/app/api/share/file/[id]/route.ts +37 -0
  53. package/app/api/skills/[name]/execute/route.ts +121 -0
  54. package/app/api/skills/[name]/route.ts +167 -0
  55. package/app/api/skills/create/route.ts +65 -0
  56. package/app/api/skills/generate/route.ts +405 -0
  57. package/app/api/skills/installed/route.ts +151 -0
  58. package/app/api/skills/route.ts +174 -0
  59. package/app/api/skills/translate/route.ts +40 -0
  60. package/app/api/skills/user/[name]/route.ts +159 -0
  61. package/app/api/skills/user/route.ts +90 -0
  62. package/app/api/statistics/route.ts +94 -0
  63. package/app/api/task-executions/[id]/route.ts +34 -0
  64. package/app/api/task-executions/route.ts +29 -0
  65. package/app/api/tickets/[id]/approve/route.ts +129 -0
  66. package/app/api/tickets/[id]/execute/route.ts +201 -0
  67. package/app/api/tickets/[id]/route.ts +127 -0
  68. package/app/api/tickets/route.ts +103 -0
  69. package/app/api/user/skills/route.ts +175 -0
  70. package/app/api/users/route.ts +80 -0
  71. package/app/chat/page.tsx +5 -0
  72. package/app/globals.css +84 -0
  73. package/app/h5/layout.tsx +5 -0
  74. package/app/h5/mobile-approvals-page.tsx +167 -0
  75. package/app/h5/mobile-chat-page.tsx +951 -0
  76. package/app/h5/mobile-profile-page.tsx +147 -0
  77. package/app/h5/mobile-tickets-page.tsx +121 -0
  78. package/app/h5/page.tsx +23 -0
  79. package/app/h5/ticket-action-buttons.tsx +80 -0
  80. package/app/layout.tsx +26 -0
  81. package/app/login/page.tsx +318 -0
  82. package/app/market/knowledge/[id]/page.tsx +77 -0
  83. package/app/market/knowledge/page.tsx +358 -0
  84. package/app/market/layout.tsx +29 -0
  85. package/app/market/page.tsx +18 -0
  86. package/app/market/skills/page.tsx +397 -0
  87. package/app/my/files/page.tsx +511 -0
  88. package/app/my/knowledge/[id]/page.tsx +271 -0
  89. package/app/my/knowledge/new/page.tsx +234 -0
  90. package/app/my/knowledge/page.tsx +248 -0
  91. package/app/my/layout.tsx +32 -0
  92. package/app/my/memory/page.tsx +164 -0
  93. package/app/my/page.tsx +18 -0
  94. package/app/my/scheduled-tasks/[id]/edit/page.tsx +290 -0
  95. package/app/my/scheduled-tasks/[id]/executions/page.tsx +275 -0
  96. package/app/my/scheduled-tasks/[id]/page.tsx +284 -0
  97. package/app/my/scheduled-tasks/new/page.tsx +230 -0
  98. package/app/my/scheduled-tasks/page.tsx +27 -0
  99. package/app/my/skills/[name]/page.tsx +320 -0
  100. package/app/my/skills/new/page.tsx +394 -0
  101. package/app/my/skills/page.tsx +303 -0
  102. package/app/page.tsx +2288 -0
  103. package/app/share/[sessionId]/page.tsx +226 -0
  104. package/app/share/file/[id]/page.tsx +140 -0
  105. package/bin/README.md +63 -0
  106. package/bin/generate-api-system +300 -0
  107. package/bin/postinstall.js +95 -0
  108. package/bin/work-agent.js +173 -0
  109. package/components/ai-elements/agent.tsx +142 -0
  110. package/components/ai-elements/artifact.tsx +149 -0
  111. package/components/ai-elements/attachments.tsx +427 -0
  112. package/components/ai-elements/audio-player.tsx +232 -0
  113. package/components/ai-elements/canvas.tsx +26 -0
  114. package/components/ai-elements/chain-of-thought.tsx +223 -0
  115. package/components/ai-elements/checkpoint.tsx +72 -0
  116. package/components/ai-elements/code-block.tsx +555 -0
  117. package/components/ai-elements/commit.tsx +449 -0
  118. package/components/ai-elements/confirmation.tsx +173 -0
  119. package/components/ai-elements/connection.tsx +28 -0
  120. package/components/ai-elements/context.tsx +410 -0
  121. package/components/ai-elements/controls.tsx +19 -0
  122. package/components/ai-elements/conversation.tsx +167 -0
  123. package/components/ai-elements/edge.tsx +144 -0
  124. package/components/ai-elements/environment-variables.tsx +325 -0
  125. package/components/ai-elements/file-tree.tsx +298 -0
  126. package/components/ai-elements/image.tsx +25 -0
  127. package/components/ai-elements/inline-citation.tsx +294 -0
  128. package/components/ai-elements/jsx-preview.tsx +250 -0
  129. package/components/ai-elements/message.tsx +367 -0
  130. package/components/ai-elements/mic-selector.tsx +372 -0
  131. package/components/ai-elements/model-selector.tsx +214 -0
  132. package/components/ai-elements/node.tsx +72 -0
  133. package/components/ai-elements/open-in-chat.tsx +367 -0
  134. package/components/ai-elements/package-info.tsx +235 -0
  135. package/components/ai-elements/panel.tsx +16 -0
  136. package/components/ai-elements/persona.tsx +280 -0
  137. package/components/ai-elements/plan.tsx +144 -0
  138. package/components/ai-elements/prompt-input.tsx +1341 -0
  139. package/components/ai-elements/queue.tsx +275 -0
  140. package/components/ai-elements/reasoning.tsx +355 -0
  141. package/components/ai-elements/sandbox.tsx +133 -0
  142. package/components/ai-elements/schema-display.tsx +473 -0
  143. package/components/ai-elements/shimmer.tsx +78 -0
  144. package/components/ai-elements/snippet.tsx +141 -0
  145. package/components/ai-elements/sources.tsx +78 -0
  146. package/components/ai-elements/speech-input.tsx +324 -0
  147. package/components/ai-elements/stack-trace.tsx +531 -0
  148. package/components/ai-elements/suggestion.tsx +58 -0
  149. package/components/ai-elements/task.tsx +88 -0
  150. package/components/ai-elements/terminal.tsx +277 -0
  151. package/components/ai-elements/test-results.tsx +497 -0
  152. package/components/ai-elements/tool.tsx +174 -0
  153. package/components/ai-elements/toolbar.tsx +17 -0
  154. package/components/ai-elements/transcription.tsx +126 -0
  155. package/components/ai-elements/voice-selector.tsx +525 -0
  156. package/components/ai-elements/web-preview.tsx +282 -0
  157. package/components/audit-log-list.tsx +114 -0
  158. package/components/chat/EmptyPreviewState.tsx +12 -0
  159. package/components/chat/KnowledgePickerDialog.tsx +464 -0
  160. package/components/chat/KnowledgePreview.tsx +70 -0
  161. package/components/chat/KnowledgePreviewPanel.tsx +86 -0
  162. package/components/chat/MentionInput.tsx +309 -0
  163. package/components/chat/OrganizeDialog.tsx +258 -0
  164. package/components/chat/RecommendationBanner.tsx +94 -0
  165. package/components/chat/SaveToKnowledgeDialog.tsx +193 -0
  166. package/components/chat/SkillSelector.tsx +305 -0
  167. package/components/chat/SkillSwitcher.tsx +163 -0
  168. package/components/client-layout.tsx +15 -0
  169. package/components/knowledge/KnowledgeMetadataPanel.tsx +293 -0
  170. package/components/layout-wrapper.tsx +18 -0
  171. package/components/mobile-layout.tsx +62 -0
  172. package/components/scheduled-task-list.tsx +356 -0
  173. package/components/setup-guide.tsx +484 -0
  174. package/components/sub-nav.tsx +54 -0
  175. package/components/ticket-detail-content.tsx +383 -0
  176. package/components/ticket-list.tsx +366 -0
  177. package/components/top-nav.tsx +132 -0
  178. package/components/ui/accordion.tsx +58 -0
  179. package/components/ui/alert.tsx +59 -0
  180. package/components/ui/avatar.tsx +50 -0
  181. package/components/ui/badge.tsx +36 -0
  182. package/components/ui/button-group.tsx +83 -0
  183. package/components/ui/button.tsx +57 -0
  184. package/components/ui/card.tsx +91 -0
  185. package/components/ui/carousel.tsx +262 -0
  186. package/components/ui/collapsible.tsx +11 -0
  187. package/components/ui/command.tsx +153 -0
  188. package/components/ui/dialog.tsx +122 -0
  189. package/components/ui/dropdown-menu.tsx +200 -0
  190. package/components/ui/hover-card.tsx +29 -0
  191. package/components/ui/input-group.tsx +170 -0
  192. package/components/ui/input.tsx +22 -0
  193. package/components/ui/label.tsx +26 -0
  194. package/components/ui/popover.tsx +31 -0
  195. package/components/ui/progress.tsx +28 -0
  196. package/components/ui/scroll-area.tsx +48 -0
  197. package/components/ui/select.tsx +174 -0
  198. package/components/ui/separator.tsx +31 -0
  199. package/components/ui/spinner.tsx +16 -0
  200. package/components/ui/switch.tsx +29 -0
  201. package/components/ui/table.tsx +120 -0
  202. package/components/ui/tabs.tsx +55 -0
  203. package/components/ui/textarea.tsx +22 -0
  204. package/components/ui/tooltip.tsx +30 -0
  205. package/components/welcome-guide.tsx +182 -0
  206. package/components.json +24 -0
  207. package/lib/command-parser.ts +331 -0
  208. package/lib/dangerous-commands.ts +672 -0
  209. package/lib/db.ts +2250 -0
  210. package/lib/feishu-auth.ts +135 -0
  211. package/lib/file-storage.ts +306 -0
  212. package/lib/file-tool.ts +583 -0
  213. package/lib/knowledge-tool.ts +152 -0
  214. package/lib/knowledge-types.ts +66 -0
  215. package/lib/market-client.ts +313 -0
  216. package/lib/market-db.ts +736 -0
  217. package/lib/market-types.ts +51 -0
  218. package/lib/memory-tool.ts +211 -0
  219. package/lib/memory.ts +197 -0
  220. package/lib/pi-config.ts +436 -0
  221. package/lib/pi-session.ts +799 -0
  222. package/lib/pinyin.ts +13 -0
  223. package/lib/recommendation.ts +227 -0
  224. package/lib/risk-estimator.ts +350 -0
  225. package/lib/scheduled-task-tool.ts +184 -0
  226. package/lib/scheduler-init.ts +43 -0
  227. package/lib/scheduler.ts +416 -0
  228. package/lib/secure-bash-tool.ts +413 -0
  229. package/lib/skill-engine.ts +396 -0
  230. package/lib/skill-generator.ts +269 -0
  231. package/lib/skill-loader.ts +234 -0
  232. package/lib/skill-tool.ts +188 -0
  233. package/lib/skill-types.ts +82 -0
  234. package/lib/skills-init.ts +58 -0
  235. package/lib/ticket-tool.ts +246 -0
  236. package/lib/user-skill-types.ts +30 -0
  237. package/lib/user-skills.ts +362 -0
  238. package/lib/utils.ts +6 -0
  239. package/lib/workflow.ts +154 -0
  240. package/lib/zip-tool.ts +191 -0
  241. package/next.config.js +8 -0
  242. package/package.json +106 -0
  243. package/public/.gitkeep +1 -0
  244. package/public/icon.svg +1 -0
  245. package/tsconfig.json +42 -0
@@ -0,0 +1,219 @@
1
+ /**
2
+ * POST /api/marketplace/skills/:id/promote-to-system - 管理员提升技能为系统技能
3
+ */
4
+
5
+ import { NextRequest, NextResponse } from 'next/server';
6
+ import { getMarketSkillById } from '@/lib/market-db';
7
+ import { readSkillJsonFromZip, readSkillMdFromZip } from '@/lib/zip-tool';
8
+ import { getUserById, getDb, createAuditLog } from '@/lib/db';
9
+ import { mkdirSync, existsSync, writeFileSync, rmSync } from 'fs';
10
+ import { join } from 'path';
11
+
12
+ // 系统技能目录
13
+ const SYSTEM_SKILLS_BASE = join(process.cwd(), '.pi', 'skills');
14
+
15
+ type RouteContext = {
16
+ params: Promise<{ id: string }>;
17
+ };
18
+
19
+ /**
20
+ * 检查用户是否是管理员
21
+ */
22
+ function isAdmin(userId: string): boolean {
23
+ const user = getUserById(userId);
24
+ return user?.role === 'admin';
25
+ }
26
+
27
+ /**
28
+ * POST /api/marketplace/skills/:id/promote-to-system
29
+ *
30
+ * 查询参数:
31
+ * - userId: 管理员用户ID
32
+ *
33
+ * 流程:
34
+ * 1. 验证管理员权限
35
+ * 2. 验证技能存在且状态为 approved
36
+ * 3. 从ZIP解压到系统技能目录 .pi/skills/{skillName}/
37
+ * 4. 创建 SKILL.json 和 SKILL.md
38
+ * 5. 返回成功
39
+ */
40
+ export async function POST(
41
+ request: NextRequest,
42
+ context: RouteContext
43
+ ) {
44
+ try {
45
+ const { id: skillId } = await context.params;
46
+ const { searchParams } = new URL(request.url);
47
+ const userId = searchParams.get('userId');
48
+
49
+ // 验证必填字段
50
+ if (!userId) {
51
+ return NextResponse.json(
52
+ { error: '缺少必要参数: userId' },
53
+ { status: 400 },
54
+ );
55
+ }
56
+
57
+ // 验证管理员权限
58
+ if (!isAdmin(userId)) {
59
+ return NextResponse.json(
60
+ { error: '需要管理员权限' },
61
+ { status: 403 },
62
+ );
63
+ }
64
+
65
+ // 验证管理员用户存在
66
+ const adminUser = getUserById(userId);
67
+ if (!adminUser) {
68
+ return NextResponse.json(
69
+ { error: '管理员用户不存在' },
70
+ { status: 404 },
71
+ );
72
+ }
73
+
74
+ // 获取技能信息
75
+ const skill = getMarketSkillById(skillId);
76
+ if (!skill) {
77
+ return NextResponse.json(
78
+ { error: '技能不存在' },
79
+ { status: 404 },
80
+ );
81
+ }
82
+
83
+ // 只能提升已审核通过的技能
84
+ if (skill.status !== 'approved') {
85
+ return NextResponse.json(
86
+ { error: `技能状态为 ${skill.status},只有已审核通过的技能才能提升为系统技能` },
87
+ { status: 400 },
88
+ );
89
+ }
90
+
91
+ // 获取ZIP文件路径
92
+ const db = getDb();
93
+ const row = db
94
+ .prepare('SELECT file_path FROM skill_market WHERE id = ?')
95
+ .get(skillId) as any;
96
+ const zipPath = row?.file_path;
97
+
98
+ if (!zipPath || !existsSync(zipPath)) {
99
+ return NextResponse.json(
100
+ { error: '技能文件不存在' },
101
+ { status: 404 },
102
+ );
103
+ }
104
+
105
+ // 确保系统技能目录存在
106
+ if (!existsSync(SYSTEM_SKILLS_BASE)) {
107
+ mkdirSync(SYSTEM_SKILLS_BASE, { recursive: true });
108
+ }
109
+
110
+ // 目标系统技能目录
111
+ const systemSkillDir = join(SYSTEM_SKILLS_BASE, skill.name);
112
+
113
+ // 检查是否已存在同名系统技能
114
+ if (existsSync(systemSkillDir)) {
115
+ return NextResponse.json(
116
+ { error: `系统技能目录下已存在同名技能 "${skill.name}",请先删除现有技能` },
117
+ { status: 409 },
118
+ );
119
+ }
120
+
121
+ // 创建系统技能目录
122
+ mkdirSync(systemSkillDir, { recursive: true });
123
+
124
+ // 从ZIP读取并写入SKILL.json和SKILL.md
125
+ try {
126
+ const skillJson = readSkillJsonFromZip(zipPath);
127
+ const skillMd = readSkillMdFromZip(zipPath);
128
+
129
+ // 验证SKILL.json包含必填字段
130
+ if (!skillJson.name || !skillJson.displayName) {
131
+ // 清理已创建的目录
132
+ try {
133
+ rmSync(systemSkillDir, { recursive: true, force: true });
134
+ } catch (cleanupError) {
135
+ console.error('[promote-to-system] Failed to cleanup directory after validation error:', cleanupError);
136
+ }
137
+ return NextResponse.json(
138
+ { error: 'SKILL.json 缺少必填字段 name 或 displayName' },
139
+ { status: 400 },
140
+ );
141
+ }
142
+
143
+ // 写入SKILL.json
144
+ try {
145
+ writeFileSync(
146
+ join(systemSkillDir, 'SKILL.json'),
147
+ JSON.stringify(skillJson, null, 2),
148
+ 'utf-8'
149
+ );
150
+ } catch (writeError) {
151
+ // 写入失败,清理已创建的目录
152
+ try {
153
+ rmSync(systemSkillDir, { recursive: true, force: true });
154
+ } catch (cleanupError) {
155
+ console.error('[promote-to-system] Failed to cleanup directory after write error:', cleanupError);
156
+ }
157
+ throw writeError;
158
+ }
159
+
160
+ // 写入SKILL.md
161
+ try {
162
+ writeFileSync(
163
+ join(systemSkillDir, 'SKILL.md'),
164
+ skillMd,
165
+ 'utf-8'
166
+ );
167
+ } catch (writeError) {
168
+ // 写入失败,清理已创建的目录和SKILL.json
169
+ try {
170
+ rmSync(systemSkillDir, { recursive: true, force: true });
171
+ } catch (cleanupError) {
172
+ console.error('[promote-to-system] Failed to cleanup directory after write error:', cleanupError);
173
+ }
174
+ throw writeError;
175
+ }
176
+
177
+ console.log(`[promote-to-system] Promoted skill "${skill.name}" to system skills`);
178
+ } catch (error) {
179
+ console.error('[promote-to-system] Failed to extract skill files:', error);
180
+ return NextResponse.json(
181
+ { error: '提取技能文件失败' },
182
+ { status: 500 },
183
+ );
184
+ }
185
+
186
+ // 创建审计日志
187
+ try {
188
+ createAuditLog({
189
+ action: 'promote_skill_to_system',
190
+ userId: adminUser.id,
191
+ details: {
192
+ skillId: skill.id,
193
+ skillName: skill.name,
194
+ skillDisplayName: skill.displayName,
195
+ marketplaceSkillId: skillId,
196
+ },
197
+ status: 'success',
198
+ });
199
+ } catch (auditError) {
200
+ // 审计日志失败不影响主流程
201
+ console.error('[promote-to-system] Failed to create audit log:', auditError);
202
+ }
203
+
204
+ return NextResponse.json(
205
+ {
206
+ success: true,
207
+ systemSkillName: skill.name,
208
+ message: `技能 "${skill.displayName || skill.name}" 已提升为系统技能`,
209
+ },
210
+ { status: 200 },
211
+ );
212
+ } catch (error) {
213
+ console.error('[marketplace/skills/:id/promote-to-system] POST failed:', error);
214
+ return NextResponse.json(
215
+ { error: error instanceof Error ? error.message : '提升技能失败' },
216
+ { status: 500 },
217
+ );
218
+ }
219
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * POST /api/marketplace/skills/:id/rate - 用户评分
3
+ *
4
+ * 请求体:
5
+ * - userId: 用户ID (必填)
6
+ * - rating: 评分 1-5 (必填)
7
+ * - comment: 评论文本 (可选)
8
+ */
9
+
10
+ import { NextRequest, NextResponse } from 'next/server';
11
+ import { addRating, getMarketSkillById } from '@/lib/market-db';
12
+ import { getUserById } from '@/lib/db';
13
+
14
+ type RouteContext = {
15
+ params: Promise<{ id: string }>;
16
+ };
17
+
18
+ /**
19
+ * POST /api/marketplace/skills/:id/rate - 用户评分
20
+ */
21
+ export async function POST(request: NextRequest, context: RouteContext) {
22
+ try {
23
+ const { id } = await context.params;
24
+ const body = await request.json();
25
+ const { userId, rating, comment } = body;
26
+
27
+ // 验证必填字段
28
+ if (!userId) {
29
+ return NextResponse.json(
30
+ { error: '缺少用户ID' },
31
+ { status: 400 },
32
+ );
33
+ }
34
+
35
+ if (rating === undefined || rating === null) {
36
+ return NextResponse.json(
37
+ { error: '缺少评分' },
38
+ { status: 400 },
39
+ );
40
+ }
41
+
42
+ // 验证评分范围
43
+ const ratingNum = Number(rating);
44
+ if (!Number.isInteger(ratingNum) || ratingNum < 1 || ratingNum > 5) {
45
+ return NextResponse.json(
46
+ { error: '评分必须是1-5之间的整数' },
47
+ { status: 400 },
48
+ );
49
+ }
50
+
51
+ // 验证技能是否存在
52
+ const skill = getMarketSkillById(id);
53
+ if (!skill) {
54
+ return NextResponse.json(
55
+ { error: '技能不存在' },
56
+ { status: 404 },
57
+ );
58
+ }
59
+
60
+ // 验证用户存在
61
+ const user = getUserById(userId);
62
+ if (!user) {
63
+ return NextResponse.json(
64
+ { error: '用户不存在' },
65
+ { status: 404 },
66
+ );
67
+ }
68
+
69
+ // 添加/更新评分(数据库层处理UPSERT)
70
+ const skillRating = addRating(id, {
71
+ userId,
72
+ rating: ratingNum,
73
+ comment,
74
+ });
75
+
76
+ // 获取更新后的技能信息
77
+ const updatedSkill = getMarketSkillById(id);
78
+
79
+ return NextResponse.json({
80
+ rating: skillRating,
81
+ skill: updatedSkill,
82
+ });
83
+ } catch (error) {
84
+ console.error('[marketplace/skills/:id/rate] POST failed:', error);
85
+ return NextResponse.json(
86
+ { error: error instanceof Error ? error.message : '评分失败' },
87
+ { status: 500 },
88
+ );
89
+ }
90
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * GET /api/marketplace/skills/:id/ratings - 获取技能评分列表
3
+ *
4
+ * 查询参数:
5
+ * - userId: 可选,获取特定用户的评分
6
+ */
7
+
8
+ import { NextRequest, NextResponse } from 'next/server';
9
+ import { getSkillRatings, getUserRating, getMarketSkillById } from '@/lib/market-db';
10
+
11
+ type RouteContext = {
12
+ params: Promise<{ id: string }>;
13
+ };
14
+
15
+ /**
16
+ * GET /api/marketplace/skills/:id/ratings - 获取技能评分列表
17
+ */
18
+ export async function GET(request: NextRequest, context: RouteContext) {
19
+ try {
20
+ const { id } = await context.params;
21
+ const { searchParams } = new URL(request.url);
22
+ const userId = searchParams.get('userId');
23
+
24
+ // 验证技能是否存在
25
+ const skill = getMarketSkillById(id);
26
+ if (!skill) {
27
+ return NextResponse.json(
28
+ { error: '技能不存在' },
29
+ { status: 404 },
30
+ );
31
+ }
32
+
33
+ // 如果指定了用户ID,返回该用户的评分
34
+ if (userId) {
35
+ const userRating = getUserRating(id, userId);
36
+ return NextResponse.json({
37
+ ratings: userRating ? [userRating] : [],
38
+ total: userRating ? 1 : 0,
39
+ });
40
+ }
41
+
42
+ // 返回所有评分
43
+ const ratings = getSkillRatings(id);
44
+ return NextResponse.json({
45
+ ratings,
46
+ total: ratings.length,
47
+ });
48
+ } catch (error) {
49
+ console.error('[marketplace/skills/:id/ratings] GET failed:', error);
50
+ return NextResponse.json(
51
+ { error: error instanceof Error ? error.message : '获取评分列表失败' },
52
+ { status: 500 },
53
+ );
54
+ }
55
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * PUT /api/marketplace/skills/:id/reject - 管理员拒绝技能
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: false,
57
+ comment,
58
+ });
59
+
60
+ return NextResponse.json({ skill });
61
+ } catch (error) {
62
+ console.error('[marketplace/skills/:id/reject] PUT failed:', error);
63
+ return NextResponse.json(
64
+ { error: error instanceof Error ? error.message : '拒绝失败' },
65
+ { status: 500 },
66
+ );
67
+ }
68
+ }
@@ -0,0 +1,177 @@
1
+ /**
2
+ * 技能市场详情 API - 获取技能详情和删除技能
3
+ *
4
+ * GET /api/marketplace/skills/:id - 获取技能详情
5
+ * DELETE /api/marketplace/skills/:id - 删除技能
6
+ *
7
+ * 其他操作:
8
+ * - PUT /api/marketplace/skills/:id/approve - 管理员审核通过
9
+ * - PUT /api/marketplace/skills/:id/reject - 管理员拒绝
10
+ * - PUT /api/marketplace/skills/:id/certify - 管理员设为官方认证
11
+ */
12
+
13
+ import { NextRequest, NextResponse } from 'next/server';
14
+ import {
15
+ getMarketSkillDetailById,
16
+ deleteMarketSkill,
17
+ } from '@/lib/market-db';
18
+ import { getUserById } from '@/lib/db';
19
+ import { getMarketClient } from '@/lib/market-client';
20
+
21
+ type RouteContext = {
22
+ params: Promise<{ id: string }>;
23
+ };
24
+
25
+ /**
26
+ * GET /api/marketplace/skills/:id - 获取技能详情
27
+ */
28
+ export async function GET(request: NextRequest, context: RouteContext) {
29
+ try {
30
+ const { id } = await context.params;
31
+
32
+ const client = getMarketClient();
33
+
34
+ // 如果启用了远程服务,优先从远程获取
35
+ if (client.isRemoteEnabled()) {
36
+ try {
37
+ const skill = await client.getSkill(id);
38
+ if (skill) {
39
+ return NextResponse.json({ skill, source: 'remote' });
40
+ }
41
+ } catch (error: any) {
42
+ console.error('[marketplace/skills/:id] Remote failed:', error.message);
43
+ return NextResponse.json(
44
+ { error: error.message || '远程市场服务不可用', code: 'MARKET_UNAVAILABLE' },
45
+ { status: 503 }
46
+ );
47
+ }
48
+ }
49
+
50
+ // 本地模式:直接调用本地 SQLite
51
+ const skill = getMarketSkillDetailById(id);
52
+ if (!skill) {
53
+ return NextResponse.json(
54
+ { error: '技能不存在' },
55
+ { status: 404 },
56
+ );
57
+ }
58
+
59
+ return NextResponse.json({ skill, source: 'local' });
60
+ } catch (error) {
61
+ console.error('[marketplace/skills/:id] GET failed:', error);
62
+ return NextResponse.json(
63
+ { error: error instanceof Error ? error.message : '获取技能详情失败' },
64
+ { status: 500 },
65
+ );
66
+ }
67
+ }
68
+
69
+ /**
70
+ * DELETE /api/marketplace/skills/:id - 删除技能
71
+ *
72
+ * 查询参数:
73
+ * - userId: 删除操作人ID
74
+ * - role: 用户角色,admin 可以删除任何技能
75
+ *
76
+ * 注意:
77
+ * - 远程模式:直接删除远程技能(即使本地不存在也可以删除)
78
+ * - 本地模式:需要技能在本地存在
79
+ */
80
+ export async function DELETE(request: NextRequest, context: RouteContext) {
81
+ try {
82
+ const { id } = await context.params;
83
+ const { searchParams } = new URL(request.url);
84
+ const userId = searchParams.get('userId');
85
+ const role = searchParams.get('role');
86
+
87
+ // 验证必填字段
88
+ if (!userId) {
89
+ return NextResponse.json(
90
+ { error: '缺少用户ID' },
91
+ { status: 400 },
92
+ );
93
+ }
94
+
95
+ // 权限检查:需要管理员权限
96
+ if (role !== 'admin') {
97
+ return NextResponse.json(
98
+ { error: '需要管理员权限' },
99
+ { status: 403 },
100
+ );
101
+ }
102
+
103
+ const client = getMarketClient();
104
+
105
+ if (client.isRemoteMode()) {
106
+ // 远程模式:直接调用远程市场 API 删除(即使本地不存在也可以删除)
107
+ try {
108
+ const response = await fetch(
109
+ `${client['baseUrl']}/api/market/skills/${id}?api_key=${client['apiKey']}&userId=${userId}&role=${role}`,
110
+ { method: 'DELETE' }
111
+ );
112
+
113
+ if (!response.ok) {
114
+ const errorText = await response.text();
115
+ console.error('[marketplace/skills/:id] Remote delete failed:', errorText);
116
+
117
+ // 如果远程返回404,说明已经不存在了,也算成功
118
+ if (response.status === 404) {
119
+ console.log('[marketplace/skills/:id] Already removed from remote market:', id);
120
+ } else {
121
+ throw new Error(`Remote market error: ${response.status}`);
122
+ }
123
+ } else {
124
+ console.log('[marketplace/skills/:id] Removed from remote market:', id);
125
+ }
126
+
127
+ // 如果本地存在,也删除本地记录
128
+ const localSkill = getMarketSkillDetailById(id);
129
+ if (localSkill) {
130
+ deleteMarketSkill(id, userId, role);
131
+ console.log('[marketplace/skills/:id] Also removed from local:', id);
132
+ }
133
+
134
+ return NextResponse.json({
135
+ success: true,
136
+ message: '已从远程市场删除'
137
+ });
138
+ } catch (error: any) {
139
+ console.error('[marketplace/skills/:id] Failed to remove from remote:', error.message);
140
+ return NextResponse.json(
141
+ { error: '从远程市场删除失败,请稍后重试' },
142
+ { status: 500 }
143
+ );
144
+ }
145
+ } else {
146
+ // 本地模式:验证用户存在
147
+ const user = getUserById(userId);
148
+ if (!user) {
149
+ return NextResponse.json(
150
+ { error: '用户不存在' },
151
+ { status: 404 },
152
+ );
153
+ }
154
+
155
+ // 删除技能(会验证所有权,管理员可删除任何技能)
156
+ const deleted = deleteMarketSkill(id, userId, role);
157
+
158
+ if (!deleted) {
159
+ return NextResponse.json(
160
+ { error: '技能不存在或无权删除' },
161
+ { status: 404 },
162
+ );
163
+ }
164
+
165
+ return NextResponse.json({
166
+ success: true,
167
+ message: '已从本地市场删除'
168
+ });
169
+ }
170
+ } catch (error) {
171
+ console.error('[marketplace/skills/:id] DELETE failed:', error);
172
+ return NextResponse.json(
173
+ { error: error instanceof Error ? error.message : '删除技能失败' },
174
+ { status: 500 },
175
+ );
176
+ }
177
+ }