feat(config): 添加任务配置中的模型模式支持

- 在config.json中为任务添加mode和useBrowser字段
- 默认使用glm-5模型模式

feat(ui): 更新前端界面显示模型信息并添加模型选择功能

- 在任务表格中添加模型列显示
- 在新增/编辑任务表单中添加模型选择下拉框
- 支持多种模型选项包括qwen3.5-plus、qwen3-max等
- 更新表格列数以适应新增的模型列

feat(core): 实现任务模型模式的功能支持

- 在agentService.js中添加normalizeMode函数处理模型模式
- 修改createTask和runAgentTask函数支持mode参数
- 在scheduler.js中实现任务的模型模式配置
- 在server.js中添加模型模式的标准化和API支持
- 为任务运行时添加模型模式的日志输出
```
This commit is contained in:
2026-03-10 18:11:11 +08:00
parent b9270428db
commit 40118ec508
5 changed files with 66 additions and 6 deletions

View File

@@ -10,7 +10,9 @@
"id": "task-1710000000001", "id": "task-1710000000001",
"city": "南京市", "city": "南京市",
"prompt": "使用scrapling技能的fetch来爬取https://njggzy.nanjing.gov.cn/njweb/gchw/goods.html里房建市政、工程货物、交通水务、政府采购、产权交易、土地矿产、铁路航运和农村产权8个板块的招标公告列表的当天的的招标公告获取项目名称 和项目金额(可能为合同预估价/最高投标限价等等如果当天没有公告默认获取最新的1条数据不要试图写代码去正则匹配金额因为金额的表述很多样优先获取到详情md文件或者html使用`takeMoney`工具进行获取提取金额。\n输出结果为jsonjson结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link: \"http...\"\n\t }\n ]\n}\n```", "prompt": "使用scrapling技能的fetch来爬取https://njggzy.nanjing.gov.cn/njweb/gchw/goods.html里房建市政、工程货物、交通水务、政府采购、产权交易、土地矿产、铁路航运和农村产权8个板块的招标公告列表的当天的的招标公告获取项目名称 和项目金额(可能为合同预估价/最高投标限价等等如果当天没有公告默认获取最新的1条数据不要试图写代码去正则匹配金额因为金额的表述很多样优先获取到详情md文件或者html使用`takeMoney`工具进行获取提取金额。\n输出结果为jsonjson结构如下\n```\n{\n \"results\": [\n\n {\n \"type\": \"比如房建市政\",\n\t\t\"project_name\": \"\",\n\t\t\"amount_yuan\": 0,\n\t\t\"date\": \"yyyy-MM-dd\",\n\t\t\"target_link: \"http...\"\n\t }\n ]\n}\n```",
"enabled": true "enabled": true,
"mode": "glm-5",
"useBrowser": false
} }
], ],
"scheduler": { "scheduler": {

View File

@@ -397,13 +397,14 @@
<tr> <tr>
<th>城市</th> <th>城市</th>
<th>提示词</th> <th>提示词</th>
<th>模型</th>
<th>状态</th> <th>状态</th>
<th>浏览器</th> <th>浏览器</th>
<th>操作</th> <th>操作</th>
</tr> </tr>
</thead> </thead>
<tbody id="tasksTbody"> <tbody id="tasksTbody">
<tr><td colspan="5" class="empty-state">加载中...</td></tr> <tr><td colspan="6" class="empty-state">加载中...</td></tr>
</tbody> </tbody>
</table> </table>
@@ -529,6 +530,19 @@
<label>提示词 (在提示词中包含目标网址和抓取要求)</label> <label>提示词 (在提示词中包含目标网址和抓取要求)</label>
<textarea id="taskPrompt" rows="6" placeholder="请访问 https://xxx.com 获取今天的所有招标公告和中标公告信息..." required></textarea> <textarea id="taskPrompt" rows="6" placeholder="请访问 https://xxx.com 获取今天的所有招标公告和中标公告信息..." required></textarea>
</div> </div>
<div class="form-group">
<label>模型 (mode)</label>
<select id="taskMode">
<option value="qwen3.5-plus">qwen3.5-plus</option>
<option value="qwen3-max-2026-01-23">qwen3-max-2026-01-23</option>
<option value="qwen3-coder-next">qwen3-coder-next</option>
<option value="qwen3-coder-plus">qwen3-coder-plus</option>
<option value="glm-5">glm-5</option>
<option value="glm-4.7">glm-4.7</option>
<option value="kimi-k2.5">kimi-k2.5</option>
<option value="MiniMax-M2.5">MiniMax-M2.5</option>
</select>
</div>
<div class="form-group"> <div class="form-group">
<label> <label>
<input type="checkbox" id="taskEnabled" checked> 启用 <input type="checkbox" id="taskEnabled" checked> 启用
@@ -581,7 +595,7 @@
document.getElementById('taskSummary').textContent = `${tasksList.length} 个任务,${enabled} 个启用`; document.getElementById('taskSummary').textContent = `${tasksList.length} 个任务,${enabled} 个启用`;
if (tasksList.length === 0) { if (tasksList.length === 0) {
tbody.innerHTML = '<tr><td colspan="5" class="empty-state">暂无任务,点击「新增任务」添加</td></tr>'; tbody.innerHTML = '<tr><td colspan="6" class="empty-state">暂无任务,点击「新增任务」添加</td></tr>';
return; return;
} }
@@ -589,6 +603,7 @@
<tr> <tr>
<td><strong>${t.city || '-'}</strong></td> <td><strong>${t.city || '-'}</strong></td>
<td class="prompt-cell" title="${(t.prompt || '').replace(/"/g, '&quot;')}">${t.prompt || '-'}</td> <td class="prompt-cell" title="${(t.prompt || '').replace(/"/g, '&quot;')}">${t.prompt || '-'}</td>
<td><code style="font-family:'Fira Code','Cascadia Code',monospace;font-size:12px;">${t.mode || 'qwen3.5-plus'}</code></td>
<td><span class="tag ${t.enabled ? 'tag-on' : 'tag-off'}">${t.enabled ? '启用' : '禁用'}</span></td> <td><span class="tag ${t.enabled ? 'tag-on' : 'tag-off'}">${t.enabled ? '启用' : '禁用'}</span></td>
<td><span class="tag ${t.useBrowser === true ? 'tag-on' : (t.useBrowser === false ? 'tag-off' : '')}">${t.useBrowser === true ? '打开' : (t.useBrowser === false ? '关闭' : '继承全局')}</span></td> <td><span class="tag ${t.useBrowser === true ? 'tag-on' : (t.useBrowser === false ? 'tag-off' : '')}">${t.useBrowser === true ? '打开' : (t.useBrowser === false ? '关闭' : '继承全局')}</span></td>
<td> <td>
@@ -608,6 +623,15 @@
document.getElementById('taskEditId').value = item ? item.id : ''; document.getElementById('taskEditId').value = item ? item.id : '';
document.getElementById('taskCity').value = item ? item.city : ''; document.getElementById('taskCity').value = item ? item.city : '';
document.getElementById('taskPrompt').value = item ? item.prompt : ''; document.getElementById('taskPrompt').value = item ? item.prompt : '';
const modeSelect = document.getElementById('taskMode');
const modeValue = item?.mode || 'qwen3.5-plus';
if (![...modeSelect.options].some(opt => opt.value === modeValue)) {
const option = document.createElement('option');
option.value = modeValue;
option.textContent = modeValue;
modeSelect.appendChild(option);
}
modeSelect.value = modeValue;
document.getElementById('taskEnabled').checked = item ? item.enabled : true; document.getElementById('taskEnabled').checked = item ? item.enabled : true;
document.getElementById('taskUseBrowser').checked = item document.getElementById('taskUseBrowser').checked = item
? (typeof item.useBrowser === 'boolean' ? item.useBrowser : globalUseBrowser) ? (typeof item.useBrowser === 'boolean' ? item.useBrowser : globalUseBrowser)
@@ -625,6 +649,7 @@
const data = { const data = {
city: document.getElementById('taskCity').value.trim(), city: document.getElementById('taskCity').value.trim(),
prompt: document.getElementById('taskPrompt').value.trim(), prompt: document.getElementById('taskPrompt').value.trim(),
mode: document.getElementById('taskMode').value.trim() || 'qwen3.5-plus',
enabled: document.getElementById('taskEnabled').checked, enabled: document.getElementById('taskEnabled').checked,
useBrowser: document.getElementById('taskUseBrowser').checked, useBrowser: document.getElementById('taskUseBrowser').checked,
}; };

View File

@@ -8,6 +8,13 @@ const DEFAULT_POLL_INTERVAL = 3000; // 3秒轮询
const DEFAULT_TIMEOUT = 3600000; // 1小时超时 const DEFAULT_TIMEOUT = 3600000; // 1小时超时
const FETCH_TIMEOUT = 30000; // 单次 fetch 30秒超时 const FETCH_TIMEOUT = 30000; // 单次 fetch 30秒超时
const MAX_FETCH_RETRIES = 5; // 网络错误最多重试5次 const MAX_FETCH_RETRIES = 5; // 网络错误最多重试5次
const DEFAULT_MODE = 'qwen3.5-plus';
function normalizeMode(value) {
if (typeof value === 'string' && value.trim()) return value.trim();
return DEFAULT_MODE;
}
function generateTaskId() { function generateTaskId() {
return `task-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`; return `task-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
@@ -43,13 +50,14 @@ async function fetchWithRetry(url, fetchOptions, retries = MAX_FETCH_RETRIES, lo
async function createTask(prompt, options = {}) { async function createTask(prompt, options = {}) {
const baseUrl = options.baseUrl || DEFAULT_BASE_URL; const baseUrl = options.baseUrl || DEFAULT_BASE_URL;
const useBrowser = options.useBrowser ?? false; const useBrowser = options.useBrowser ?? false;
const mode = normalizeMode(options.mode);
const taskId = generateTaskId(); const taskId = generateTaskId();
const logPrefix = options.logPrefix || '[Agent]'; const logPrefix = options.logPrefix || '[Agent]';
const res = await fetchWithRetry(`${baseUrl}/agent/createTask`, { const res = await fetchWithRetry(`${baseUrl}/agent/createTask`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ taskId, prompt, useBrowser }), body: JSON.stringify({ taskId, prompt, useBrowser, mode }),
}, MAX_FETCH_RETRIES, logPrefix); }, MAX_FETCH_RETRIES, logPrefix);
if (!res.ok) { if (!res.ok) {
@@ -97,12 +105,14 @@ async function checkTask(taskId, options = {}) {
export async function runAgentTask(prompt, options = {}) { export async function runAgentTask(prompt, options = {}) {
const baseUrl = options.baseUrl || DEFAULT_BASE_URL; const baseUrl = options.baseUrl || DEFAULT_BASE_URL;
const useBrowser = options.useBrowser ?? false; const useBrowser = options.useBrowser ?? false;
const mode = normalizeMode(options.mode);
const pollInterval = options.pollInterval || DEFAULT_POLL_INTERVAL; const pollInterval = options.pollInterval || DEFAULT_POLL_INTERVAL;
const timeout = options.timeout || DEFAULT_TIMEOUT; const timeout = options.timeout || DEFAULT_TIMEOUT;
const logPrefix = options.logPrefix || '[Agent]'; const logPrefix = options.logPrefix || '[Agent]';
console.log(`${logPrefix} 创建任务...`); console.log(`${logPrefix} 创建任务...`);
const { taskId } = await createTask(prompt, { baseUrl, useBrowser, logPrefix }); console.log(`${logPrefix} 使用 mode: ${mode}`);
const { taskId } = await createTask(prompt, { baseUrl, useBrowser, mode, logPrefix });
console.log(`${logPrefix} 任务已创建: ${taskId}`); console.log(`${logPrefix} 任务已创建: ${taskId}`);
const startTime = Date.now(); const startTime = Date.now();

View File

@@ -10,6 +10,12 @@ const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename); const __dirname = dirname(__filename);
const RESULTS_PATH = join(__dirname, '..', 'results.json'); const RESULTS_PATH = join(__dirname, '..', 'results.json');
const DEFAULT_TASK_MODE = 'qwen3.5-plus';
function normalizeTaskMode(value) {
if (typeof value === 'string' && value.trim()) return value.trim();
return DEFAULT_TASK_MODE;
}
function loadConfig() { function loadConfig() {
try { try {
@@ -47,10 +53,13 @@ function appendResult(result) {
async function runTask(task, agentCfg) { async function runTask(task, agentCfg) {
const useBrowser = typeof task.useBrowser === 'boolean' ? task.useBrowser : agentCfg.useBrowser; const useBrowser = typeof task.useBrowser === 'boolean' ? task.useBrowser : agentCfg.useBrowser;
const mode = normalizeTaskMode(task.mode);
console.log(`[定时任务][Agent] ${task.city}:开始执行`); console.log(`[定时任务][Agent] ${task.city}:开始执行`);
console.log(`[定时任务][Agent] ${task.city}mode=${mode}`);
const { results } = await runAgentTask(task.prompt, { const { results } = await runAgentTask(task.prompt, {
baseUrl: agentCfg.baseUrl, baseUrl: agentCfg.baseUrl,
useBrowser, useBrowser,
mode,
pollInterval: agentCfg.pollInterval, pollInterval: agentCfg.pollInterval,
timeout: agentCfg.timeout, timeout: agentCfg.timeout,
logPrefix: `[定时任务][Agent][${task.city}]`, logPrefix: `[定时任务][Agent][${task.city}]`,

View File

@@ -18,6 +18,7 @@ const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename); const __dirname = dirname(__filename);
const CONFIG_PATH = join(__dirname, '..', 'config.json'); const CONFIG_PATH = join(__dirname, '..', 'config.json');
const RESULTS_PATH = join(__dirname, '..', 'results.json'); const RESULTS_PATH = join(__dirname, '..', 'results.json');
const DEFAULT_TASK_MODE = 'qwen3.5-plus';
function readConfig() { function readConfig() {
return JSON.parse(readFileSync(CONFIG_PATH, 'utf-8')); return JSON.parse(readFileSync(CONFIG_PATH, 'utf-8'));
@@ -31,6 +32,11 @@ function normalizeUseBrowser(value) {
return value === true || value === 'true'; return value === true || value === 'true';
} }
function normalizeTaskMode(value) {
if (typeof value === 'string' && value.trim()) return value.trim();
return DEFAULT_TASK_MODE;
}
// ========== 抓取结果存取 ========== // ========== 抓取结果存取 ==========
function readResults() { function readResults() {
@@ -119,7 +125,8 @@ app.get('/api/results/filters', (req, res) => {
app.get('/api/tasks', (req, res) => { app.get('/api/tasks', (req, res) => {
try { try {
const cfg = readConfig(); const cfg = readConfig();
res.json({ success: true, data: cfg.tasks || [] }); const tasks = (cfg.tasks || []).map(t => ({ ...t, mode: normalizeTaskMode(t.mode) }));
res.json({ success: true, data: tasks });
} catch (e) { } catch (e) {
res.status(500).json({ success: false, error: e.message }); res.status(500).json({ success: false, error: e.message });
} }
@@ -135,6 +142,7 @@ app.post('/api/tasks', (req, res) => {
prompt: req.body.prompt || '', prompt: req.body.prompt || '',
enabled: req.body.enabled !== false, enabled: req.body.enabled !== false,
useBrowser: normalizeUseBrowser(req.body.useBrowser), useBrowser: normalizeUseBrowser(req.body.useBrowser),
mode: normalizeTaskMode(req.body.mode),
}; };
cfg.tasks.push(item); cfg.tasks.push(item);
saveConfig(cfg); saveConfig(cfg);
@@ -153,6 +161,9 @@ app.put('/api/tasks/:id', (req, res) => {
if (Object.prototype.hasOwnProperty.call(patch, 'useBrowser')) { if (Object.prototype.hasOwnProperty.call(patch, 'useBrowser')) {
patch.useBrowser = normalizeUseBrowser(patch.useBrowser); patch.useBrowser = normalizeUseBrowser(patch.useBrowser);
} }
if (Object.prototype.hasOwnProperty.call(patch, 'mode')) {
patch.mode = normalizeTaskMode(patch.mode);
}
cfg.tasks[idx] = { ...cfg.tasks[idx], ...patch, id: req.params.id }; cfg.tasks[idx] = { ...cfg.tasks[idx], ...patch, id: req.params.id };
saveConfig(cfg); saveConfig(cfg);
res.json({ success: true, data: cfg.tasks[idx] }); res.json({ success: true, data: cfg.tasks[idx] });
@@ -183,11 +194,14 @@ async function runTask(task) {
const cfg = readConfig(); const cfg = readConfig();
const agentCfg = cfg.agent || {}; const agentCfg = cfg.agent || {};
const useBrowser = typeof task.useBrowser === 'boolean' ? task.useBrowser : agentCfg.useBrowser; const useBrowser = typeof task.useBrowser === 'boolean' ? task.useBrowser : agentCfg.useBrowser;
const mode = normalizeTaskMode(task.mode);
console.log(`[Agent] ${task.city}:开始执行`); console.log(`[Agent] ${task.city}:开始执行`);
console.log(`[Agent] ${task.city}mode=${mode}`);
const { results } = await runAgentTask(task.prompt, { const { results } = await runAgentTask(task.prompt, {
baseUrl: agentCfg.baseUrl, baseUrl: agentCfg.baseUrl,
useBrowser, useBrowser,
mode,
pollInterval: agentCfg.pollInterval, pollInterval: agentCfg.pollInterval,
timeout: agentCfg.timeout, timeout: agentCfg.timeout,
logPrefix: `[Agent][${task.city}]`, logPrefix: `[Agent][${task.city}]`,