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,464 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
|
|
4
|
+
import { Search, X, ExternalLink, BookOpen, Loader2 } from 'lucide-react';
|
|
5
|
+
import Link from 'next/link';
|
|
6
|
+
import { cn } from '@/lib/utils';
|
|
7
|
+
import { MessageResponse } from '@/components/ai-elements/message';
|
|
8
|
+
import { KnowledgePreviewPanel } from './KnowledgePreviewPanel';
|
|
9
|
+
import { EmptyPreviewState } from './EmptyPreviewState';
|
|
10
|
+
|
|
11
|
+
interface KnowledgeItem {
|
|
12
|
+
id: string;
|
|
13
|
+
title: string;
|
|
14
|
+
content?: string;
|
|
15
|
+
category?: string;
|
|
16
|
+
createdAt: string;
|
|
17
|
+
updatedAt: string;
|
|
18
|
+
author?: string;
|
|
19
|
+
marketStatus?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface KnowledgePickerDialogProps {
|
|
23
|
+
open: boolean;
|
|
24
|
+
onOpenChange: (open: boolean) => void;
|
|
25
|
+
onSelect: (knowledge: KnowledgeItem) => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const PAGE_SIZE = 10;
|
|
29
|
+
|
|
30
|
+
function debounce<T extends (...args: any[]) => any>(func: T, wait: number): T {
|
|
31
|
+
let timeout: NodeJS.Timeout | null = null;
|
|
32
|
+
return ((...args: Parameters<T>) => {
|
|
33
|
+
if (timeout) clearTimeout(timeout);
|
|
34
|
+
timeout = setTimeout(() => func(...args), wait);
|
|
35
|
+
}) as T;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function KnowledgePickerDialog({ open, onOpenChange, onSelect }: KnowledgePickerDialogProps) {
|
|
39
|
+
const [activeTab, setActiveTab] = useState<'my' | 'market'>('my');
|
|
40
|
+
const [search, setSearch] = useState('');
|
|
41
|
+
const [knowledge, setKnowledge] = useState<KnowledgeItem[]>([]);
|
|
42
|
+
const [total, setTotal] = useState(0);
|
|
43
|
+
const [page, setPage] = useState(1);
|
|
44
|
+
const [loading, setLoading] = useState(false);
|
|
45
|
+
const searchInputRef = useRef<HTMLInputElement>(null);
|
|
46
|
+
|
|
47
|
+
const [selectedKnowledge, setSelectedKnowledge] = useState<KnowledgeItem | null>(null);
|
|
48
|
+
const [previewContent, setPreviewContent] = useState<string>('');
|
|
49
|
+
const [previewLoading, setPreviewLoading] = useState(false);
|
|
50
|
+
const [contentCache, setContentCache] = useState<Map<string, string>>(new Map());
|
|
51
|
+
const [isMobile, setIsMobile] = useState(false);
|
|
52
|
+
|
|
53
|
+
const fetchKnowledge = useCallback(async () => {
|
|
54
|
+
setLoading(true);
|
|
55
|
+
try {
|
|
56
|
+
const offset = (page - 1) * PAGE_SIZE;
|
|
57
|
+
const userId = localStorage.getItem('userId');
|
|
58
|
+
|
|
59
|
+
let url = '';
|
|
60
|
+
if (activeTab === 'my') {
|
|
61
|
+
url = `/api/knowledge?author=${encodeURIComponent(userId || '')}&limit=${PAGE_SIZE}&offset=${offset}`;
|
|
62
|
+
if (search) {
|
|
63
|
+
url += `&search=${encodeURIComponent(search)}`;
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
url = `/api/market/knowledge?limit=${PAGE_SIZE}&offset=${offset}`;
|
|
67
|
+
if (search) {
|
|
68
|
+
url += `&search=${encodeURIComponent(search)}`;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const response = await fetch(url);
|
|
73
|
+
const data = await response.json();
|
|
74
|
+
|
|
75
|
+
if (activeTab === 'my') {
|
|
76
|
+
setKnowledge(data.documents || []);
|
|
77
|
+
setTotal(data.total || 0);
|
|
78
|
+
} else {
|
|
79
|
+
setKnowledge(data.documents || []);
|
|
80
|
+
setTotal(data.total || 0);
|
|
81
|
+
}
|
|
82
|
+
} catch (error) {
|
|
83
|
+
console.error('Failed to fetch knowledge:', error);
|
|
84
|
+
} finally {
|
|
85
|
+
setLoading(false);
|
|
86
|
+
}
|
|
87
|
+
}, [activeTab, search, page]);
|
|
88
|
+
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
if (open) {
|
|
91
|
+
setPage(1);
|
|
92
|
+
setSearch('');
|
|
93
|
+
setSelectedKnowledge(null);
|
|
94
|
+
setPreviewContent('');
|
|
95
|
+
fetchKnowledge();
|
|
96
|
+
}
|
|
97
|
+
}, [open, activeTab]);
|
|
98
|
+
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
fetchKnowledge();
|
|
101
|
+
}, [page]);
|
|
102
|
+
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
if (open && searchInputRef.current) {
|
|
105
|
+
setTimeout(() => searchInputRef.current?.focus(), 100);
|
|
106
|
+
}
|
|
107
|
+
}, [open, search]);
|
|
108
|
+
|
|
109
|
+
const totalPages = Math.ceil(total / PAGE_SIZE);
|
|
110
|
+
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
const checkMobile = () => setIsMobile(window.innerWidth < 768);
|
|
113
|
+
checkMobile();
|
|
114
|
+
window.addEventListener('resize', checkMobile);
|
|
115
|
+
return () => window.removeEventListener('resize', checkMobile);
|
|
116
|
+
}, []);
|
|
117
|
+
|
|
118
|
+
const loadPreviewContent = useCallback(async (id: string) => {
|
|
119
|
+
if (contentCache.has(id)) {
|
|
120
|
+
setPreviewContent(contentCache.get(id)!);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
setPreviewLoading(true);
|
|
125
|
+
try {
|
|
126
|
+
const response = await fetch(`/api/knowledge/${id}`);
|
|
127
|
+
const data = await response.json();
|
|
128
|
+
const content = data.document?.content || '';
|
|
129
|
+
|
|
130
|
+
setContentCache(prev => new Map(prev).set(id, content));
|
|
131
|
+
setPreviewContent(content);
|
|
132
|
+
} catch (error) {
|
|
133
|
+
console.error('Failed to load preview:', error);
|
|
134
|
+
setPreviewContent('');
|
|
135
|
+
} finally {
|
|
136
|
+
setPreviewLoading(false);
|
|
137
|
+
}
|
|
138
|
+
}, [contentCache]);
|
|
139
|
+
|
|
140
|
+
const handlePreview = useCallback((item: KnowledgeItem) => {
|
|
141
|
+
setSelectedKnowledge(item);
|
|
142
|
+
loadPreviewContent(item.id);
|
|
143
|
+
}, [loadPreviewContent]);
|
|
144
|
+
|
|
145
|
+
const handleSelect = useCallback((item: KnowledgeItem) => {
|
|
146
|
+
onSelect(item);
|
|
147
|
+
onOpenChange(false);
|
|
148
|
+
}, [onSelect, onOpenChange]);
|
|
149
|
+
|
|
150
|
+
const debouncedSearch = useMemo(
|
|
151
|
+
() => debounce((value: string) => {
|
|
152
|
+
setPage(1);
|
|
153
|
+
fetchKnowledge();
|
|
154
|
+
}, 300),
|
|
155
|
+
[fetchKnowledge]
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
useEffect(() => {
|
|
159
|
+
if (!open) return;
|
|
160
|
+
|
|
161
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
162
|
+
if (e.key === 'Escape') {
|
|
163
|
+
onOpenChange(false);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (knowledge.length === 0) return;
|
|
168
|
+
|
|
169
|
+
const currentIndex = selectedKnowledge
|
|
170
|
+
? knowledge.findIndex(k => k.id === selectedKnowledge.id)
|
|
171
|
+
: -1;
|
|
172
|
+
|
|
173
|
+
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
|
|
174
|
+
e.preventDefault();
|
|
175
|
+
let newIndex: number;
|
|
176
|
+
|
|
177
|
+
if (e.key === 'ArrowDown') {
|
|
178
|
+
newIndex = currentIndex < knowledge.length - 1 ? currentIndex + 1 : 0;
|
|
179
|
+
} else {
|
|
180
|
+
newIndex = currentIndex > 0 ? currentIndex - 1 : knowledge.length - 1;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
handlePreview(knowledge[newIndex]);
|
|
184
|
+
} else if (e.key === 'Enter' && selectedKnowledge) {
|
|
185
|
+
e.preventDefault();
|
|
186
|
+
if (e.ctrlKey || e.metaKey) {
|
|
187
|
+
handleSelect(selectedKnowledge);
|
|
188
|
+
} else {
|
|
189
|
+
handlePreview(selectedKnowledge);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
195
|
+
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
196
|
+
}, [open, knowledge, selectedKnowledge, onOpenChange, handlePreview, handleSelect]);
|
|
197
|
+
|
|
198
|
+
return (
|
|
199
|
+
<>
|
|
200
|
+
{open && (
|
|
201
|
+
<div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4">
|
|
202
|
+
<div className={cn(
|
|
203
|
+
"bg-white rounded-xl shadow-2xl w-full flex flex-col overflow-hidden",
|
|
204
|
+
isMobile ? "max-w-2xl max-h-[90vh]" : "max-w-5xl max-h-[80vh]"
|
|
205
|
+
)}>
|
|
206
|
+
{/* Header */}
|
|
207
|
+
<div className="px-4 py-3 border-b border-gray-200 flex items-center justify-between">
|
|
208
|
+
<h2 className="text-lg font-semibold text-gray-900 flex items-center gap-2">
|
|
209
|
+
<BookOpen className="w-5 h-5 text-amber-600" />
|
|
210
|
+
知识库
|
|
211
|
+
</h2>
|
|
212
|
+
<button
|
|
213
|
+
onClick={() => onOpenChange(false)}
|
|
214
|
+
className="p-1 text-gray-400 hover:text-gray-600 rounded"
|
|
215
|
+
>
|
|
216
|
+
<X className="w-5 h-5" />
|
|
217
|
+
</button>
|
|
218
|
+
</div>
|
|
219
|
+
|
|
220
|
+
{/* Tabs & Search */}
|
|
221
|
+
<div className="px-4 py-3 border-b border-gray-200 space-y-3">
|
|
222
|
+
<div className="flex gap-2">
|
|
223
|
+
<button
|
|
224
|
+
onClick={() => setActiveTab('my')}
|
|
225
|
+
className={`px-4 py-1.5 rounded-lg text-sm font-medium transition-colors ${
|
|
226
|
+
activeTab === 'my'
|
|
227
|
+
? 'bg-amber-100 text-amber-700'
|
|
228
|
+
: 'text-gray-600 hover:bg-gray-100'
|
|
229
|
+
}`}
|
|
230
|
+
>
|
|
231
|
+
我的知识
|
|
232
|
+
</button>
|
|
233
|
+
<button
|
|
234
|
+
onClick={() => setActiveTab('market')}
|
|
235
|
+
className={`px-4 py-1.5 rounded-lg text-sm font-medium transition-colors ${
|
|
236
|
+
activeTab === 'market'
|
|
237
|
+
? 'bg-amber-100 text-amber-700'
|
|
238
|
+
: 'text-gray-600 hover:bg-gray-100'
|
|
239
|
+
}`}
|
|
240
|
+
>
|
|
241
|
+
市场知识
|
|
242
|
+
</button>
|
|
243
|
+
</div>
|
|
244
|
+
|
|
245
|
+
<div className="relative">
|
|
246
|
+
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" />
|
|
247
|
+
<input
|
|
248
|
+
ref={searchInputRef}
|
|
249
|
+
type="text"
|
|
250
|
+
value={search}
|
|
251
|
+
onChange={(e) => {
|
|
252
|
+
setSearch(e.target.value);
|
|
253
|
+
setSelectedKnowledge(null);
|
|
254
|
+
setPreviewContent('');
|
|
255
|
+
debouncedSearch(e.target.value);
|
|
256
|
+
}}
|
|
257
|
+
placeholder="搜索知识..."
|
|
258
|
+
className="w-full pl-10 pr-4 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-amber-500"
|
|
259
|
+
/>
|
|
260
|
+
</div>
|
|
261
|
+
</div>
|
|
262
|
+
|
|
263
|
+
{/* Main Content - 桌面端左右分栏,移动端手风琴 */}
|
|
264
|
+
{isMobile ? (
|
|
265
|
+
<div className="flex-1 overflow-y-auto min-h-0">
|
|
266
|
+
{loading ? (
|
|
267
|
+
<div className="flex items-center justify-center py-12">
|
|
268
|
+
<Loader2 className="w-6 h-6 text-amber-600 animate-spin" />
|
|
269
|
+
</div>
|
|
270
|
+
) : knowledge.length === 0 ? (
|
|
271
|
+
<div className="flex flex-col items-center justify-center py-12 text-gray-500">
|
|
272
|
+
<BookOpen className="w-12 h-12 mb-3 text-gray-300" />
|
|
273
|
+
<p className="text-sm">暂无知识</p>
|
|
274
|
+
{activeTab === 'my' && (
|
|
275
|
+
<Link
|
|
276
|
+
href="/my/knowledge/new"
|
|
277
|
+
className="mt-2 text-sm text-amber-600 hover:text-amber-700"
|
|
278
|
+
onClick={() => onOpenChange(false)}
|
|
279
|
+
>
|
|
280
|
+
创建第一条知识
|
|
281
|
+
</Link>
|
|
282
|
+
)}
|
|
283
|
+
</div>
|
|
284
|
+
) : (
|
|
285
|
+
<div className="divide-y divide-gray-100">
|
|
286
|
+
{knowledge.map((item) => (
|
|
287
|
+
<div key={item.id}>
|
|
288
|
+
<div
|
|
289
|
+
className={cn(
|
|
290
|
+
"px-4 py-3 transition-colors cursor-pointer",
|
|
291
|
+
selectedKnowledge?.id === item.id
|
|
292
|
+
? "bg-amber-50"
|
|
293
|
+
: "hover:bg-gray-50"
|
|
294
|
+
)}
|
|
295
|
+
onClick={() => {
|
|
296
|
+
if (selectedKnowledge?.id === item.id) {
|
|
297
|
+
setSelectedKnowledge(null);
|
|
298
|
+
setPreviewContent('');
|
|
299
|
+
} else {
|
|
300
|
+
handlePreview(item);
|
|
301
|
+
}
|
|
302
|
+
}}
|
|
303
|
+
>
|
|
304
|
+
<div className="flex items-start justify-between gap-3">
|
|
305
|
+
<div className="min-w-0 flex-1">
|
|
306
|
+
<h3 className="font-medium text-gray-900 truncate">
|
|
307
|
+
{item.title}
|
|
308
|
+
</h3>
|
|
309
|
+
<div className="flex items-center gap-2 mt-1 text-xs text-gray-500 flex-wrap">
|
|
310
|
+
{item.category && (
|
|
311
|
+
<span className="px-2 py-0.5 bg-gray-100 rounded">
|
|
312
|
+
{item.category}
|
|
313
|
+
</span>
|
|
314
|
+
)}
|
|
315
|
+
<span>
|
|
316
|
+
{new Date(item.updatedAt).toLocaleDateString('zh-CN')}
|
|
317
|
+
</span>
|
|
318
|
+
{activeTab === 'market' && item.author && (
|
|
319
|
+
<span>· {item.author}</span>
|
|
320
|
+
)}
|
|
321
|
+
</div>
|
|
322
|
+
</div>
|
|
323
|
+
<button
|
|
324
|
+
onClick={(e) => {
|
|
325
|
+
e.stopPropagation();
|
|
326
|
+
handleSelect(item);
|
|
327
|
+
}}
|
|
328
|
+
className="px-3 py-1 text-xs font-medium bg-amber-100 text-amber-700 rounded-lg hover:bg-amber-200 transition-colors shrink-0"
|
|
329
|
+
>
|
|
330
|
+
引用
|
|
331
|
+
</button>
|
|
332
|
+
</div>
|
|
333
|
+
</div>
|
|
334
|
+
|
|
335
|
+
{selectedKnowledge?.id === item.id && (
|
|
336
|
+
<div className="px-4 py-3 bg-gray-50 border-t border-gray-200">
|
|
337
|
+
{previewLoading ? (
|
|
338
|
+
<div className="space-y-3 animate-pulse">
|
|
339
|
+
<div className="h-4 bg-gray-200 rounded w-3/4"></div>
|
|
340
|
+
<div className="h-4 bg-gray-200 rounded w-full"></div>
|
|
341
|
+
<div className="h-4 bg-gray-200 rounded w-5/6"></div>
|
|
342
|
+
</div>
|
|
343
|
+
) : (
|
|
344
|
+
<div className="prose prose-sm max-w-none">
|
|
345
|
+
<MessageResponse>{previewContent}</MessageResponse>
|
|
346
|
+
</div>
|
|
347
|
+
)}
|
|
348
|
+
</div>
|
|
349
|
+
)}
|
|
350
|
+
</div>
|
|
351
|
+
))}
|
|
352
|
+
</div>
|
|
353
|
+
)}
|
|
354
|
+
</div>
|
|
355
|
+
) : (
|
|
356
|
+
<div className="flex flex-1 overflow-hidden min-h-0">
|
|
357
|
+
<div className="w-2/5 border-r border-gray-200 overflow-y-auto">
|
|
358
|
+
{loading ? (
|
|
359
|
+
<div className="flex items-center justify-center py-12">
|
|
360
|
+
<Loader2 className="w-6 h-6 text-amber-600 animate-spin" />
|
|
361
|
+
</div>
|
|
362
|
+
) : knowledge.length === 0 ? (
|
|
363
|
+
<div className="flex flex-col items-center justify-center py-12 text-gray-500">
|
|
364
|
+
<BookOpen className="w-12 h-12 mb-3 text-gray-300" />
|
|
365
|
+
<p className="text-sm">暂无知识</p>
|
|
366
|
+
{activeTab === 'my' && (
|
|
367
|
+
<Link
|
|
368
|
+
href="/my/knowledge/new"
|
|
369
|
+
className="mt-2 text-sm text-amber-600 hover:text-amber-700"
|
|
370
|
+
onClick={() => onOpenChange(false)}
|
|
371
|
+
>
|
|
372
|
+
创建第一条知识
|
|
373
|
+
</Link>
|
|
374
|
+
)}
|
|
375
|
+
</div>
|
|
376
|
+
) : (
|
|
377
|
+
<div className="divide-y divide-gray-100">
|
|
378
|
+
{knowledge.map((item) => (
|
|
379
|
+
<div
|
|
380
|
+
key={item.id}
|
|
381
|
+
className={cn(
|
|
382
|
+
"px-4 py-3 transition-colors cursor-pointer",
|
|
383
|
+
selectedKnowledge?.id === item.id
|
|
384
|
+
? "bg-amber-50 border-l-2 border-amber-600"
|
|
385
|
+
: "hover:bg-gray-50 border-l-2 border-transparent"
|
|
386
|
+
)}
|
|
387
|
+
onClick={() => handlePreview(item)}
|
|
388
|
+
onDoubleClick={() => handleSelect(item)}
|
|
389
|
+
>
|
|
390
|
+
<div className="flex items-start justify-between gap-3">
|
|
391
|
+
<div className="min-w-0 flex-1">
|
|
392
|
+
<h3 className="font-medium text-gray-900 truncate">
|
|
393
|
+
{item.title}
|
|
394
|
+
</h3>
|
|
395
|
+
<div className="flex items-center gap-2 mt-1 text-xs text-gray-500 flex-wrap">
|
|
396
|
+
{item.category && (
|
|
397
|
+
<span className="px-2 py-0.5 bg-gray-100 rounded">
|
|
398
|
+
{item.category}
|
|
399
|
+
</span>
|
|
400
|
+
)}
|
|
401
|
+
<span>
|
|
402
|
+
{new Date(item.updatedAt).toLocaleDateString('zh-CN')}
|
|
403
|
+
</span>
|
|
404
|
+
{activeTab === 'market' && item.author && (
|
|
405
|
+
<span>· {item.author}</span>
|
|
406
|
+
)}
|
|
407
|
+
</div>
|
|
408
|
+
</div>
|
|
409
|
+
</div>
|
|
410
|
+
</div>
|
|
411
|
+
))}
|
|
412
|
+
</div>
|
|
413
|
+
)}
|
|
414
|
+
</div>
|
|
415
|
+
|
|
416
|
+
<div className="w-3/5 overflow-hidden bg-gray-50">
|
|
417
|
+
{selectedKnowledge ? (
|
|
418
|
+
<KnowledgePreviewPanel
|
|
419
|
+
knowledge={selectedKnowledge}
|
|
420
|
+
content={previewContent}
|
|
421
|
+
loading={previewLoading}
|
|
422
|
+
onCite={handleSelect}
|
|
423
|
+
activeTab={activeTab}
|
|
424
|
+
/>
|
|
425
|
+
) : (
|
|
426
|
+
<EmptyPreviewState />
|
|
427
|
+
)}
|
|
428
|
+
</div>
|
|
429
|
+
</div>
|
|
430
|
+
)}
|
|
431
|
+
|
|
432
|
+
{/* Pagination */}
|
|
433
|
+
{totalPages > 1 && (
|
|
434
|
+
<div className="px-4 py-3 border-t border-gray-200 flex items-center justify-between">
|
|
435
|
+
<span className="text-xs text-gray-500">
|
|
436
|
+
共 {total} 条知识
|
|
437
|
+
</span>
|
|
438
|
+
<div className="flex items-center gap-1">
|
|
439
|
+
<button
|
|
440
|
+
onClick={() => setPage(p => Math.max(1, p - 1))}
|
|
441
|
+
disabled={page === 1}
|
|
442
|
+
className="px-2 py-1 text-xs text-gray-600 hover:bg-gray-100 rounded disabled:opacity-50 disabled:cursor-not-allowed"
|
|
443
|
+
>
|
|
444
|
+
上一页
|
|
445
|
+
</button>
|
|
446
|
+
<span className="px-2 py-1 text-xs text-gray-600">
|
|
447
|
+
{page} / {totalPages}
|
|
448
|
+
</span>
|
|
449
|
+
<button
|
|
450
|
+
onClick={() => setPage(p => Math.min(totalPages, p + 1))}
|
|
451
|
+
disabled={page === totalPages}
|
|
452
|
+
className="px-2 py-1 text-xs text-gray-600 hover:bg-gray-100 rounded disabled:opacity-50 disabled:cursor-not-allowed"
|
|
453
|
+
>
|
|
454
|
+
下一页
|
|
455
|
+
</button>
|
|
456
|
+
</div>
|
|
457
|
+
</div>
|
|
458
|
+
)}
|
|
459
|
+
</div>
|
|
460
|
+
</div>
|
|
461
|
+
)}
|
|
462
|
+
</>
|
|
463
|
+
);
|
|
464
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import dynamic from 'next/dynamic';
|
|
4
|
+
import { KNOWLEDGE_TYPE_LABELS } from '@/lib/knowledge-types';
|
|
5
|
+
|
|
6
|
+
const MDEditor = dynamic(
|
|
7
|
+
() => import('@uiw/react-md-editor'),
|
|
8
|
+
{ ssr: false }
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
interface KnowledgePreviewProps {
|
|
12
|
+
title: string;
|
|
13
|
+
content: string;
|
|
14
|
+
category?: string;
|
|
15
|
+
tags?: string[];
|
|
16
|
+
author: string;
|
|
17
|
+
updatedAt: string;
|
|
18
|
+
type?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function KnowledgePreview({
|
|
22
|
+
title,
|
|
23
|
+
content,
|
|
24
|
+
category,
|
|
25
|
+
tags,
|
|
26
|
+
author,
|
|
27
|
+
updatedAt,
|
|
28
|
+
type,
|
|
29
|
+
}: KnowledgePreviewProps) {
|
|
30
|
+
return (
|
|
31
|
+
<div className="bg-white rounded-lg shadow-sm border p-6">
|
|
32
|
+
<div className="mb-6">
|
|
33
|
+
<h1 className="text-2xl font-bold text-gray-900 mb-2">{title}</h1>
|
|
34
|
+
<div className="flex items-center gap-3 text-sm text-gray-500">
|
|
35
|
+
{category && (
|
|
36
|
+
<span className="px-2 py-0.5 bg-gray-100 rounded text-xs">
|
|
37
|
+
{category}
|
|
38
|
+
</span>
|
|
39
|
+
)}
|
|
40
|
+
{type && KNOWLEDGE_TYPE_LABELS[type as keyof typeof KNOWLEDGE_TYPE_LABELS] && (
|
|
41
|
+
<span className="px-2 py-0.5 bg-blue-100 text-blue-700 rounded text-xs">
|
|
42
|
+
{KNOWLEDGE_TYPE_LABELS[type as keyof typeof KNOWLEDGE_TYPE_LABELS]}
|
|
43
|
+
</span>
|
|
44
|
+
)}
|
|
45
|
+
<span>作者: {author}</span>
|
|
46
|
+
<span>更新于: {new Date(updatedAt).toLocaleDateString('zh-CN')}</span>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
{tags && tags.length > 0 && (
|
|
51
|
+
<div className="flex flex-wrap gap-2 mb-6">
|
|
52
|
+
{tags.map((tag, index) => (
|
|
53
|
+
<span key={index} className="px-2 py-0.5 bg-gray-100 text-gray-600 rounded text-xs">
|
|
54
|
+
{tag}
|
|
55
|
+
</span>
|
|
56
|
+
))}
|
|
57
|
+
</div>
|
|
58
|
+
)}
|
|
59
|
+
|
|
60
|
+
<div className="prose max-w-none" data-color-mode="light" style={{ minHeight: '60vh' }}>
|
|
61
|
+
<MDEditor
|
|
62
|
+
value={content}
|
|
63
|
+
preview="preview"
|
|
64
|
+
hideToolbar
|
|
65
|
+
height={600}
|
|
66
|
+
/>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { ExternalLink } from 'lucide-react';
|
|
4
|
+
import Link from 'next/link';
|
|
5
|
+
import { MessageResponse } from '@/components/ai-elements/message';
|
|
6
|
+
|
|
7
|
+
interface KnowledgeItem {
|
|
8
|
+
id: string;
|
|
9
|
+
title: string;
|
|
10
|
+
content?: string;
|
|
11
|
+
category?: string;
|
|
12
|
+
createdAt: string;
|
|
13
|
+
updatedAt: string;
|
|
14
|
+
author?: string;
|
|
15
|
+
marketStatus?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface KnowledgePreviewPanelProps {
|
|
19
|
+
knowledge: KnowledgeItem;
|
|
20
|
+
content: string;
|
|
21
|
+
loading: boolean;
|
|
22
|
+
onCite: (knowledge: KnowledgeItem) => void;
|
|
23
|
+
activeTab: 'my' | 'market';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function KnowledgePreviewPanel({
|
|
27
|
+
knowledge,
|
|
28
|
+
content,
|
|
29
|
+
loading,
|
|
30
|
+
onCite,
|
|
31
|
+
activeTab
|
|
32
|
+
}: KnowledgePreviewPanelProps) {
|
|
33
|
+
return (
|
|
34
|
+
<div className="h-full flex flex-col bg-white">
|
|
35
|
+
<div className="px-6 py-4 border-b border-gray-200">
|
|
36
|
+
<h3 className="text-lg font-semibold text-gray-900">{knowledge.title}</h3>
|
|
37
|
+
<div className="flex items-center gap-2 mt-2 text-xs text-gray-500 flex-wrap">
|
|
38
|
+
{knowledge.category && (
|
|
39
|
+
<span className="px-2 py-0.5 bg-amber-100 text-amber-700 rounded">
|
|
40
|
+
{knowledge.category}
|
|
41
|
+
</span>
|
|
42
|
+
)}
|
|
43
|
+
<span>更新于 {new Date(knowledge.updatedAt).toLocaleDateString('zh-CN')}</span>
|
|
44
|
+
{activeTab === 'market' && knowledge.author && (
|
|
45
|
+
<span>· 作者: {knowledge.author}</span>
|
|
46
|
+
)}
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<div className="flex-1 overflow-y-auto px-6 py-4 min-h-0">
|
|
51
|
+
{loading ? (
|
|
52
|
+
<div className="space-y-3 animate-pulse">
|
|
53
|
+
<div className="h-4 bg-gray-200 rounded w-3/4"></div>
|
|
54
|
+
<div className="h-4 bg-gray-200 rounded w-full"></div>
|
|
55
|
+
<div className="h-4 bg-gray-200 rounded w-5/6"></div>
|
|
56
|
+
<div className="h-4 bg-gray-200 rounded w-full"></div>
|
|
57
|
+
<div className="h-4 bg-gray-200 rounded w-2/3"></div>
|
|
58
|
+
<div className="h-4 bg-gray-200 rounded w-full"></div>
|
|
59
|
+
<div className="h-4 bg-gray-200 rounded w-4/5"></div>
|
|
60
|
+
</div>
|
|
61
|
+
) : (
|
|
62
|
+
<div className="prose prose-sm max-w-none">
|
|
63
|
+
<MessageResponse>{content}</MessageResponse>
|
|
64
|
+
</div>
|
|
65
|
+
)}
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<div className="px-6 py-3 border-t border-gray-200 bg-gray-50 flex items-center gap-2">
|
|
69
|
+
<button
|
|
70
|
+
onClick={() => onCite(knowledge)}
|
|
71
|
+
className="px-4 py-2 bg-amber-600 text-white rounded-lg hover:bg-amber-700 transition-colors text-sm font-medium"
|
|
72
|
+
>
|
|
73
|
+
引用此知识
|
|
74
|
+
</button>
|
|
75
|
+
<Link
|
|
76
|
+
href={activeTab === 'my' ? `/my/knowledge/${knowledge.id}` : `/market/knowledge/${knowledge.id}`}
|
|
77
|
+
target="_blank"
|
|
78
|
+
className="px-4 py-2 text-gray-600 hover:text-gray-800 rounded-lg hover:bg-gray-100 transition-colors text-sm font-medium flex items-center gap-1"
|
|
79
|
+
>
|
|
80
|
+
<ExternalLink className="w-4 h-4" />
|
|
81
|
+
在新窗口打开
|
|
82
|
+
</Link>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
);
|
|
86
|
+
}
|