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,147 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from 'react';
|
|
4
|
+
import { User, Settings, BookOpen, Clock, LogOut, ChevronRight, Bot, Plus } from 'lucide-react';
|
|
5
|
+
import { Button } from '@/components/ui/button';
|
|
6
|
+
|
|
7
|
+
interface UserInfo {
|
|
8
|
+
userId: string;
|
|
9
|
+
name?: string;
|
|
10
|
+
email?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default function MobileProfilePage() {
|
|
14
|
+
const [userInfo, setUserInfo] = useState<UserInfo | null>(null);
|
|
15
|
+
const [stats, setStats] = useState({ ticketsCount: 0, sessionsCount: 0, skillsCount: 0 });
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
const uid = localStorage.getItem('userId');
|
|
19
|
+
if (uid) {
|
|
20
|
+
setUserInfo({ userId: uid });
|
|
21
|
+
|
|
22
|
+
async function loadStats() {
|
|
23
|
+
try {
|
|
24
|
+
const [ticketsRes, skillsRes] = await Promise.all([
|
|
25
|
+
fetch(`/api/tickets?limit=1`),
|
|
26
|
+
fetch(`/api/user/skills?userId=${uid}`),
|
|
27
|
+
]);
|
|
28
|
+
const ticketsData = await ticketsRes.json();
|
|
29
|
+
const skillsData = await skillsRes.json();
|
|
30
|
+
|
|
31
|
+
setStats({
|
|
32
|
+
ticketsCount: ticketsData.total || 0,
|
|
33
|
+
sessionsCount: 0,
|
|
34
|
+
skillsCount: (skillsData.skills || []).length,
|
|
35
|
+
});
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.error('Failed to load stats:', error);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
loadStats();
|
|
41
|
+
}
|
|
42
|
+
}, []);
|
|
43
|
+
|
|
44
|
+
const handleLogout = () => {
|
|
45
|
+
localStorage.removeItem('userId');
|
|
46
|
+
localStorage.removeItem('assistant-session-id');
|
|
47
|
+
window.location.href = '/login';
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
if (!userInfo) {
|
|
51
|
+
return (
|
|
52
|
+
<div className="flex-1 flex items-center justify-center p-4">
|
|
53
|
+
<div className="text-center">
|
|
54
|
+
<p className="text-muted-foreground mb-4">请先登录</p>
|
|
55
|
+
<Button onClick={() => window.location.href = '/login'}>去登录</Button>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const menuItems = [
|
|
62
|
+
{ icon: Bot, label: '我的技能', href: '/my/skills', badge: stats.skillsCount },
|
|
63
|
+
{ icon: BookOpen, label: '知识库', href: '/my/knowledge', badge: null },
|
|
64
|
+
{ icon: Clock, label: '定时任务', href: '/my/scheduled-tasks', badge: null },
|
|
65
|
+
{ icon: Settings, label: '设置', href: '/my', badge: null },
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<div className="flex-1 overflow-y-auto p-4">
|
|
70
|
+
{/* 用户信息卡片 */}
|
|
71
|
+
<div className="bg-card border rounded-xl p-4 mb-4">
|
|
72
|
+
<div className="flex items-center gap-3">
|
|
73
|
+
<div className="w-14 h-14 rounded-full bg-primary/10 flex items-center justify-center">
|
|
74
|
+
<User className="h-8 w-8 text-primary" />
|
|
75
|
+
</div>
|
|
76
|
+
<div>
|
|
77
|
+
<p className="font-medium">用户</p>
|
|
78
|
+
<p className="text-xs text-muted-foreground truncate max-w-[200px]">{userInfo.userId}</p>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
{/* 统计信息 */}
|
|
83
|
+
<div className="grid grid-cols-3 gap-2 mt-4 pt-4 border-t">
|
|
84
|
+
<div className="text-center">
|
|
85
|
+
<p className="text-xl font-semibold">{stats.ticketsCount}</p>
|
|
86
|
+
<p className="text-xs text-muted-foreground">工单</p>
|
|
87
|
+
</div>
|
|
88
|
+
<div className="text-center">
|
|
89
|
+
<p className="text-xl font-semibold">{stats.sessionsCount}</p>
|
|
90
|
+
<p className="text-xs text-muted-foreground">会话</p>
|
|
91
|
+
</div>
|
|
92
|
+
<div className="text-center">
|
|
93
|
+
<p className="text-xl font-semibold">{stats.skillsCount}</p>
|
|
94
|
+
<p className="text-xs text-muted-foreground">技能</p>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
{/* 菜单列表 */}
|
|
100
|
+
<div className="space-y-1">
|
|
101
|
+
{menuItems.map((item) => (
|
|
102
|
+
<button
|
|
103
|
+
key={item.label}
|
|
104
|
+
onClick={() => {
|
|
105
|
+
window.location.href = item.href + '?desktop=true';
|
|
106
|
+
}}
|
|
107
|
+
className="w-full flex items-center justify-between p-3 bg-card border rounded-lg hover:bg-muted/50"
|
|
108
|
+
>
|
|
109
|
+
<div className="flex items-center gap-3">
|
|
110
|
+
<item.icon className="h-5 w-5 text-muted-foreground" />
|
|
111
|
+
<span>{item.label}</span>
|
|
112
|
+
</div>
|
|
113
|
+
<div className="flex items-center gap-2">
|
|
114
|
+
{item.badge !== null && item.badge > 0 && (
|
|
115
|
+
<span className="bg-primary/10 text-primary text-xs px-2 py-0.5 rounded-full">
|
|
116
|
+
{item.badge}
|
|
117
|
+
</span>
|
|
118
|
+
)}
|
|
119
|
+
<ChevronRight className="h-4 w-4 text-muted-foreground" />
|
|
120
|
+
</div>
|
|
121
|
+
</button>
|
|
122
|
+
))}
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
{/* 桌面版入口 */}
|
|
126
|
+
<div className="mt-4 pt-4 border-t">
|
|
127
|
+
<button
|
|
128
|
+
onClick={() => {
|
|
129
|
+
window.location.href = '/?desktop=true';
|
|
130
|
+
}}
|
|
131
|
+
className="w-full flex items-center justify-center gap-2 p-3 bg-muted/50 rounded-lg hover:bg-muted"
|
|
132
|
+
>
|
|
133
|
+
<ChevronRight className="h-4 w-4 rotate-180" />
|
|
134
|
+
<span className="text-sm">切换到桌面版</span>
|
|
135
|
+
</button>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
{/* 退出登录 */}
|
|
139
|
+
<div className="mt-4">
|
|
140
|
+
<Button variant="outline" className="w-full text-red-600 border-red-200 hover:bg-red-50" onClick={handleLogout}>
|
|
141
|
+
<LogOut className="h-4 w-4 mr-2" />
|
|
142
|
+
退出登录
|
|
143
|
+
</Button>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from 'react';
|
|
4
|
+
import { ClipboardList } from 'lucide-react';
|
|
5
|
+
import { Button } from '@/components/ui/button';
|
|
6
|
+
import { Badge } from '@/components/ui/badge';
|
|
7
|
+
|
|
8
|
+
interface Ticket {
|
|
9
|
+
id: string;
|
|
10
|
+
title: string;
|
|
11
|
+
type: string;
|
|
12
|
+
status: string;
|
|
13
|
+
riskLevel: string;
|
|
14
|
+
createdAt: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default function MobileTicketsPage() {
|
|
18
|
+
const [tickets, setTickets] = useState<Ticket[]>([]);
|
|
19
|
+
const [loading, setLoading] = useState(true);
|
|
20
|
+
const [selectedTicket, setSelectedTicket] = useState<Ticket | null>(null);
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
async function load() {
|
|
24
|
+
try {
|
|
25
|
+
const response = await fetch('/api/tickets?limit=50');
|
|
26
|
+
if (!response.ok) throw new Error('Failed to load');
|
|
27
|
+
const data = await response.json();
|
|
28
|
+
setTickets(Array.isArray(data.tickets) ? data.tickets.slice(0, 50) : []);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
console.error('Failed to load tickets:', error);
|
|
31
|
+
setTickets([]);
|
|
32
|
+
} finally {
|
|
33
|
+
setLoading(false);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
load();
|
|
37
|
+
}, []);
|
|
38
|
+
|
|
39
|
+
const getStatusBadge = (status: string) => {
|
|
40
|
+
const config: Record<string, { label: string; className: string }> = {
|
|
41
|
+
draft: { label: '草稿', className: 'bg-gray-100 text-gray-800' },
|
|
42
|
+
pending: { label: '待审核', className: 'bg-yellow-100 text-yellow-800' },
|
|
43
|
+
approved: { label: '已批准', className: 'bg-green-100 text-green-800' },
|
|
44
|
+
rejected: { label: '已拒绝', className: 'bg-red-100 text-red-800' },
|
|
45
|
+
executing: { label: '执行中', className: 'bg-blue-100 text-blue-800' },
|
|
46
|
+
completed: { label: '已完成', className: 'bg-green-100 text-green-800' },
|
|
47
|
+
failed: { label: '失败', className: 'bg-red-100 text-red-800' },
|
|
48
|
+
};
|
|
49
|
+
const { label, className } = config[status] || config.draft;
|
|
50
|
+
return <span className={`px-2 py-0.5 rounded-full text-xs ${className}`}>{label}</span>;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const getRiskBadge = (risk: string) => {
|
|
54
|
+
const config: Record<string, { label: string; className: string }> = {
|
|
55
|
+
low: { label: '低', className: 'bg-green-100 text-green-800' },
|
|
56
|
+
medium: { label: '中', className: 'bg-yellow-100 text-yellow-800' },
|
|
57
|
+
high: { label: '高', className: 'bg-orange-100 text-orange-800' },
|
|
58
|
+
critical: { label: '极高', className: 'bg-red-100 text-red-800' },
|
|
59
|
+
};
|
|
60
|
+
const { label, className } = config[risk] || config.medium;
|
|
61
|
+
return <span className={`px-1.5 py-0.5 rounded text-xs ${className}`}>{label}</span>;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
if (loading) {
|
|
65
|
+
return <div className="flex-1 flex items-center justify-center">加载中...</div>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (selectedTicket) {
|
|
69
|
+
return (
|
|
70
|
+
<div className="flex-1 overflow-y-auto p-4">
|
|
71
|
+
<div className="flex items-center gap-2 mb-4">
|
|
72
|
+
<Button variant="ghost" size="sm" onClick={() => setSelectedTicket(null)} className="-ml-2">
|
|
73
|
+
← 返回
|
|
74
|
+
</Button>
|
|
75
|
+
</div>
|
|
76
|
+
<h3 className="font-semibold text-lg mb-3">{selectedTicket.title}</h3>
|
|
77
|
+
<div className="space-y-3">
|
|
78
|
+
<div className="flex items-center gap-2">
|
|
79
|
+
{getStatusBadge(selectedTicket.status)}
|
|
80
|
+
{getRiskBadge(selectedTicket.riskLevel)}
|
|
81
|
+
</div>
|
|
82
|
+
<div className="text-sm text-muted-foreground">
|
|
83
|
+
类型: {selectedTicket.type === 'bash-execute' ? '命令执行' : 'Skill 脚本'}
|
|
84
|
+
</div>
|
|
85
|
+
<div className="text-xs text-muted-foreground">
|
|
86
|
+
创建时间: {new Date(selectedTicket.createdAt).toLocaleString('zh-CN')}
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<div className="flex-1 overflow-y-auto p-3">
|
|
95
|
+
{tickets.length === 0 ? (
|
|
96
|
+
<div className="text-center text-muted-foreground py-8">暂无工单</div>
|
|
97
|
+
) : (
|
|
98
|
+
<div className="space-y-2">
|
|
99
|
+
{tickets.map((ticket) => (
|
|
100
|
+
<div
|
|
101
|
+
key={ticket.id}
|
|
102
|
+
onClick={() => setSelectedTicket(ticket)}
|
|
103
|
+
className="p-3 bg-muted/30 hover:bg-muted rounded-lg cursor-pointer"
|
|
104
|
+
>
|
|
105
|
+
<div className="flex items-start justify-between mb-1">
|
|
106
|
+
<span className="font-medium text-sm flex-1">{ticket.title}</span>
|
|
107
|
+
{getStatusBadge(ticket.status)}
|
|
108
|
+
</div>
|
|
109
|
+
<div className="text-xs text-muted-foreground flex items-center gap-2">
|
|
110
|
+
<span>{ticket.type === 'bash-execute' ? '命令' : 'Skill'}</span>
|
|
111
|
+
{getRiskBadge(ticket.riskLevel)}
|
|
112
|
+
<span>·</span>
|
|
113
|
+
<span>{new Date(ticket.createdAt).toLocaleDateString('zh-CN')}</span>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
))}
|
|
117
|
+
</div>
|
|
118
|
+
)}
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
}
|
package/app/h5/page.tsx
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { MobileLayout } from '@/components/mobile-layout';
|
|
5
|
+
import MobileChatPage from './mobile-chat-page';
|
|
6
|
+
import MobileTicketsPage from './mobile-tickets-page';
|
|
7
|
+
import MobileApprovalsPage from './mobile-approvals-page';
|
|
8
|
+
import MobileProfilePage from './mobile-profile-page';
|
|
9
|
+
|
|
10
|
+
type Tab = 'chat' | 'tickets' | 'approvals' | 'profile';
|
|
11
|
+
|
|
12
|
+
export default function MobilePage() {
|
|
13
|
+
const [activeTab, setActiveTab] = useState<Tab>('chat');
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<MobileLayout activeTab={activeTab} onTabChange={setActiveTab}>
|
|
17
|
+
{activeTab === 'chat' && <MobileChatPage />}
|
|
18
|
+
{activeTab === 'tickets' && <MobileTicketsPage />}
|
|
19
|
+
{activeTab === 'approvals' && <MobileApprovalsPage />}
|
|
20
|
+
{activeTab === 'profile' && <MobileProfilePage />}
|
|
21
|
+
</MobileLayout>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from 'react';
|
|
4
|
+
import { Button } from '@/components/ui/button';
|
|
5
|
+
import { ClipboardList } from 'lucide-react';
|
|
6
|
+
|
|
7
|
+
export function TicketActionButtons({
|
|
8
|
+
ticketId,
|
|
9
|
+
ticketStatus,
|
|
10
|
+
onAction,
|
|
11
|
+
onViewDetails,
|
|
12
|
+
loadingTicket,
|
|
13
|
+
}: {
|
|
14
|
+
ticketId: string;
|
|
15
|
+
ticketStatus: string;
|
|
16
|
+
onAction: (ticketId: string, action: 'approve' | 'reject' | 'execute') => void;
|
|
17
|
+
onViewDetails: () => void;
|
|
18
|
+
loadingTicket: boolean;
|
|
19
|
+
}) {
|
|
20
|
+
const [currentStatus, setCurrentStatus] = useState(ticketStatus);
|
|
21
|
+
const [isOperating, setIsOperating] = useState(false);
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
async function fetchStatus() {
|
|
25
|
+
try {
|
|
26
|
+
const response = await fetch(`/api/tickets/${ticketId}`);
|
|
27
|
+
if (response.ok) {
|
|
28
|
+
const data = await response.json();
|
|
29
|
+
setCurrentStatus(data.ticket?.status || ticketStatus);
|
|
30
|
+
}
|
|
31
|
+
} catch (e) {}
|
|
32
|
+
}
|
|
33
|
+
fetchStatus();
|
|
34
|
+
}, [ticketId, ticketStatus]);
|
|
35
|
+
|
|
36
|
+
const handleAction = async (action: 'approve' | 'reject' | 'execute') => {
|
|
37
|
+
setIsOperating(true);
|
|
38
|
+
if (action === 'approve') setCurrentStatus('approved');
|
|
39
|
+
else if (action === 'reject') setCurrentStatus('rejected');
|
|
40
|
+
else if (action === 'execute') setCurrentStatus('executing');
|
|
41
|
+
try {
|
|
42
|
+
await onAction(ticketId, action);
|
|
43
|
+
} finally {
|
|
44
|
+
setIsOperating(false);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<div className="flex items-center gap-1 flex-wrap">
|
|
50
|
+
<Button variant="ghost" size="sm" onClick={onViewDetails} disabled={loadingTicket} className="h-6 px-2 text-xs">
|
|
51
|
+
<ClipboardList className="h-3 w-3 mr-1" />
|
|
52
|
+
详情
|
|
53
|
+
</Button>
|
|
54
|
+
{currentStatus === 'pending' && (
|
|
55
|
+
<>
|
|
56
|
+
<Button variant="default" size="sm" onClick={() => handleAction('approve')} disabled={isOperating} className="h-6 px-2 text-xs bg-green-600 hover:bg-green-700">
|
|
57
|
+
批准
|
|
58
|
+
</Button>
|
|
59
|
+
<Button variant="destructive" size="sm" onClick={() => handleAction('reject')} disabled={isOperating} className="h-6 px-2 text-xs">
|
|
60
|
+
拒绝
|
|
61
|
+
</Button>
|
|
62
|
+
</>
|
|
63
|
+
)}
|
|
64
|
+
{currentStatus === 'approved' && (
|
|
65
|
+
<Button variant="default" size="sm" onClick={() => handleAction('execute')} disabled={isOperating} className="h-6 px-2 text-xs bg-blue-600 hover:bg-blue-700">
|
|
66
|
+
执行
|
|
67
|
+
</Button>
|
|
68
|
+
)}
|
|
69
|
+
{currentStatus === 'executing' && (
|
|
70
|
+
<span className="text-xs text-blue-600 flex items-center gap-1 px-2">
|
|
71
|
+
<span className="w-1.5 h-1.5 rounded-full bg-blue-600 animate-pulse"></span>
|
|
72
|
+
执行中...
|
|
73
|
+
</span>
|
|
74
|
+
)}
|
|
75
|
+
{currentStatus === 'completed' && <span className="text-xs text-green-600 px-2">已完成</span>}
|
|
76
|
+
{currentStatus === 'rejected' && <span className="text-xs text-red-600 px-2">已拒绝</span>}
|
|
77
|
+
{currentStatus === 'failed' && <span className="text-xs text-red-600 px-2">失败</span>}
|
|
78
|
+
</div>
|
|
79
|
+
);
|
|
80
|
+
}
|
package/app/layout.tsx
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Metadata } from 'next';
|
|
2
|
+
import './globals.css';
|
|
3
|
+
import 'streamdown/styles.css';
|
|
4
|
+
import { ClientLayout } from '@/components/client-layout';
|
|
5
|
+
|
|
6
|
+
export const metadata: Metadata = {
|
|
7
|
+
title: '智能工作助手',
|
|
8
|
+
description: '通用智能助手系统,支持命令执行、工单审批、定时任务等功能',
|
|
9
|
+
icons: {
|
|
10
|
+
icon: '/icon.svg',
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export default function RootLayout({
|
|
15
|
+
children,
|
|
16
|
+
}: Readonly<{
|
|
17
|
+
children: React.ReactNode;
|
|
18
|
+
}>) {
|
|
19
|
+
return (
|
|
20
|
+
<html lang="zh-CN">
|
|
21
|
+
<body>
|
|
22
|
+
<ClientLayout>{children}</ClientLayout>
|
|
23
|
+
</body>
|
|
24
|
+
</html>
|
|
25
|
+
);
|
|
26
|
+
}
|