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,235 @@
1
+ /**
2
+ * 技能市场 API - 获取列表和上传技能
3
+ *
4
+ * GET /api/marketplace/skills - 列出市场上的技能(支持筛选)
5
+ * POST /api/marketplace/skills/upload - 用户上传技能到市场
6
+ */
7
+
8
+ import { NextRequest, NextResponse } from 'next/server';
9
+ import {
10
+ listMarketSkills,
11
+ uploadSkillToMarket,
12
+ } from '@/lib/market-db';
13
+ import {
14
+ getUserById,
15
+ getDb,
16
+ } from '@/lib/db';
17
+ import {
18
+ packSkillToZip,
19
+ validateSkillZip,
20
+ } from '@/lib/zip-tool';
21
+ import {
22
+ getUserSkill,
23
+ } from '@/lib/user-skills';
24
+ import { getMarketClient } from '@/lib/market-client';
25
+ import { mkdirSync, existsSync } from 'fs';
26
+ import { join } from 'path';
27
+
28
+ // 技能市场ZIP文件存储目录
29
+ const MARKET_ZIP_DIR = join(process.cwd(), 'data', 'market', 'zips');
30
+
31
+ /**
32
+ * 确保市场ZIP目录存在
33
+ */
34
+ function ensureMarketZipDir(): string {
35
+ if (!existsSync(MARKET_ZIP_DIR)) {
36
+ mkdirSync(MARKET_ZIP_DIR, { recursive: true });
37
+ }
38
+ return MARKET_ZIP_DIR;
39
+ }
40
+
41
+ /**
42
+ * GET /api/marketplace/skills - 列出市场上的技能(支持筛选)
43
+ *
44
+ * 查询参数:
45
+ * - status: 技能状态 (pending, approved, rejected, archived, all)
46
+ * - author: 作者ID过滤
47
+ * - isOfficial: 是否官方认证 (true/false)
48
+ * - search: 搜索关键词(在display_name和description中搜索)
49
+ * - limit: 返回数量限制
50
+ * - offset: 偏移量(用于分页)
51
+ * - orderBy: 排序方式 (rating, downloads, created, updated)
52
+ */
53
+ export async function GET(request: NextRequest) {
54
+ try {
55
+ const { searchParams } = new URL(request.url);
56
+
57
+ // 解析查询参数
58
+ const status = searchParams.get('status') as 'pending' | 'approved' | 'rejected' | 'archived' | 'all' | null;
59
+ const author = searchParams.get('author') || undefined;
60
+ const isOfficialParam = searchParams.get('isOfficial');
61
+ const isOfficial = isOfficialParam === 'true' ? true : isOfficialParam === 'false' ? false : undefined;
62
+ const search = searchParams.get('search') || undefined;
63
+ const limit = searchParams.get('limit') ? parseInt(searchParams.get('limit')!) : undefined;
64
+ const offset = searchParams.get('offset') ? parseInt(searchParams.get('offset')!) : undefined;
65
+ const orderBy = searchParams.get('orderBy') as 'rating' | 'downloads' | 'created' | 'updated' | null;
66
+
67
+ const client = getMarketClient();
68
+
69
+ // 如果启用了远程服务,优先从远程获取
70
+ if (client.isRemoteEnabled()) {
71
+ try {
72
+ const result = await client.searchSkills({
73
+ status: status || undefined,
74
+ author,
75
+ isOfficial,
76
+ search,
77
+ limit,
78
+ offset,
79
+ });
80
+
81
+ return NextResponse.json({
82
+ skills: result.skills,
83
+ total: result.total,
84
+ limit,
85
+ offset,
86
+ source: 'remote',
87
+ });
88
+ } catch (error) {
89
+ console.warn('[marketplace/skills] Remote failed, falling back to local:', error);
90
+ }
91
+ }
92
+
93
+ // 降级逻辑:直接调用本地 SQLite
94
+ const result = listMarketSkills({
95
+ status: status || undefined,
96
+ author,
97
+ isOfficial,
98
+ search,
99
+ limit,
100
+ offset,
101
+ orderBy: orderBy || undefined,
102
+ });
103
+
104
+ return NextResponse.json({
105
+ skills: result.skills,
106
+ total: result.total,
107
+ limit,
108
+ offset,
109
+ source: 'local',
110
+ });
111
+ } catch (error) {
112
+ console.error('[marketplace/skills] GET failed:', error);
113
+ return NextResponse.json(
114
+ { error: error instanceof Error ? error.message : '获取技能列表失败' },
115
+ { status: 500 },
116
+ );
117
+ }
118
+ }
119
+
120
+ /**
121
+ * POST /api/marketplace/skills/upload - 用户上传技能到市场
122
+ *
123
+ * 请求体:
124
+ * - userId: 用户ID
125
+ * - userName: 用户名(可选)
126
+ * - skillName: 用户私有技能的名称
127
+ *
128
+ * 流程:
129
+ * 1. 验证用户身份
130
+ * 2. 获取用户的私有技能
131
+ * 3. 将技能打包为ZIP
132
+ * 4. 上传到市场(创建pending状态的记录)
133
+ */
134
+ export async function POST(request: NextRequest) {
135
+ try {
136
+ const body = await request.json();
137
+ const { userId, userName, skillName } = body;
138
+
139
+ // 验证必填字段
140
+ if (!userId || !skillName) {
141
+ return NextResponse.json(
142
+ { error: '缺少必要参数: userId, skillName' },
143
+ { status: 400 },
144
+ );
145
+ }
146
+
147
+ // 验证用户存在
148
+ const user = getUserById(userId);
149
+ if (!user) {
150
+ return NextResponse.json(
151
+ { error: '用户不存在' },
152
+ { status: 404 },
153
+ );
154
+ }
155
+
156
+ // 获取用户技能
157
+ const userSkill = getUserSkill(userId, skillName);
158
+ if (!userSkill) {
159
+ return NextResponse.json(
160
+ { error: '用户技能不存在' },
161
+ { status: 404 },
162
+ );
163
+ }
164
+
165
+ // 检查技能所有权
166
+ if (userSkill.author !== userId) {
167
+ return NextResponse.json(
168
+ { error: '无权上传此技能' },
169
+ { status: 403 },
170
+ );
171
+ }
172
+
173
+ // 获取用户技能目录(从数据库查询 dir_name)
174
+ const db = getDb();
175
+ const row = db.prepare(
176
+ 'SELECT dir_name FROM user_skills WHERE user_id = ? AND skill_name = ?'
177
+ ).get(userId, skillName) as { dir_name: string } | undefined;
178
+ const dirName = row?.dir_name || skillName;
179
+ const userSkillDir = join(process.cwd(), 'data', 'skills', 'users', userId, dirName);
180
+
181
+ // 确保市场ZIP目录存在
182
+ const zipDir = ensureMarketZipDir();
183
+ const zipFileName = `${skillName}-${Date.now()}.zip`;
184
+ const zipPath = join(zipDir, zipFileName);
185
+
186
+ // 打包技能为ZIP
187
+ try {
188
+ packSkillToZip(userSkillDir, zipPath);
189
+ } catch (error) {
190
+ console.error('[marketplace/skills] Failed to pack skill:', error);
191
+ return NextResponse.json(
192
+ { error: '打包技能失败' },
193
+ { status: 500 },
194
+ );
195
+ }
196
+
197
+ // 验证ZIP包
198
+ const validation = validateSkillZip(zipPath);
199
+ if (!validation.valid) {
200
+ return NextResponse.json(
201
+ { error: `技能包验证失败: ${validation.error}` },
202
+ { status: 400 },
203
+ );
204
+ }
205
+
206
+ // 上传到市场
207
+ const marketSkill = await uploadSkillToMarket(
208
+ {
209
+ userId,
210
+ userName: userName || user.username,
211
+ skillName,
212
+ },
213
+ zipPath,
214
+ );
215
+
216
+ // 判断是新建还是更新(通过比较创建时间和当前时间,如果创建时间较早则是更新)
217
+ const createdAt = new Date(marketSkill.createdAt).getTime();
218
+ const now = Date.now();
219
+ const isUpdate = (now - createdAt) > 5000; // 超过5秒认为是更新
220
+
221
+ return NextResponse.json(
222
+ {
223
+ skill: marketSkill,
224
+ updated: isUpdate,
225
+ },
226
+ { status: isUpdate ? 200 : 201 },
227
+ );
228
+ } catch (error) {
229
+ console.error('[marketplace/skills] POST failed:', error);
230
+ return NextResponse.json(
231
+ { error: error instanceof Error ? error.message : '上传技能失败' },
232
+ { status: 500 },
233
+ );
234
+ }
235
+ }
@@ -0,0 +1,40 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { loadUserMemories, updateMemories } from '@/lib/memory';
3
+
4
+ export async function GET(request: NextRequest) {
5
+ const userId = request.nextUrl.searchParams.get('userId');
6
+
7
+ if (!userId) {
8
+ return NextResponse.json({ error: 'userId is required' }, { status: 400 });
9
+ }
10
+
11
+ try {
12
+ const content = loadUserMemories(userId);
13
+ return NextResponse.json({ content });
14
+ } catch (error: any) {
15
+ console.error('[memory] GET error:', error);
16
+ return NextResponse.json({ error: error.message }, { status: 500 });
17
+ }
18
+ }
19
+
20
+ export async function POST(request: NextRequest) {
21
+ try {
22
+ const body = await request.json();
23
+ const { userId, content } = body;
24
+
25
+ if (!userId) {
26
+ return NextResponse.json({ error: 'userId is required' }, { status: 400 });
27
+ }
28
+
29
+ if (content === undefined) {
30
+ return NextResponse.json({ error: 'content is required' }, { status: 400 });
31
+ }
32
+
33
+ updateMemories(userId, content);
34
+
35
+ return NextResponse.json({ success: true });
36
+ } catch (error: any) {
37
+ console.error('[memory] POST error:', error);
38
+ return NextResponse.json({ error: error.message }, { status: 500 });
39
+ }
40
+ }
@@ -0,0 +1,52 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { getDocument } from '@/lib/db';
3
+ import { getStorageProvider } from '@/lib/file-storage';
4
+
5
+ export async function GET(
6
+ request: NextRequest,
7
+ { params }: { params: Promise<{ id: string }> }
8
+ ) {
9
+ try {
10
+ const { id } = await params;
11
+ const { searchParams } = new URL(request.url);
12
+ const userId = searchParams.get('userId');
13
+ const download = searchParams.get('download') === 'true';
14
+
15
+ const document = getDocument(id);
16
+ if (!document) {
17
+ return NextResponse.json({ error: 'File not found' }, { status: 404 });
18
+ }
19
+
20
+ if (!document.isPublic && document.author !== userId) {
21
+ return NextResponse.json({ error: 'Permission denied' }, { status: 403 });
22
+ }
23
+
24
+ if (!document.fileMetadata?.storageKey) {
25
+ return NextResponse.json({ error: 'File storage key not found' }, { status: 404 });
26
+ }
27
+
28
+ const storage = getStorageProvider();
29
+ const buffer = await storage.download(document.fileMetadata.storageKey);
30
+
31
+ const filename = document.fileMetadata.originalName || document.title;
32
+ const mimeType = document.fileMetadata.mimeType || 'application/octet-stream';
33
+
34
+ const headers: Record<string, string> = {
35
+ 'Content-Type': mimeType,
36
+ 'Content-Length': buffer.length.toString(),
37
+ };
38
+
39
+ if (download) {
40
+ headers['Content-Disposition'] = `attachment; filename*=UTF-8''${encodeURIComponent(filename)}`;
41
+ } else {
42
+ headers['Content-Disposition'] = `inline; filename*=UTF-8''${encodeURIComponent(filename)}`;
43
+ }
44
+
45
+ return new NextResponse(new Uint8Array(buffer), { headers });
46
+ } catch (error) {
47
+ console.error('[api/my/files/[id]] Download error:', error);
48
+ return NextResponse.json({
49
+ error: error instanceof Error ? error.message : 'Download failed'
50
+ }, { status: 500 });
51
+ }
52
+ }
@@ -0,0 +1,230 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { getDb, createDocument, getDocuments, deleteDocument, updateDocument, getDocument } from '@/lib/db';
3
+ import { getStorageProvider, MAX_FILE_SIZE, isAllowedMimeType, formatFileSize } from '@/lib/file-storage';
4
+ import { createAuditLog } from '@/lib/db';
5
+ import type { FileMetadata } from '@/lib/db';
6
+
7
+ export async function GET(request: NextRequest) {
8
+ try {
9
+ const { searchParams } = new URL(request.url);
10
+ const userId = searchParams.get('userId');
11
+ const search = searchParams.get('search') || undefined;
12
+ const limit = parseInt(searchParams.get('limit') || '50');
13
+ const offset = parseInt(searchParams.get('offset') || '0');
14
+
15
+ if (!userId) {
16
+ return NextResponse.json({ error: 'userId is required' }, { status: 400 });
17
+ }
18
+
19
+ const db = getDb();
20
+ let whereClause = 'WHERE author = ? AND type = ?';
21
+ const params: any[] = [userId, 'file'];
22
+
23
+ if (search) {
24
+ whereClause += ' AND (title LIKE ? OR content LIKE ?)';
25
+ const searchTerm = `%${search}%`;
26
+ params.push(searchTerm, searchTerm);
27
+ }
28
+
29
+ const countStmt = db.prepare(`SELECT COUNT(*) as count FROM documents ${whereClause}`);
30
+ const countResult = countStmt.get(...params) as { count: number };
31
+ const total = countResult.count;
32
+
33
+ let query = `SELECT * FROM documents ${whereClause} ORDER BY created_at DESC LIMIT ? OFFSET ?`;
34
+ params.push(limit, offset);
35
+
36
+ const stmt = db.prepare(query);
37
+ const rows = stmt.all(...params) as any[];
38
+
39
+ const files = rows.map(row => ({
40
+ id: row.id,
41
+ title: row.title,
42
+ author: row.author,
43
+ createdAt: row.created_at,
44
+ updatedAt: row.updated_at,
45
+ fileMetadata: row.file_metadata ? JSON.parse(row.file_metadata) : null,
46
+ isPublic: row.is_public === 1,
47
+ content: row.content,
48
+ }));
49
+
50
+ const totalSize = rows.reduce((acc, row) => {
51
+ const metadata = row.file_metadata ? JSON.parse(row.file_metadata) : null;
52
+ return acc + (metadata?.size || 0);
53
+ }, 0);
54
+
55
+ return NextResponse.json({
56
+ files,
57
+ total,
58
+ totalSize,
59
+ totalSizeFormatted: formatFileSize(totalSize),
60
+ });
61
+ } catch (error) {
62
+ console.error('[api/my/files] Error:', error);
63
+ return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
64
+ }
65
+ }
66
+
67
+ export async function POST(request: NextRequest) {
68
+ try {
69
+ const formData = await request.formData();
70
+ const userId = formData.get('userId') as string;
71
+ const file = formData.get('file') as File | null;
72
+ const description = formData.get('description') as string || '';
73
+ const isPublic = formData.get('isPublic') === 'true';
74
+
75
+ if (!userId) {
76
+ return NextResponse.json({ error: 'userId is required' }, { status: 400 });
77
+ }
78
+
79
+ if (!file) {
80
+ return NextResponse.json({ error: 'No file provided' }, { status: 400 });
81
+ }
82
+
83
+ if (file.size > MAX_FILE_SIZE) {
84
+ return NextResponse.json({
85
+ error: `File size exceeds limit of ${formatFileSize(MAX_FILE_SIZE)}`
86
+ }, { status: 400 });
87
+ }
88
+
89
+ if (!isAllowedMimeType(file.type)) {
90
+ return NextResponse.json({
91
+ error: `File type "${file.type}" is not allowed`
92
+ }, { status: 400 });
93
+ }
94
+
95
+ const buffer = Buffer.from(await file.arrayBuffer());
96
+ const storage = getStorageProvider();
97
+
98
+ const { storageKey, md5 } = await storage.upload({
99
+ userId,
100
+ filename: file.name,
101
+ mimeType: file.type,
102
+ buffer,
103
+ });
104
+
105
+ const fileMetadata: FileMetadata = {
106
+ size: file.size,
107
+ mimeType: file.type,
108
+ md5,
109
+ storageKey,
110
+ storageType: 'local',
111
+ originalName: file.name,
112
+ };
113
+
114
+ const document = createDocument({
115
+ title: file.name,
116
+ content: description,
117
+ author: userId,
118
+ type: 'file',
119
+ fileMetadata,
120
+ isPublic,
121
+ isActive: true,
122
+ });
123
+
124
+ createAuditLog({
125
+ action: 'file_upload',
126
+ userId,
127
+ details: { fileId: document.id, filename: file.name, size: file.size },
128
+ status: 'success',
129
+ });
130
+
131
+ return NextResponse.json({
132
+ success: true,
133
+ file: {
134
+ id: document.id,
135
+ title: document.title,
136
+ fileMetadata: document.fileMetadata,
137
+ isPublic: document.isPublic,
138
+ createdAt: document.createdAt,
139
+ },
140
+ });
141
+ } catch (error) {
142
+ console.error('[api/my/files] Upload error:', error);
143
+ return NextResponse.json({
144
+ error: error instanceof Error ? error.message : 'Upload failed'
145
+ }, { status: 500 });
146
+ }
147
+ }
148
+
149
+ export async function DELETE(request: NextRequest) {
150
+ try {
151
+ const { searchParams } = new URL(request.url);
152
+ const userId = searchParams.get('userId');
153
+ const fileId = searchParams.get('fileId');
154
+
155
+ if (!userId || !fileId) {
156
+ return NextResponse.json({ error: 'userId and fileId are required' }, { status: 400 });
157
+ }
158
+
159
+ const document = getDocument(fileId);
160
+ if (!document) {
161
+ return NextResponse.json({ error: 'File not found' }, { status: 404 });
162
+ }
163
+
164
+ if (document.author !== userId) {
165
+ return NextResponse.json({ error: 'Permission denied' }, { status: 403 });
166
+ }
167
+
168
+ if (document.fileMetadata?.storageKey) {
169
+ const storage = getStorageProvider();
170
+ await storage.delete(document.fileMetadata.storageKey);
171
+ }
172
+
173
+ deleteDocument(fileId);
174
+
175
+ createAuditLog({
176
+ action: 'file_delete',
177
+ userId,
178
+ details: { fileId, filename: document.title },
179
+ status: 'success',
180
+ });
181
+
182
+ return NextResponse.json({ success: true });
183
+ } catch (error) {
184
+ console.error('[api/my/files] Delete error:', error);
185
+ return NextResponse.json({
186
+ error: error instanceof Error ? error.message : 'Delete failed'
187
+ }, { status: 500 });
188
+ }
189
+ }
190
+
191
+ export async function PATCH(request: NextRequest) {
192
+ try {
193
+ const body = await request.json();
194
+ const { userId, fileId, title, description, isPublic } = body;
195
+
196
+ if (!userId || !fileId) {
197
+ return NextResponse.json({ error: 'userId and fileId are required' }, { status: 400 });
198
+ }
199
+
200
+ const document = getDocument(fileId);
201
+ if (!document) {
202
+ return NextResponse.json({ error: 'File not found' }, { status: 404 });
203
+ }
204
+
205
+ if (document.author !== userId) {
206
+ return NextResponse.json({ error: 'Permission denied' }, { status: 403 });
207
+ }
208
+
209
+ const updates: any = {};
210
+ if (title !== undefined) updates.title = title;
211
+ if (description !== undefined) updates.content = description;
212
+ if (isPublic !== undefined) updates.isPublic = isPublic;
213
+
214
+ const updated = updateDocument(fileId, updates);
215
+
216
+ createAuditLog({
217
+ action: 'file_update',
218
+ userId,
219
+ details: { fileId, updates },
220
+ status: 'success',
221
+ });
222
+
223
+ return NextResponse.json({ success: true, file: updated });
224
+ } catch (error) {
225
+ console.error('[api/my/files] Update error:', error);
226
+ return NextResponse.json({
227
+ error: error instanceof Error ? error.message : 'Update failed'
228
+ }, { status: 500 });
229
+ }
230
+ }
@@ -0,0 +1,36 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { getDocuments, getDocument, getUserByUsername } from '@/lib/db';
3
+
4
+ export async function GET(request: NextRequest) {
5
+ try {
6
+ const userId = request.nextUrl.searchParams.get('userId');
7
+ const username = request.nextUrl.searchParams.get('username');
8
+
9
+ if (!userId && !username) {
10
+ return NextResponse.json(
11
+ { error: '缺少userId或username参数' },
12
+ { status: 400 }
13
+ );
14
+ }
15
+
16
+ let targetUserId = userId;
17
+ if (!targetUserId && username) {
18
+ const user = getUserByUsername(username);
19
+ if (!user) {
20
+ return NextResponse.json({ knowledge: [] });
21
+ }
22
+ targetUserId = user.id;
23
+ }
24
+
25
+ const allDocs = getDocuments({}) as any[];
26
+ const userKnowledge = allDocs.filter((d: any) => d.author === targetUserId);
27
+
28
+ return NextResponse.json({ knowledge: userKnowledge });
29
+ } catch (error) {
30
+ console.error('Get my knowledge failed:', error);
31
+ return NextResponse.json(
32
+ { error: '获取知识失败' },
33
+ { status: 500 }
34
+ );
35
+ }
36
+ }