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,248 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useState } from 'react';
4
+ import Link from 'next/link';
5
+ import { useRouter } from 'next/navigation';
6
+ import {
7
+ ArrowLeft,
8
+ Plus,
9
+ Trash2,
10
+ FileText,
11
+ Edit,
12
+ Eye,
13
+ Star,
14
+ Upload
15
+ } from 'lucide-react';
16
+ import { Button } from '@/components/ui/button';
17
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
18
+ import { Badge } from '@/components/ui/badge';
19
+
20
+ interface Knowledge {
21
+ id: string;
22
+ title: string;
23
+ content: string;
24
+ category?: string;
25
+ tags?: string[];
26
+ author: string;
27
+ marketStatus?: string;
28
+ createdAt: string;
29
+ updatedAt: string;
30
+ }
31
+
32
+ export default function MyKnowledgePage() {
33
+ const router = useRouter();
34
+ const [knowledge, setKnowledge] = useState<Knowledge[]>([]);
35
+ const [loading, setLoading] = useState(true);
36
+ const [userId, setUserId] = useState<string>('');
37
+
38
+ useEffect(() => {
39
+ const uid = localStorage.getItem('userId');
40
+ if (!uid) {
41
+ router.push('/login');
42
+ return;
43
+ }
44
+ setUserId(uid);
45
+ fetchKnowledge(uid);
46
+ }, [router]);
47
+
48
+ const fetchKnowledge = async (uid: string) => {
49
+ try {
50
+ const res = await fetch(`/api/my/knowledge?userId=${uid}`);
51
+ if (res.ok) {
52
+ const data = await res.json();
53
+ setKnowledge(data.knowledge || []);
54
+ }
55
+ } catch (error) {
56
+ console.error('Failed to fetch knowledge:', error);
57
+ } finally {
58
+ setLoading(false);
59
+ }
60
+ };
61
+
62
+ const handleDelete = async (id: string) => {
63
+ if (!confirm('确定删除这条知识吗?')) return;
64
+
65
+ try {
66
+ const res = await fetch(`/api/knowledge/${id}`, { method: 'DELETE' });
67
+ if (res.ok) {
68
+ setKnowledge(prev => prev.filter(k => k.id !== id));
69
+ }
70
+ } catch (error) {
71
+ console.error('Failed to delete:', error);
72
+ }
73
+ };
74
+
75
+ const handlePublish = async (id: string) => {
76
+ try {
77
+ const res = await fetch('/api/market/knowledge', {
78
+ method: 'POST',
79
+ headers: { 'Content-Type': 'application/json' },
80
+ body: JSON.stringify({ id, author: userId }),
81
+ });
82
+
83
+ if (!res.ok) {
84
+ const error = await res.json();
85
+ alert(error.error || '发布失败');
86
+ return;
87
+ }
88
+
89
+ // 发布成功,更新本地状态
90
+ setKnowledge(prev => prev.map(k =>
91
+ k.id === id ? { ...k, marketStatus: 'published' } : k
92
+ ));
93
+
94
+ alert('发布成功!');
95
+ } catch (error: any) {
96
+ console.error('Failed to publish:', error);
97
+ alert(error.message || '发布失败');
98
+ }
99
+ };
100
+
101
+ const handleRepublish = async (id: string) => {
102
+ if (!confirm('确定要重新发布此知识吗?这将更新市场中的内容。')) {
103
+ return;
104
+ }
105
+ await handlePublish(id);
106
+ };
107
+
108
+ const myKnowledge = knowledge.filter(k => k.author === userId);
109
+ const publishedKnowledge = knowledge.filter(k => k.marketStatus === 'published');
110
+
111
+ return (
112
+ <div className="p-6">
113
+ <div className="flex items-center justify-between mb-6">
114
+ <div />
115
+ <div className="flex items-center gap-2">
116
+ <Link href="/my/knowledge/new">
117
+ <Button>
118
+ <Plus className="w-4 h-4 mr-2" />
119
+ 创建知识
120
+ </Button>
121
+ </Link>
122
+ </div>
123
+ </div>
124
+ <div>
125
+ {loading ? (
126
+ <div className="text-center py-12 text-gray-500">加载中...</div>
127
+ ) : knowledge.length === 0 ? (
128
+ <div className="text-center py-12">
129
+ <FileText className="w-12 h-12 mx-auto text-gray-300 mb-4" />
130
+ <p className="text-gray-500 mb-4">还没有创建任何知识</p>
131
+ <Link href="/my/knowledge/new">
132
+ <Button>
133
+ <Plus className="w-4 h-4 mr-2" />
134
+ 创建第一条知识
135
+ </Button>
136
+ </Link>
137
+ </div>
138
+ ) : (
139
+ <div className="space-y-8">
140
+ {/* 已发布 */}
141
+ {publishedKnowledge.length > 0 && (
142
+ <section>
143
+ <h2 className="text-lg font-semibold mb-4 flex items-center gap-2">
144
+ <Upload className="w-5 h-5 text-green-500" />
145
+ 已发布到市场 ({publishedKnowledge.length})
146
+ </h2>
147
+ <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
148
+ {publishedKnowledge.map((item) => (
149
+ <Card key={item.id} className="hover:shadow-md transition-shadow">
150
+ <CardHeader className="pb-3">
151
+ <CardTitle className="text-lg line-clamp-1">{item.title}</CardTitle>
152
+ <CardDescription>
153
+ 创建于 {new Date(item.createdAt).toLocaleDateString()}
154
+ </CardDescription>
155
+ </CardHeader>
156
+ <CardContent>
157
+ <p className="text-sm text-gray-600 mb-3 line-clamp-2">
158
+ {item.content?.substring(0, 100)}...
159
+ </p>
160
+ <div className="flex items-center justify-between">
161
+ <div className="flex gap-1">
162
+ <Button
163
+ variant="outline"
164
+ size="sm"
165
+ onClick={() => handleRepublish(item.id)}
166
+ >
167
+ <Upload className="w-4 h-4 mr-1" />
168
+ 重新发布
169
+ </Button>
170
+ </div>
171
+ <div className="flex gap-1">
172
+ <Button variant="ghost" size="sm" asChild>
173
+ <Link href={`/my/knowledge/${item.id}`}>
174
+ <Edit className="w-4 h-4" />
175
+ </Link>
176
+ </Button>
177
+ <Button variant="ghost" size="sm" onClick={() => handleDelete(item.id)}>
178
+ <Trash2 className="w-4 h-4" />
179
+ </Button>
180
+ </div>
181
+ </div>
182
+ </CardContent>
183
+ </Card>
184
+ ))}
185
+ </div>
186
+ </section>
187
+ )}
188
+
189
+ {/* 未发布 */}
190
+ {myKnowledge.filter(k => k.marketStatus !== 'published').length > 0 && (
191
+ <section>
192
+ <h2 className="text-lg font-semibold mb-4 flex items-center gap-2">
193
+ <FileText className="w-5 h-5 text-gray-500" />
194
+ 个人知识 ({myKnowledge.filter(k => k.marketStatus !== 'published').length})
195
+ </h2>
196
+ <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
197
+ {myKnowledge.filter(k => k.marketStatus !== 'published').map((item) => (
198
+ <Card key={item.id} className="hover:shadow-md transition-shadow">
199
+ <CardHeader className="pb-3">
200
+ <CardTitle className="text-lg line-clamp-1">{item.title}</CardTitle>
201
+ <CardDescription>
202
+ 创建于 {new Date(item.createdAt).toLocaleDateString()}
203
+ </CardDescription>
204
+ </CardHeader>
205
+ <CardContent>
206
+ <p className="text-sm text-gray-600 mb-3 line-clamp-2">
207
+ {item.content?.substring(0, 100)}...
208
+ </p>
209
+ <div className="flex items-center justify-between">
210
+ <div className="flex gap-1">
211
+ <Button
212
+ variant="outline"
213
+ size="sm"
214
+ onClick={() => handlePublish(item.id)}
215
+ >
216
+ <Upload className="w-4 h-4 mr-1" />
217
+ 发布
218
+ </Button>
219
+ <Button variant="outline" size="sm" asChild>
220
+ <Link href={`/my/knowledge/${item.id}`}>
221
+ <Eye className="w-4 h-4 mr-1" />
222
+ 预览
223
+ </Link>
224
+ </Button>
225
+ </div>
226
+ <div className="flex gap-1">
227
+ <Button variant="ghost" size="sm" asChild>
228
+ <Link href={`/my/knowledge/${item.id}`}>
229
+ <Edit className="w-4 h-4" />
230
+ </Link>
231
+ </Button>
232
+ <Button variant="ghost" size="sm" onClick={() => handleDelete(item.id)}>
233
+ <Trash2 className="w-4 h-4" />
234
+ </Button>
235
+ </div>
236
+ </div>
237
+ </CardContent>
238
+ </Card>
239
+ ))}
240
+ </div>
241
+ </section>
242
+ )}
243
+ </div>
244
+ )}
245
+ </div>
246
+ </div>
247
+ );
248
+ }
@@ -0,0 +1,32 @@
1
+ 'use client';
2
+
3
+ import { Package, FileText, Clock, Brain, FolderOpen } from 'lucide-react';
4
+ import { SubNav, type NavItem } from '@/components/sub-nav';
5
+
6
+ const myNavItems: NavItem[] = [
7
+ { href: '/my/knowledge', label: '我的知识', icon: FileText },
8
+ { href: '/my/files', label: '我的文件', icon: FolderOpen },
9
+ { href: '/my/skills', label: '我的Skill', icon: Package },
10
+ { href: '/my/memory', label: '我的记忆', icon: Brain },
11
+ { href: '/my/scheduled-tasks', label: '定时任务', icon: Clock },
12
+ ];
13
+
14
+ export default function MyLayout({
15
+ children,
16
+ }: {
17
+ children: React.ReactNode;
18
+ }) {
19
+ return (
20
+ <div className="flex min-h-[calc(100vh-3rem)] bg-gray-50">
21
+ <div className="w-48 flex-shrink-0 border-r bg-white">
22
+ <div className="py-4 px-3 border-b">
23
+ <h2 className="text-sm font-semibold text-gray-900">我的</h2>
24
+ </div>
25
+ <SubNav items={myNavItems} />
26
+ </div>
27
+ <main className="flex-1 overflow-auto">
28
+ {children}
29
+ </main>
30
+ </div>
31
+ );
32
+ }
@@ -0,0 +1,164 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect, useCallback } from 'react';
4
+ import { useRouter } from 'next/navigation';
5
+ import dynamic from 'next/dynamic';
6
+ import { Brain, Eye, EyeOff, Save } from 'lucide-react';
7
+ import { Button } from '@/components/ui/button';
8
+ import ReactMarkdown from 'react-markdown';
9
+
10
+ const MDEditor = dynamic(
11
+ () => import('@uiw/react-md-editor'),
12
+ { ssr: false }
13
+ );
14
+
15
+ const DEFAULT_CONTENT = `# 用户记忆
16
+
17
+ 暂无记忆内容。每次您告诉我"记下来"时,我会自动将重要信息保存到这里。
18
+
19
+ ## 偏好
20
+ - 在此记录您的偏好设置
21
+
22
+ ## 项目
23
+ - 在此记录您的项目信息
24
+
25
+ ## 其他
26
+ - 在此记录其他需要记住的信息
27
+ `;
28
+
29
+ export default function MemoryPage() {
30
+ const router = useRouter();
31
+ const [content, setContent] = useState('');
32
+ const [previewMode, setPreviewMode] = useState<'edit' | 'preview'>('edit');
33
+ const [saving, setSaving] = useState(false);
34
+ const [loading, setLoading] = useState(true);
35
+ const [userId, setUserId] = useState<string>('');
36
+ const [lastSaved, setLastSaved] = useState<Date | null>(null);
37
+
38
+ const loadMemory = async (uid: string) => {
39
+ setLoading(true);
40
+ try {
41
+ const res = await fetch(`/api/memory?userId=${uid}`);
42
+ if (res.ok) {
43
+ const data = await res.json();
44
+ setContent(data.content || DEFAULT_CONTENT);
45
+ } else {
46
+ setContent(DEFAULT_CONTENT);
47
+ }
48
+ } catch (error) {
49
+ console.error('Failed to load memory:', error);
50
+ setContent(DEFAULT_CONTENT);
51
+ } finally {
52
+ setLoading(false);
53
+ }
54
+ };
55
+
56
+ const handleSave = useCallback(async () => {
57
+ if (!userId) return;
58
+
59
+ setSaving(true);
60
+ try {
61
+ const res = await fetch('/api/memory', {
62
+ method: 'POST',
63
+ headers: { 'Content-Type': 'application/json' },
64
+ body: JSON.stringify({ userId, content }),
65
+ });
66
+
67
+ if (res.ok) {
68
+ setLastSaved(new Date());
69
+ } else {
70
+ const error = await res.json();
71
+ alert(error.error || '保存失败');
72
+ }
73
+ } catch (error) {
74
+ console.error('Failed to save memory:', error);
75
+ alert('保存失败');
76
+ } finally {
77
+ setSaving(false);
78
+ }
79
+ }, [userId, content]);
80
+
81
+ useEffect(() => {
82
+ const uid = localStorage.getItem('userId');
83
+ if (!uid) {
84
+ router.push('/login');
85
+ return;
86
+ }
87
+ setUserId(uid);
88
+ loadMemory(uid);
89
+ }, [router]);
90
+
91
+ useEffect(() => {
92
+ const handleKeyDown = (e: KeyboardEvent) => {
93
+ if ((e.ctrlKey || e.metaKey) && e.key === 's') {
94
+ e.preventDefault();
95
+ handleSave();
96
+ }
97
+ if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
98
+ e.preventDefault();
99
+ setPreviewMode(p => p === 'edit' ? 'preview' : 'edit');
100
+ }
101
+ };
102
+ window.addEventListener('keydown', handleKeyDown);
103
+ return () => window.removeEventListener('keydown', handleKeyDown);
104
+ }, [handleSave]);
105
+
106
+ const wordCount = content.trim() ? content.trim().split(/\s+/).length : 0;
107
+
108
+ if (loading) {
109
+ return (
110
+ <div className="h-full flex items-center justify-center">
111
+ <div className="text-muted-foreground">加载中...</div>
112
+ </div>
113
+ );
114
+ }
115
+
116
+ return (
117
+ <div className="p-6 h-full flex flex-col">
118
+ <div className="flex items-center justify-between mb-4">
119
+ <div className="flex items-center gap-2">
120
+ <Brain className="w-5 h-5 text-purple-500" />
121
+ <h1 className="text-lg font-semibold">我的记忆</h1>
122
+ </div>
123
+ <div className="flex items-center gap-2">
124
+ <Button
125
+ variant="outline"
126
+ size="sm"
127
+ onClick={() => setPreviewMode(previewMode === 'edit' ? 'preview' : 'edit')}
128
+ >
129
+ {previewMode === 'edit' ? <Eye className="w-4 h-4 mr-1" /> : <EyeOff className="w-4 h-4 mr-1" />}
130
+ {previewMode === 'edit' ? '预览' : '编辑'}
131
+ </Button>
132
+ <Button onClick={handleSave} disabled={saving}>
133
+ <Save className="w-4 h-4 mr-1" />
134
+ {saving ? '保存中...' : '保存'}
135
+ </Button>
136
+ </div>
137
+ </div>
138
+
139
+ <div className="flex-1 overflow-hidden border rounded-lg">
140
+ {previewMode === 'preview' ? (
141
+ <div className="h-full overflow-auto p-6 prose prose-sm max-w-none">
142
+ <ReactMarkdown>{content}</ReactMarkdown>
143
+ </div>
144
+ ) : (
145
+ <div className="h-full" data-color-mode="light">
146
+ <MDEditor
147
+ value={content}
148
+ onChange={(val) => setContent(val || '')}
149
+ height="100%"
150
+ preview="live"
151
+ hideToolbar={false}
152
+ visibleDragbar={false}
153
+ />
154
+ </div>
155
+ )}
156
+ </div>
157
+
158
+ <div className="flex-shrink-0 mt-2 px-1 py-1 text-xs text-muted-foreground flex items-center justify-between">
159
+ <span>字数: {wordCount}</span>
160
+ {lastSaved && <span>上次保存: {lastSaved.toLocaleTimeString('zh-CN')}</span>}
161
+ </div>
162
+ </div>
163
+ );
164
+ }
@@ -0,0 +1,18 @@
1
+ 'use client';
2
+
3
+ import { useEffect } from 'react';
4
+ import { useRouter } from 'next/navigation';
5
+
6
+ export default function MyIndex() {
7
+ const router = useRouter();
8
+
9
+ useEffect(() => {
10
+ router.replace('/my/knowledge');
11
+ }, [router]);
12
+
13
+ return (
14
+ <div className="flex items-center justify-center h-full">
15
+ <div className="text-muted-foreground">加载中...</div>
16
+ </div>
17
+ );
18
+ }