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,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 定时任务详情 API - GET/PUT/DELETE
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
6
|
+
import { getScheduledTask, updateScheduledTask, deleteScheduledTask, createAuditLog } from '@/lib/db';
|
|
7
|
+
import { getNextRunTime, isValidCronExpression } from '@/lib/scheduler';
|
|
8
|
+
|
|
9
|
+
type RouteContext = {
|
|
10
|
+
params: Promise<{ id: string }>;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* GET /api/scheduled-tasks/[id] - 获取单个定时任务
|
|
15
|
+
*/
|
|
16
|
+
export async function GET(request: NextRequest, context: RouteContext) {
|
|
17
|
+
try {
|
|
18
|
+
const { id } = await context.params;
|
|
19
|
+
const task = await getScheduledTask(id);
|
|
20
|
+
|
|
21
|
+
if (!task) {
|
|
22
|
+
return NextResponse.json(
|
|
23
|
+
{ error: '定时任务不存在' },
|
|
24
|
+
{ status: 404 },
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return NextResponse.json({ task });
|
|
29
|
+
} catch (error) {
|
|
30
|
+
return NextResponse.json(
|
|
31
|
+
{ error: error instanceof Error ? error.message : '获取定时任务失败' },
|
|
32
|
+
{ status: 500 },
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* PUT /api/scheduled-tasks/[id] - 更新定时任务
|
|
39
|
+
*/
|
|
40
|
+
export async function PUT(request: NextRequest, context: RouteContext) {
|
|
41
|
+
try {
|
|
42
|
+
const { id } = await context.params;
|
|
43
|
+
const body = await request.json();
|
|
44
|
+
const {
|
|
45
|
+
title,
|
|
46
|
+
description,
|
|
47
|
+
command,
|
|
48
|
+
status,
|
|
49
|
+
scheduleType,
|
|
50
|
+
scheduleExpression,
|
|
51
|
+
updatedBy,
|
|
52
|
+
} = body;
|
|
53
|
+
|
|
54
|
+
const current = await getScheduledTask(id);
|
|
55
|
+
if (!current) {
|
|
56
|
+
return NextResponse.json(
|
|
57
|
+
{ error: '定时任务不存在' },
|
|
58
|
+
{ status: 404 },
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 验证必填字段
|
|
63
|
+
if (!title || !command || !scheduleType || !scheduleExpression) {
|
|
64
|
+
return NextResponse.json(
|
|
65
|
+
{ error: '缺少必填字段: title, command, scheduleType, scheduleExpression' },
|
|
66
|
+
{ status: 400 },
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const validScheduleTypes = ['cron', 'interval', 'once'];
|
|
71
|
+
if (!validScheduleTypes.includes(scheduleType)) {
|
|
72
|
+
return NextResponse.json(
|
|
73
|
+
{ error: `无效的调度类型,必须是: ${validScheduleTypes.join(', ')}` },
|
|
74
|
+
{ status: 400 },
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const validStatuses = ['active', 'paused', 'disabled'];
|
|
79
|
+
if (status && !validStatuses.includes(status)) {
|
|
80
|
+
return NextResponse.json(
|
|
81
|
+
{ error: `无效的状态,必须是: ${validStatuses.join(', ')}` },
|
|
82
|
+
{ status: 400 },
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 验证 cron 表达式
|
|
87
|
+
if (scheduleType === 'cron' && !isValidCronExpression(scheduleExpression)) {
|
|
88
|
+
return NextResponse.json(
|
|
89
|
+
{ error: '无效的 cron 表达式' },
|
|
90
|
+
{ status: 400 },
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 如果调度表达式改变了,重新计算 nextRunAt
|
|
95
|
+
let nextRunAt = current.nextRunAt;
|
|
96
|
+
if (scheduleExpression !== current.scheduleExpression && scheduleType === 'cron') {
|
|
97
|
+
const calculated = getNextRunTime(scheduleExpression);
|
|
98
|
+
nextRunAt = calculated || undefined;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const updates: any = {
|
|
102
|
+
title,
|
|
103
|
+
description: description ?? '',
|
|
104
|
+
command,
|
|
105
|
+
scheduleType,
|
|
106
|
+
scheduleExpression,
|
|
107
|
+
nextRunAt,
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
if (status) {
|
|
111
|
+
updates.status = status;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const task = await updateScheduledTask(id, updates);
|
|
115
|
+
|
|
116
|
+
await createAuditLog({
|
|
117
|
+
action: 'scheduled_task_updated',
|
|
118
|
+
userId: updatedBy || 'system',
|
|
119
|
+
details: { taskId: id, updates },
|
|
120
|
+
status: 'success',
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
return NextResponse.json({ task });
|
|
124
|
+
} catch (error) {
|
|
125
|
+
return NextResponse.json(
|
|
126
|
+
{ error: error instanceof Error ? error.message : '更新定时任务失败' },
|
|
127
|
+
{ status: 500 },
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* DELETE /api/scheduled-tasks/[id] - 删除定时任务
|
|
134
|
+
*/
|
|
135
|
+
export async function DELETE(request: NextRequest, context: RouteContext) {
|
|
136
|
+
try {
|
|
137
|
+
const { id } = await context.params;
|
|
138
|
+
const { searchParams } = new URL(request.url);
|
|
139
|
+
const deletedBy = searchParams.get('deletedBy') || 'system';
|
|
140
|
+
|
|
141
|
+
const current = await getScheduledTask(id);
|
|
142
|
+
if (!current) {
|
|
143
|
+
return NextResponse.json(
|
|
144
|
+
{ error: '定时任务不存在' },
|
|
145
|
+
{ status: 404 },
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
await deleteScheduledTask(id);
|
|
150
|
+
|
|
151
|
+
await createAuditLog({
|
|
152
|
+
action: 'scheduled_task_deleted',
|
|
153
|
+
userId: deletedBy,
|
|
154
|
+
details: { taskId: id, title: current.title },
|
|
155
|
+
status: 'success',
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
return NextResponse.json({ success: true });
|
|
159
|
+
} catch (error) {
|
|
160
|
+
return NextResponse.json(
|
|
161
|
+
{ error: error instanceof Error ? error.message : '删除定时任务失败' },
|
|
162
|
+
{ status: 500 },
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 定时任务启用/禁用 API - POST
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
6
|
+
import { getScheduledTask, updateScheduledTask, createAuditLog } from '@/lib/db';
|
|
7
|
+
|
|
8
|
+
type RouteContext = {
|
|
9
|
+
params: Promise<{ id: string }>;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* POST /api/scheduled-tasks/[id]/toggle - 启用/禁用定时任务
|
|
14
|
+
*/
|
|
15
|
+
export async function POST(request: NextRequest, context: RouteContext) {
|
|
16
|
+
try {
|
|
17
|
+
const { id } = await context.params;
|
|
18
|
+
const body = await request.json();
|
|
19
|
+
const { userId } = body;
|
|
20
|
+
|
|
21
|
+
const current = await getScheduledTask(id);
|
|
22
|
+
if (!current) {
|
|
23
|
+
return NextResponse.json(
|
|
24
|
+
{ error: '定时任务不存在' },
|
|
25
|
+
{ status: 404 },
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// 切换状态:active <-> paused
|
|
30
|
+
const newStatus = current.status === 'active' ? 'paused' : 'active';
|
|
31
|
+
|
|
32
|
+
const task = await updateScheduledTask(id, { status: newStatus });
|
|
33
|
+
|
|
34
|
+
await createAuditLog({
|
|
35
|
+
action: 'scheduled_task_toggled',
|
|
36
|
+
userId: userId || 'system',
|
|
37
|
+
details: {
|
|
38
|
+
taskId: id,
|
|
39
|
+
title: current.title,
|
|
40
|
+
oldStatus: current.status,
|
|
41
|
+
newStatus,
|
|
42
|
+
},
|
|
43
|
+
status: 'success',
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
return NextResponse.json({ task });
|
|
47
|
+
} catch (error) {
|
|
48
|
+
return NextResponse.json(
|
|
49
|
+
{ error: error instanceof Error ? error.message : '切换定时任务状态失败' },
|
|
50
|
+
{ status: 500 },
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 定时任务 API - GET/POST
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
6
|
+
import { getScheduledTasks, createScheduledTask, createAuditLog } from '@/lib/db';
|
|
7
|
+
import { getNextRunTime, isValidCronExpression } from '@/lib/scheduler';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* GET /api/scheduled-tasks - 获取定时任务列表
|
|
11
|
+
*/
|
|
12
|
+
export async function GET(request: NextRequest) {
|
|
13
|
+
try {
|
|
14
|
+
const { searchParams } = new URL(request.url);
|
|
15
|
+
const filters = {
|
|
16
|
+
status: searchParams.get('status') ?? undefined,
|
|
17
|
+
createdBy: searchParams.get('createdBy') ?? undefined,
|
|
18
|
+
limit: searchParams.get('limit') ? parseInt(searchParams.get('limit')!) : undefined,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const tasks = await getScheduledTasks(filters);
|
|
22
|
+
|
|
23
|
+
return NextResponse.json({ tasks });
|
|
24
|
+
} catch (error) {
|
|
25
|
+
return NextResponse.json(
|
|
26
|
+
{ error: error instanceof Error ? error.message : '获取定时任务失败' },
|
|
27
|
+
{ status: 500 },
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* POST /api/scheduled-tasks - 创建新定时任务
|
|
34
|
+
*/
|
|
35
|
+
export async function POST(request: NextRequest) {
|
|
36
|
+
try {
|
|
37
|
+
const body = await request.json();
|
|
38
|
+
const {
|
|
39
|
+
title,
|
|
40
|
+
description,
|
|
41
|
+
command,
|
|
42
|
+
scheduleType,
|
|
43
|
+
scheduleExpression,
|
|
44
|
+
createdBy,
|
|
45
|
+
} = body;
|
|
46
|
+
|
|
47
|
+
// 验证必填字段
|
|
48
|
+
if (!title || !command || !scheduleType || !scheduleExpression || !createdBy) {
|
|
49
|
+
return NextResponse.json(
|
|
50
|
+
{ error: '缺少必填字段: title, command, scheduleType, scheduleExpression, createdBy' },
|
|
51
|
+
{ status: 400 },
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const validScheduleTypes = ['cron', 'interval', 'once'];
|
|
56
|
+
if (!validScheduleTypes.includes(scheduleType)) {
|
|
57
|
+
return NextResponse.json(
|
|
58
|
+
{ error: `无效的调度类型,必须是: ${validScheduleTypes.join(', ')}` },
|
|
59
|
+
{ status: 400 },
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 验证 cron 表达式
|
|
64
|
+
if (scheduleType === 'cron' && !isValidCronExpression(scheduleExpression)) {
|
|
65
|
+
return NextResponse.json(
|
|
66
|
+
{ error: '无效的 cron 表达式' },
|
|
67
|
+
{ status: 400 },
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 计算 nextRunAt
|
|
72
|
+
const nextRunAt = scheduleType === 'cron' ? getNextRunTime(scheduleExpression) : undefined;
|
|
73
|
+
|
|
74
|
+
const taskData = {
|
|
75
|
+
title,
|
|
76
|
+
description: description ?? '',
|
|
77
|
+
command,
|
|
78
|
+
status: 'active' as const,
|
|
79
|
+
scheduleType,
|
|
80
|
+
scheduleExpression,
|
|
81
|
+
nextRunAt: nextRunAt || undefined,
|
|
82
|
+
createdBy,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const task = await createScheduledTask(taskData);
|
|
86
|
+
|
|
87
|
+
await createAuditLog({
|
|
88
|
+
action: 'scheduled_task_created',
|
|
89
|
+
userId: createdBy,
|
|
90
|
+
details: { taskId: task.id, title, command, scheduleExpression },
|
|
91
|
+
status: 'success',
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
return NextResponse.json({ task }, { status: 201 });
|
|
95
|
+
} catch (error) {
|
|
96
|
+
return NextResponse.json(
|
|
97
|
+
{ error: error instanceof Error ? error.message : '创建定时任务失败' },
|
|
98
|
+
{ status: 500 },
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Messages API - GET
|
|
3
|
+
*
|
|
4
|
+
* Returns message history for a session from the JSONL file
|
|
5
|
+
* Includes chain-of-thought data (thinking and tool executions)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
9
|
+
import { existsSync, readFileSync } from 'fs';
|
|
10
|
+
import { join } from 'path';
|
|
11
|
+
|
|
12
|
+
const PI_SESSIONS_DIR = join(process.cwd(), 'data', 'pi-sessions');
|
|
13
|
+
|
|
14
|
+
interface ToolExecution {
|
|
15
|
+
toolName: string;
|
|
16
|
+
startTime: Date;
|
|
17
|
+
endTime?: Date;
|
|
18
|
+
input?: any;
|
|
19
|
+
output?: any;
|
|
20
|
+
isError?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface MessageChainOfThought {
|
|
24
|
+
thinking: string;
|
|
25
|
+
toolExecutions: ToolExecution[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface SessionMessageWithCoT {
|
|
29
|
+
id: string;
|
|
30
|
+
role: 'user' | 'assistant' | 'system';
|
|
31
|
+
content: string;
|
|
32
|
+
timestamp: string;
|
|
33
|
+
chainOfThought?: MessageChainOfThought;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface ToolCall {
|
|
37
|
+
id: string;
|
|
38
|
+
name: string;
|
|
39
|
+
arguments: {
|
|
40
|
+
command?: string;
|
|
41
|
+
[key: string]: any;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface ContentItem {
|
|
46
|
+
type: 'text' | 'toolCall' | 'toolResult';
|
|
47
|
+
text?: string;
|
|
48
|
+
id?: string;
|
|
49
|
+
name?: string;
|
|
50
|
+
arguments?: any;
|
|
51
|
+
toolCallId?: string;
|
|
52
|
+
isError?: boolean;
|
|
53
|
+
content?: ContentItem[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* GET /api/sessions/[id]/messages - Get session messages with chain-of-thought
|
|
58
|
+
*/
|
|
59
|
+
export async function GET(
|
|
60
|
+
request: NextRequest,
|
|
61
|
+
{ params }: { params: Promise<{ id: string }> }
|
|
62
|
+
) {
|
|
63
|
+
try {
|
|
64
|
+
const { id: sessionId } = await params;
|
|
65
|
+
|
|
66
|
+
const sessionFile = join(PI_SESSIONS_DIR, `${sessionId}.jsonl`);
|
|
67
|
+
|
|
68
|
+
if (!existsSync(sessionFile)) {
|
|
69
|
+
return NextResponse.json({ messages: [] });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const content = readFileSync(sessionFile, 'utf-8');
|
|
73
|
+
const lines = content.split('\n').filter(line => line.trim());
|
|
74
|
+
const messages: SessionMessageWithCoT[] = [];
|
|
75
|
+
|
|
76
|
+
// Track chain-of-thought data for assistant messages
|
|
77
|
+
let currentThinking = '';
|
|
78
|
+
let currentToolCalls: ToolCall[] = [];
|
|
79
|
+
let currentToolStartTime: Date | null = null;
|
|
80
|
+
let pendingAssistantMessage: SessionMessageWithCoT | null = null;
|
|
81
|
+
|
|
82
|
+
// Track tool executions by toolCallId to link with results later
|
|
83
|
+
const pendingToolExecutions = new Map<string, ToolExecution>();
|
|
84
|
+
|
|
85
|
+
for (const line of lines) {
|
|
86
|
+
try {
|
|
87
|
+
const data = JSON.parse(line);
|
|
88
|
+
|
|
89
|
+
switch (data.type) {
|
|
90
|
+
case 'message': {
|
|
91
|
+
const msg = data.message;
|
|
92
|
+
if (!msg) break;
|
|
93
|
+
|
|
94
|
+
// Handle toolResult messages
|
|
95
|
+
if (msg.role === 'toolResult') {
|
|
96
|
+
const toolCallId = msg.toolCallId;
|
|
97
|
+
if (toolCallId && pendingToolExecutions.has(toolCallId)) {
|
|
98
|
+
const toolExecution = pendingToolExecutions.get(toolCallId)!;
|
|
99
|
+
// Extract output from content
|
|
100
|
+
const textItems = msg.content?.filter((c: ContentItem) => c.type === 'text') || [];
|
|
101
|
+
const output = textItems.map((c: ContentItem) => c.text || '').join('');
|
|
102
|
+
|
|
103
|
+
toolExecution.output = output;
|
|
104
|
+
toolExecution.endTime = new Date(msg.timestamp || Date.now());
|
|
105
|
+
toolExecution.isError = msg.isError;
|
|
106
|
+
}
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Finalize previous assistant message before starting a new one
|
|
111
|
+
if (pendingAssistantMessage && (msg.role === 'user' || msg.role === 'assistant')) {
|
|
112
|
+
// Add chainOfThought to the pending assistant message
|
|
113
|
+
if (pendingToolExecutions.size > 0) {
|
|
114
|
+
pendingAssistantMessage.chainOfThought = {
|
|
115
|
+
thinking: currentThinking,
|
|
116
|
+
toolExecutions: Array.from(pendingToolExecutions.values())
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
messages.push(pendingAssistantMessage);
|
|
120
|
+
pendingAssistantMessage = null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Reset for new assistant message
|
|
124
|
+
if (msg.role === 'assistant') {
|
|
125
|
+
currentThinking = '';
|
|
126
|
+
currentToolCalls = [];
|
|
127
|
+
currentToolStartTime = new Date(data.timestamp || Date.now());
|
|
128
|
+
pendingToolExecutions.clear();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (msg.content && Array.isArray(msg.content)) {
|
|
132
|
+
// Extract text content
|
|
133
|
+
const textItems = msg.content.filter((c: ContentItem) => c.type === 'text');
|
|
134
|
+
const text = textItems.map((c: ContentItem) => c.text || '').join('');
|
|
135
|
+
|
|
136
|
+
// Extract tool calls from assistant messages
|
|
137
|
+
const toolCalls = msg.content.filter((c: ContentItem) => c.type === 'toolCall');
|
|
138
|
+
if (toolCalls.length > 0) {
|
|
139
|
+
for (const tc of toolCalls) {
|
|
140
|
+
const toolCallId = tc.id || '';
|
|
141
|
+
const toolExecution: ToolExecution = {
|
|
142
|
+
toolName: tc.name || 'unknown',
|
|
143
|
+
startTime: new Date(data.timestamp || Date.now()),
|
|
144
|
+
input: tc.arguments || {}
|
|
145
|
+
};
|
|
146
|
+
pendingToolExecutions.set(toolCallId, toolExecution);
|
|
147
|
+
currentToolCalls.push({
|
|
148
|
+
id: toolCallId,
|
|
149
|
+
name: tc.name || 'unknown',
|
|
150
|
+
arguments: tc.arguments || {}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Only create messages for user and assistant roles
|
|
156
|
+
if (msg.role === 'user' || msg.role === 'assistant') {
|
|
157
|
+
const messageIndex = messages.filter(m => m.role === 'user' || m.role === 'assistant').length;
|
|
158
|
+
const messageId = `${sessionId}-${messageIndex}`;
|
|
159
|
+
|
|
160
|
+
if (msg.role === 'user') {
|
|
161
|
+
// User messages are added immediately
|
|
162
|
+
messages.push({
|
|
163
|
+
id: messageId,
|
|
164
|
+
role: msg.role,
|
|
165
|
+
content: text,
|
|
166
|
+
timestamp: data.timestamp || new Date().toISOString()
|
|
167
|
+
});
|
|
168
|
+
} else {
|
|
169
|
+
// Assistant messages are held pending until tool results are processed
|
|
170
|
+
pendingAssistantMessage = {
|
|
171
|
+
id: messageId,
|
|
172
|
+
role: msg.role,
|
|
173
|
+
content: text,
|
|
174
|
+
timestamp: data.timestamp || new Date().toISOString()
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
case 'thinking':
|
|
183
|
+
if (data.content) {
|
|
184
|
+
currentThinking += data.content;
|
|
185
|
+
}
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
} catch (e) {
|
|
189
|
+
// Skip parse errors
|
|
190
|
+
console.error('Error parsing line:', e);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Finalize any pending assistant message at the end
|
|
195
|
+
if (pendingAssistantMessage) {
|
|
196
|
+
if (pendingToolExecutions.size > 0) {
|
|
197
|
+
pendingAssistantMessage.chainOfThought = {
|
|
198
|
+
thinking: currentThinking,
|
|
199
|
+
toolExecutions: Array.from(pendingToolExecutions.values())
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
messages.push(pendingAssistantMessage);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return NextResponse.json({ messages });
|
|
206
|
+
} catch (error) {
|
|
207
|
+
return NextResponse.json(
|
|
208
|
+
{ error: error instanceof Error ? error.message : '获取会话消息失败' },
|
|
209
|
+
{ status: 500 }
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sessions API - GET, DELETE
|
|
3
|
+
*
|
|
4
|
+
* Lists all available sessions for switching, and delete sessions
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
8
|
+
import { getAllSessionMetadata, cleanupSession, migrateSessionsFromFiles } from '@/lib/pi-session';
|
|
9
|
+
import { existsSync, unlinkSync } from 'fs';
|
|
10
|
+
import { join } from 'path';
|
|
11
|
+
|
|
12
|
+
// PI sessions directory (same as in pi-session.ts)
|
|
13
|
+
const PI_SESSIONS_DIR = join(process.cwd(), 'data', 'pi-sessions');
|
|
14
|
+
const SESSION_DIR = join(process.cwd(), 'data', 'sessions');
|
|
15
|
+
|
|
16
|
+
// 启动时迁移历史会话数据
|
|
17
|
+
migrateSessionsFromFiles();
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* GET /api/sessions - List all sessions
|
|
21
|
+
*/
|
|
22
|
+
export async function GET(request: NextRequest) {
|
|
23
|
+
try {
|
|
24
|
+
const { searchParams } = new URL(request.url);
|
|
25
|
+
const userId = searchParams.get('userId');
|
|
26
|
+
|
|
27
|
+
let sessions = getAllSessionMetadata();
|
|
28
|
+
|
|
29
|
+
// Filter by userId if provided
|
|
30
|
+
if (userId) {
|
|
31
|
+
sessions = sessions.filter(s => s.userId === userId);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return NextResponse.json({
|
|
35
|
+
sessions: sessions.map((s) => ({
|
|
36
|
+
sessionId: s.sessionId,
|
|
37
|
+
userId: s.userId,
|
|
38
|
+
createdAt: s.createdAt,
|
|
39
|
+
lastAccessedAt: s.lastAccessedAt,
|
|
40
|
+
messageCount: s.messageCount,
|
|
41
|
+
title: s.title,
|
|
42
|
+
})),
|
|
43
|
+
count: sessions.length,
|
|
44
|
+
});
|
|
45
|
+
} catch (error) {
|
|
46
|
+
return NextResponse.json(
|
|
47
|
+
{ error: error instanceof Error ? error.message : '获取会话列表失败' },
|
|
48
|
+
{ status: 500 },
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* DELETE /api/sessions - Delete a session
|
|
55
|
+
*
|
|
56
|
+
* Query params:
|
|
57
|
+
* - sessionId: The session ID to delete
|
|
58
|
+
*/
|
|
59
|
+
export async function DELETE(request: NextRequest) {
|
|
60
|
+
try {
|
|
61
|
+
const { searchParams } = new URL(request.url);
|
|
62
|
+
const sessionId = searchParams.get('sessionId');
|
|
63
|
+
|
|
64
|
+
if (!sessionId) {
|
|
65
|
+
return NextResponse.json(
|
|
66
|
+
{ error: 'sessionId is required' },
|
|
67
|
+
{ status: 400 },
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Clean up in-memory session
|
|
72
|
+
cleanupSession(sessionId);
|
|
73
|
+
|
|
74
|
+
// Delete PI session file (JSONL)
|
|
75
|
+
const piSessionFile = join(PI_SESSIONS_DIR, `${sessionId}.jsonl`);
|
|
76
|
+
if (existsSync(piSessionFile)) {
|
|
77
|
+
try {
|
|
78
|
+
unlinkSync(piSessionFile);
|
|
79
|
+
} catch (e) {
|
|
80
|
+
console.error('Failed to delete PI session file:', e);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Delete metadata file (JSON) - this is done by cleanupSession but double-check
|
|
85
|
+
const metadataFile = join(SESSION_DIR, `${sessionId}.json`);
|
|
86
|
+
if (existsSync(metadataFile)) {
|
|
87
|
+
try {
|
|
88
|
+
unlinkSync(metadataFile);
|
|
89
|
+
} catch (e) {
|
|
90
|
+
console.error('Failed to delete metadata file:', e);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return NextResponse.json({ success: true, sessionId });
|
|
95
|
+
} catch (error) {
|
|
96
|
+
return NextResponse.json(
|
|
97
|
+
{ error: error instanceof Error ? error.message : '删除会话失败' },
|
|
98
|
+
{ status: 500 },
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { getDocument } from '@/lib/db';
|
|
3
|
+
|
|
4
|
+
export async function GET(
|
|
5
|
+
request: NextRequest,
|
|
6
|
+
{ params }: { params: Promise<{ id: string }> }
|
|
7
|
+
) {
|
|
8
|
+
try {
|
|
9
|
+
const { id } = await params;
|
|
10
|
+
|
|
11
|
+
const document = getDocument(id);
|
|
12
|
+
if (!document) {
|
|
13
|
+
return NextResponse.json({ error: '文件未找到' }, { status: 404 });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (!document.isPublic) {
|
|
17
|
+
return NextResponse.json({ error: '此文件未公开分享' }, { status: 403 });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (document.type !== 'file') {
|
|
21
|
+
return NextResponse.json({ error: '不是文件类型' }, { status: 400 });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return NextResponse.json({
|
|
25
|
+
file: {
|
|
26
|
+
id: document.id,
|
|
27
|
+
title: document.title,
|
|
28
|
+
fileMetadata: document.fileMetadata,
|
|
29
|
+
isPublic: document.isPublic,
|
|
30
|
+
createdAt: document.createdAt.toISOString(),
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error('[api/share/file] Error:', error);
|
|
35
|
+
return NextResponse.json({ error: '服务器错误' }, { status: 500 });
|
|
36
|
+
}
|
|
37
|
+
}
|