yiyan-browser-agent 1.5.1 → 1.5.3
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 +81 -0
- package/package.json +1 -1
- package/src/server.js +122 -27
- package/src/task-queue.js +296 -0
package/README.md
CHANGED
|
@@ -166,6 +166,26 @@ ya --debug "build a calculator"
|
|
|
166
166
|
|
|
167
167
|
When interactive mode (`-i`) is running, an HTTP server starts on port **9527**, allowing external services to send tasks.
|
|
168
168
|
|
|
169
|
+
### Task Queue (v1.5.2+)
|
|
170
|
+
|
|
171
|
+
**多客户端并发支持:** 所有请求进入队列串行处理,确保每个客户端收到正确的响应。
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
客户端A → 请求"北京天气" → 队列位置1 → 处理 → 返回"北京天气答案"
|
|
175
|
+
客户端B → 请求"上海天气" → 队列位置2 → 等待 → 处理 → 返回"上海天气答案"
|
|
176
|
+
客户端C → 请求"广州天气" → 队列位置3 → 等待 → 处理 → 返回"广州天气答案"
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### API Endpoints
|
|
180
|
+
|
|
181
|
+
| Endpoint | Method | Description |
|
|
182
|
+
|----------|--------|-------------|
|
|
183
|
+
| `/task` | POST | 提交任务 |
|
|
184
|
+
| `/status` | GET | 获取队列状态 |
|
|
185
|
+
| `/queue` | GET | 获取队列详情 |
|
|
186
|
+
| `/task/:id` | GET | 获取单个任务状态 |
|
|
187
|
+
| `/` | GET | 服务信息 |
|
|
188
|
+
|
|
169
189
|
### Process Communication
|
|
170
190
|
|
|
171
191
|
```bash
|
|
@@ -239,6 +259,67 @@ curl -X POST http://localhost:9527/task -H "Content-Type: application/json" -d '
|
|
|
239
259
|
- 第二次请求 (`newChat=false`):AI 回答"小明"
|
|
240
260
|
- 第三次请求 (`newChat=true`):AI 回答"不知道"或"你没有告诉我"
|
|
241
261
|
|
|
262
|
+
### GET /status - 队列状态查询
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
curl http://localhost:9527/status
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
**Response:**
|
|
269
|
+
```json
|
|
270
|
+
{
|
|
271
|
+
"queueLength": 2,
|
|
272
|
+
"isProcessing": true,
|
|
273
|
+
"currentTask": {
|
|
274
|
+
"id": "abc123",
|
|
275
|
+
"task": "北京天气",
|
|
276
|
+
"elapsed": 3000
|
|
277
|
+
},
|
|
278
|
+
"pendingTasks": [
|
|
279
|
+
{ "id": "def456", "task": "上海天气", "waitTime": 2000 },
|
|
280
|
+
{ "id": "ghi789", "task": "广州天气", "waitTime": 1000 }
|
|
281
|
+
],
|
|
282
|
+
"stats": {
|
|
283
|
+
"totalProcessed": 10,
|
|
284
|
+
"averageProcessTime": 5000
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### GET /queue - 队列详情
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
curl http://localhost:9527/queue
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### GET /task/:id - 单个任务状态
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
curl http://localhost:9527/task/abc123
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
**Response (processing):**
|
|
302
|
+
```json
|
|
303
|
+
{
|
|
304
|
+
"id": "abc123",
|
|
305
|
+
"task": "北京天气",
|
|
306
|
+
"status": "processing",
|
|
307
|
+
"startedAt": 1735001234,
|
|
308
|
+
"elapsed": 3000
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
**Response (pending):**
|
|
313
|
+
```json
|
|
314
|
+
{
|
|
315
|
+
"id": "def456",
|
|
316
|
+
"task": "上海天气",
|
|
317
|
+
"status": "pending",
|
|
318
|
+
"queuePosition": 2,
|
|
319
|
+
"waitTime": 5000
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
242
323
|
### Windows CMD (curl)
|
|
243
324
|
|
|
244
325
|
```cmd
|
package/package.json
CHANGED
package/src/server.js
CHANGED
|
@@ -6,6 +6,7 @@ const http = require('http');
|
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
const path = require('path');
|
|
8
8
|
const os = require('os');
|
|
9
|
+
const TaskQueue = require('./task-queue');
|
|
9
10
|
|
|
10
11
|
const DEFAULT_PORT = 9527;
|
|
11
12
|
const LOCK_FILE = path.join(os.homedir(), '.yiyan-agent', 'server.lock');
|
|
@@ -15,6 +16,11 @@ class AgentServer {
|
|
|
15
16
|
this.agent = agent;
|
|
16
17
|
this.port = port;
|
|
17
18
|
this.server = null;
|
|
19
|
+
|
|
20
|
+
// 创建任务队列,设置任务准备好时的回调
|
|
21
|
+
this.taskQueue = new TaskQueue((task) => {
|
|
22
|
+
this._executeTask(task);
|
|
23
|
+
});
|
|
18
24
|
}
|
|
19
25
|
|
|
20
26
|
async start() {
|
|
@@ -39,6 +45,9 @@ class AgentServer {
|
|
|
39
45
|
}
|
|
40
46
|
|
|
41
47
|
async stop() {
|
|
48
|
+
// 清空队列
|
|
49
|
+
this.taskQueue.clearQueue();
|
|
50
|
+
|
|
42
51
|
if (this.server) {
|
|
43
52
|
await new Promise((resolve) => {
|
|
44
53
|
this.server.close(resolve);
|
|
@@ -98,60 +107,146 @@ class AgentServer {
|
|
|
98
107
|
|
|
99
108
|
async _handleRequest(req, res) {
|
|
100
109
|
const logger = require('./logger');
|
|
110
|
+
const url = new URL(req.url, `http://localhost:${this.port}`);
|
|
111
|
+
|
|
112
|
+
// ── API 路由 ─────────────────────────────────────────────
|
|
113
|
+
|
|
114
|
+
// GET /status - 获取队列状态
|
|
115
|
+
if (req.method === 'GET' && url.pathname === '/status') {
|
|
116
|
+
this._handleStatus(req, res);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// GET /queue - 获取队列详情
|
|
121
|
+
if (req.method === 'GET' && url.pathname === '/queue') {
|
|
122
|
+
this._handleQueue(req, res);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// GET /task/:id - 获取单个任务状态
|
|
127
|
+
if (req.method === 'GET' && url.pathname.startsWith('/task/')) {
|
|
128
|
+
const requestId = url.pathname.slice(6);
|
|
129
|
+
this._handleTaskStatus(req, res, requestId);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// POST /task - 提交任务
|
|
134
|
+
if (req.method === 'POST' && url.pathname === '/task') {
|
|
135
|
+
this._handleTaskSubmit(req, res);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
101
138
|
|
|
102
|
-
//
|
|
103
|
-
if (req.method
|
|
104
|
-
res.writeHead(
|
|
105
|
-
res.end(JSON.stringify({
|
|
139
|
+
// GET / - 简单介绍
|
|
140
|
+
if (req.method === 'GET' && url.pathname === '/') {
|
|
141
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
142
|
+
res.end(JSON.stringify({
|
|
143
|
+
name: 'yiyan-agent',
|
|
144
|
+
version: '1.5.2',
|
|
145
|
+
endpoints: {
|
|
146
|
+
'POST /task': '提交任务',
|
|
147
|
+
'GET /status': '获取队列状态',
|
|
148
|
+
'GET /queue': '获取队列详情',
|
|
149
|
+
'GET /task/:id': '获取任务状态',
|
|
150
|
+
}
|
|
151
|
+
}));
|
|
106
152
|
return;
|
|
107
153
|
}
|
|
108
154
|
|
|
109
|
-
//
|
|
155
|
+
// 其他请求 - 404
|
|
156
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
157
|
+
res.end(JSON.stringify({ status: 'error', answer: 'Not found' }));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ── POST /task - 提交任务 ─────────────────────────────────────
|
|
161
|
+
_handleTaskSubmit(req, res) {
|
|
162
|
+
const logger = require('./logger');
|
|
110
163
|
let body = '';
|
|
164
|
+
|
|
111
165
|
req.on('data', chunk => { body += chunk.toString(); });
|
|
112
|
-
|
|
166
|
+
|
|
167
|
+
req.on('end', () => {
|
|
113
168
|
try {
|
|
114
169
|
const request = JSON.parse(body);
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
170
|
+
const { task, newChat = true } = request;
|
|
171
|
+
|
|
172
|
+
if (!task) {
|
|
173
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
174
|
+
res.end(JSON.stringify({ status: 'error', answer: 'No task provided' }));
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// 添加任务到队列,等待任务完成后自动调用 resolve 返回结果
|
|
179
|
+
this.taskQueue.addTask(request, (result) => {
|
|
180
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
181
|
+
res.end(JSON.stringify(result));
|
|
182
|
+
});
|
|
183
|
+
|
|
118
184
|
} catch (err) {
|
|
119
185
|
logger.error(`[HTTP Error] ${err.message}`);
|
|
120
186
|
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
121
187
|
res.end(JSON.stringify({ status: 'error', answer: `Bad request: ${err.message}` }));
|
|
122
188
|
}
|
|
123
189
|
});
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
async _executeTask(request) {
|
|
127
|
-
const { task, newChat = true } = request;
|
|
128
190
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
191
|
+
req.on('error', (err) => {
|
|
192
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
193
|
+
res.end(JSON.stringify({ status: 'error', answer: `Request error: ${err.message}` }));
|
|
194
|
+
});
|
|
195
|
+
}
|
|
132
196
|
|
|
197
|
+
// ── 执行任务(由队列回调触发)─────────────────────────────────────
|
|
198
|
+
async _executeTask(task) {
|
|
199
|
+
const { data } = task;
|
|
200
|
+
const { task: taskText, newChat = true } = data;
|
|
133
201
|
const logger = require('./logger');
|
|
134
|
-
|
|
202
|
+
|
|
203
|
+
logger.info(`[Remote Task] ${taskText}`);
|
|
135
204
|
|
|
136
205
|
try {
|
|
137
|
-
//
|
|
206
|
+
// 开启新对话
|
|
138
207
|
if (newChat) {
|
|
139
208
|
await this.agent.browser.newChat();
|
|
140
209
|
}
|
|
141
210
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
211
|
+
// 执行任务
|
|
212
|
+
const result = await this.agent.run(taskText);
|
|
213
|
+
|
|
214
|
+
// 完成任务(通知队列,触发 resolve)
|
|
215
|
+
this.taskQueue.completeCurrentTask(result);
|
|
216
|
+
|
|
145
217
|
} catch (err) {
|
|
146
218
|
logger.error(`[Remote Task Error] ${err.message}`);
|
|
147
|
-
|
|
148
|
-
question: task,
|
|
149
|
-
answer: `Error: ${err.message}`,
|
|
150
|
-
duration: 0,
|
|
151
|
-
status: 'error'
|
|
152
|
-
};
|
|
219
|
+
this.taskQueue.errorCurrentTask(err);
|
|
153
220
|
}
|
|
154
221
|
}
|
|
222
|
+
|
|
223
|
+
// ── GET /status - 获取队列状态 ─────────────────────────────────────
|
|
224
|
+
_handleStatus(req, res) {
|
|
225
|
+
const status = this.taskQueue.getQueueStatus();
|
|
226
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
227
|
+
res.end(JSON.stringify(status, null, 2));
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ── GET /queue - 获取队列详情 ─────────────────────────────────────
|
|
231
|
+
_handleQueue(req, res) {
|
|
232
|
+
const status = this.taskQueue.getQueueStatus();
|
|
233
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
234
|
+
res.end(JSON.stringify(status, null, 2));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ── GET /task/:id - 获取任务状态 ─────────────────────────────────────
|
|
238
|
+
_handleTaskStatus(req, res, requestId) {
|
|
239
|
+
const status = this.taskQueue.getTaskStatus(requestId);
|
|
240
|
+
|
|
241
|
+
if (!status) {
|
|
242
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
243
|
+
res.end(JSON.stringify({ status: 'error', answer: 'Task not found' }));
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
248
|
+
res.end(JSON.stringify(status, null, 2));
|
|
249
|
+
}
|
|
155
250
|
}
|
|
156
251
|
|
|
157
252
|
module.exports = { AgentServer, DEFAULT_PORT, LOCK_FILE };
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
// src/task-queue.js — Task queue for handling concurrent requests
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const logger = require('./logger');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* TaskQueue - 任务队列管理器
|
|
8
|
+
*
|
|
9
|
+
* 解决问题:
|
|
10
|
+
* 1. 多个客户端并发请求时,输入框竞争导致任务混淆
|
|
11
|
+
* 2. 响应返回给错误的客户端
|
|
12
|
+
*
|
|
13
|
+
* 解决方案:
|
|
14
|
+
* 1. 所有任务进入队列,串行处理
|
|
15
|
+
* 2. 每个任务绑定唯一ID,追踪响应归属
|
|
16
|
+
* 3. 提供状态查询API
|
|
17
|
+
*/
|
|
18
|
+
class TaskQueue {
|
|
19
|
+
constructor(onTaskReady = null) {
|
|
20
|
+
this.queue = []; // 待处理任务队列
|
|
21
|
+
this.currentTask = null; // 当前正在处理的任务
|
|
22
|
+
this.isProcessing = false; // 是否正在处理
|
|
23
|
+
this.completedTasks = []; // 已完成任务(最近10个)
|
|
24
|
+
this.onTaskReady = onTaskReady; // 任务准备好执行时的回调
|
|
25
|
+
this.stats = {
|
|
26
|
+
totalProcessed: 0,
|
|
27
|
+
averageProcessTime: 0,
|
|
28
|
+
totalProcessTime: 0,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 添加任务到队列
|
|
34
|
+
* @param {object} taskData - 任务数据 { task, newChat, provider }
|
|
35
|
+
* @param {function} resolve - Promise的resolve函数
|
|
36
|
+
* @returns {object} - { requestId, queuePosition, estimatedWait }
|
|
37
|
+
*/
|
|
38
|
+
addTask(taskData, resolve) {
|
|
39
|
+
const requestId = this._generateRequestId();
|
|
40
|
+
|
|
41
|
+
const task = {
|
|
42
|
+
id: requestId,
|
|
43
|
+
data: taskData,
|
|
44
|
+
resolve,
|
|
45
|
+
status: 'pending',
|
|
46
|
+
addedAt: Date.now(),
|
|
47
|
+
startedAt: null,
|
|
48
|
+
completedAt: null,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
this.queue.push(task);
|
|
52
|
+
|
|
53
|
+
const queuePosition = this.queue.length;
|
|
54
|
+
const estimatedWait = this._estimateWaitTime(queuePosition);
|
|
55
|
+
|
|
56
|
+
logger.info(`[Queue] 新任务 ${this._shortId(requestId)}: "${taskData.task.slice(0, 30)}..." (队列位置: ${queuePosition})`);
|
|
57
|
+
|
|
58
|
+
// 如果没有正在处理的任务,立即开始处理
|
|
59
|
+
if (!this.isProcessing) {
|
|
60
|
+
this._processNext();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
requestId,
|
|
65
|
+
queuePosition,
|
|
66
|
+
estimatedWait,
|
|
67
|
+
status: 'pending',
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 处理下一个任务
|
|
73
|
+
*/
|
|
74
|
+
_processNext() {
|
|
75
|
+
if (this.queue.length === 0) {
|
|
76
|
+
this.isProcessing = false;
|
|
77
|
+
this.currentTask = null;
|
|
78
|
+
logger.dim('[Queue] 队列已空,等待新任务...');
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
this.isProcessing = true;
|
|
83
|
+
this.currentTask = this.queue.shift();
|
|
84
|
+
this.currentTask.status = 'processing';
|
|
85
|
+
this.currentTask.startedAt = Date.now();
|
|
86
|
+
|
|
87
|
+
const waitTime = this.currentTask.startedAt - this.currentTask.addedAt;
|
|
88
|
+
logger.info(`[Queue] 开始处理 ${this._shortId(this.currentTask.id)}: "${this.currentTask.data.task.slice(0, 30)}..." (等待了 ${waitTime}ms)`);
|
|
89
|
+
|
|
90
|
+
// 调用外部执行回调
|
|
91
|
+
if (this.onTaskReady) {
|
|
92
|
+
this.onTaskReady(this.currentTask);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 完成当前任务(由外部调用,传入执行结果)
|
|
98
|
+
* @param {object} result - 执行结果
|
|
99
|
+
*/
|
|
100
|
+
completeCurrentTask(result) {
|
|
101
|
+
if (!this.currentTask) return;
|
|
102
|
+
|
|
103
|
+
this.currentTask.completedAt = Date.now();
|
|
104
|
+
this.currentTask.status = 'completed';
|
|
105
|
+
|
|
106
|
+
const processTime = this.currentTask.completedAt - this.currentTask.startedAt;
|
|
107
|
+
result.requestId = this.currentTask.id;
|
|
108
|
+
result.processTime = processTime;
|
|
109
|
+
result.waitTime = this.currentTask.startedAt - this.currentTask.addedAt;
|
|
110
|
+
|
|
111
|
+
// 更新统计
|
|
112
|
+
this._updateStats(processTime);
|
|
113
|
+
|
|
114
|
+
// 保存到已完成列表(保留最近10个)
|
|
115
|
+
this.completedTasks.unshift({
|
|
116
|
+
id: this.currentTask.id,
|
|
117
|
+
task: this.currentTask.data.task,
|
|
118
|
+
processTime,
|
|
119
|
+
status: result.status,
|
|
120
|
+
completedAt: this.currentTask.completedAt,
|
|
121
|
+
});
|
|
122
|
+
if (this.completedTasks.length > 10) {
|
|
123
|
+
this.completedTasks.pop();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 返回结果给客户端
|
|
127
|
+
this.currentTask.resolve(result);
|
|
128
|
+
|
|
129
|
+
logger.info(`[Queue] ✓ 完成 ${this._shortId(this.currentTask.id)} (${processTime}ms)`);
|
|
130
|
+
|
|
131
|
+
// 处理下一个
|
|
132
|
+
this.currentTask = null;
|
|
133
|
+
this._processNext();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* 当前任务出错
|
|
138
|
+
* @param {Error} err - 错误对象
|
|
139
|
+
*/
|
|
140
|
+
errorCurrentTask(err) {
|
|
141
|
+
if (!this.currentTask) return;
|
|
142
|
+
|
|
143
|
+
this.currentTask.completedAt = Date.now();
|
|
144
|
+
this.currentTask.status = 'error';
|
|
145
|
+
|
|
146
|
+
const result = {
|
|
147
|
+
question: this.currentTask.data.task,
|
|
148
|
+
answer: `Error: ${err.message}`,
|
|
149
|
+
status: 'error',
|
|
150
|
+
requestId: this.currentTask.id,
|
|
151
|
+
processTime: this.currentTask.completedAt - this.currentTask.startedAt,
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
this.currentTask.resolve(result);
|
|
155
|
+
|
|
156
|
+
logger.error(`[Queue] ✗ 错误 ${this._shortId(this.currentTask.id)}: ${err.message}`);
|
|
157
|
+
|
|
158
|
+
this.currentTask = null;
|
|
159
|
+
this._processNext();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* 获取任务状态
|
|
164
|
+
* @param {string} requestId - 任务ID
|
|
165
|
+
* @returns {object|null} - 任务状态
|
|
166
|
+
*/
|
|
167
|
+
getTaskStatus(requestId) {
|
|
168
|
+
// 检查当前任务
|
|
169
|
+
if (this.currentTask && this.currentTask.id === requestId) {
|
|
170
|
+
return {
|
|
171
|
+
id: requestId,
|
|
172
|
+
task: this.currentTask.data.task,
|
|
173
|
+
status: 'processing',
|
|
174
|
+
addedAt: this.currentTask.addedAt,
|
|
175
|
+
startedAt: this.currentTask.startedAt,
|
|
176
|
+
elapsed: Date.now() - this.currentTask.startedAt,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// 检查队列中的任务
|
|
181
|
+
const pending = this.queue.find(t => t.id === requestId);
|
|
182
|
+
if (pending) {
|
|
183
|
+
return {
|
|
184
|
+
id: requestId,
|
|
185
|
+
task: pending.data.task,
|
|
186
|
+
status: 'pending',
|
|
187
|
+
addedAt: pending.addedAt,
|
|
188
|
+
queuePosition: this.queue.indexOf(pending) + 1,
|
|
189
|
+
waitTime: Date.now() - pending.addedAt,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// 检查已完成任务
|
|
194
|
+
const completed = this.completedTasks.find(t => t.id === requestId);
|
|
195
|
+
if (completed) {
|
|
196
|
+
return {
|
|
197
|
+
id: requestId,
|
|
198
|
+
task: completed.task,
|
|
199
|
+
status: 'completed',
|
|
200
|
+
processTime: completed.processTime,
|
|
201
|
+
completedAt: completed.completedAt,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* 获取队列整体状态
|
|
210
|
+
* @returns {object} - 队列状态
|
|
211
|
+
*/
|
|
212
|
+
getQueueStatus() {
|
|
213
|
+
return {
|
|
214
|
+
queueLength: this.queue.length,
|
|
215
|
+
isProcessing: this.isProcessing,
|
|
216
|
+
currentTask: this.currentTask ? {
|
|
217
|
+
id: this.shortId(this.currentTask.id),
|
|
218
|
+
task: this.currentTask.data.task.slice(0, 50),
|
|
219
|
+
status: this.currentTask.status,
|
|
220
|
+
elapsed: Date.now() - this.currentTask.startedAt,
|
|
221
|
+
} : null,
|
|
222
|
+
pendingTasks: this.queue.map(t => ({
|
|
223
|
+
id: this.shortId(t.id),
|
|
224
|
+
task: t.data.task.slice(0, 50),
|
|
225
|
+
waitTime: Date.now() - t.addedAt,
|
|
226
|
+
})),
|
|
227
|
+
recentCompleted: this.completedTasks.slice(0, 5).map(t => ({
|
|
228
|
+
id: this.shortId(t.id),
|
|
229
|
+
task: t.task.slice(0, 50),
|
|
230
|
+
processTime: t.processTime,
|
|
231
|
+
status: t.status,
|
|
232
|
+
})),
|
|
233
|
+
stats: {
|
|
234
|
+
totalProcessed: this.stats.totalProcessed,
|
|
235
|
+
averageProcessTime: Math.round(this.stats.averageProcessTime),
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* 生成唯一请求ID
|
|
242
|
+
*/
|
|
243
|
+
_generateRequestId() {
|
|
244
|
+
return `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* 简短ID(用于显示)
|
|
249
|
+
*/
|
|
250
|
+
_shortId(id) {
|
|
251
|
+
if (!id) return 'unknown';
|
|
252
|
+
const parts = id.split('-');
|
|
253
|
+
return parts[1] || id.slice(-6);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
shortId(id) {
|
|
257
|
+
return this._shortId(id);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* 估算等待时间
|
|
262
|
+
*/
|
|
263
|
+
_estimateWaitTime(position) {
|
|
264
|
+
if (position === 0) return 0;
|
|
265
|
+
// 使用历史平均处理时间估算
|
|
266
|
+
const avgTime = this.stats.averageProcessTime || 5000;
|
|
267
|
+
return Math.round(avgTime * position);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* 更新统计信息
|
|
272
|
+
*/
|
|
273
|
+
_updateStats(processTime) {
|
|
274
|
+
this.stats.totalProcessed++;
|
|
275
|
+
this.stats.totalProcessTime += processTime;
|
|
276
|
+
this.stats.averageProcessTime = this.stats.totalProcessTime / this.stats.totalProcessed;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* 清空队列(紧急情况)
|
|
281
|
+
*/
|
|
282
|
+
clearQueue() {
|
|
283
|
+
// 通知所有等待的任务
|
|
284
|
+
for (const task of this.queue) {
|
|
285
|
+
task.resolve({
|
|
286
|
+
status: 'error',
|
|
287
|
+
answer: 'Queue cleared',
|
|
288
|
+
requestId: task.id,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
this.queue = [];
|
|
292
|
+
logger.warn('[Queue] 队列已清空');
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
module.exports = TaskQueue;
|