```
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:
@@ -10,7 +10,9 @@
|
||||
"id": "task-1710000000001",
|
||||
"city": "南京市",
|
||||
"prompt": "使用scrapling技能的fetch来爬取https://njggzy.nanjing.gov.cn/njweb/gchw/goods.html里房建市政、工程货物、交通水务、政府采购、产权交易、土地矿产、铁路航运和农村产权8个板块的招标公告列表的当天的的招标公告,获取项目名称 和项目金额(可能为合同预估价/最高投标限价等等),如果当天没有公告,默认获取最新的1条数据,不要试图写代码去正则匹配金额,因为金额的表述很多样,优先获取到详情md文件(或者html)后,使用`takeMoney`工具进行获取提取金额。\n输出结果为json,json结构如下\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": {
|
||||
|
||||
@@ -397,13 +397,14 @@
|
||||
<tr>
|
||||
<th>城市</th>
|
||||
<th>提示词</th>
|
||||
<th>模型</th>
|
||||
<th>状态</th>
|
||||
<th>浏览器</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="tasksTbody">
|
||||
<tr><td colspan="5" class="empty-state">加载中...</td></tr>
|
||||
<tr><td colspan="6" class="empty-state">加载中...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -529,6 +530,19 @@
|
||||
<label>提示词 (在提示词中包含目标网址和抓取要求)</label>
|
||||
<textarea id="taskPrompt" rows="6" placeholder="请访问 https://xxx.com 获取今天的所有招标公告和中标公告信息..." required></textarea>
|
||||
</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">
|
||||
<label>
|
||||
<input type="checkbox" id="taskEnabled" checked> 启用
|
||||
@@ -581,7 +595,7 @@
|
||||
document.getElementById('taskSummary').textContent = `共 ${tasksList.length} 个任务,${enabled} 个启用`;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -589,6 +603,7 @@
|
||||
<tr>
|
||||
<td><strong>${t.city || '-'}</strong></td>
|
||||
<td class="prompt-cell" title="${(t.prompt || '').replace(/"/g, '"')}">${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.useBrowser === true ? 'tag-on' : (t.useBrowser === false ? 'tag-off' : '')}">${t.useBrowser === true ? '打开' : (t.useBrowser === false ? '关闭' : '继承全局')}</span></td>
|
||||
<td>
|
||||
@@ -608,6 +623,15 @@
|
||||
document.getElementById('taskEditId').value = item ? item.id : '';
|
||||
document.getElementById('taskCity').value = item ? item.city : '';
|
||||
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('taskUseBrowser').checked = item
|
||||
? (typeof item.useBrowser === 'boolean' ? item.useBrowser : globalUseBrowser)
|
||||
@@ -625,6 +649,7 @@
|
||||
const data = {
|
||||
city: document.getElementById('taskCity').value.trim(),
|
||||
prompt: document.getElementById('taskPrompt').value.trim(),
|
||||
mode: document.getElementById('taskMode').value.trim() || 'qwen3.5-plus',
|
||||
enabled: document.getElementById('taskEnabled').checked,
|
||||
useBrowser: document.getElementById('taskUseBrowser').checked,
|
||||
};
|
||||
|
||||
@@ -8,6 +8,13 @@ const DEFAULT_POLL_INTERVAL = 3000; // 3秒轮询
|
||||
const DEFAULT_TIMEOUT = 3600000; // 1小时超时
|
||||
const FETCH_TIMEOUT = 30000; // 单次 fetch 30秒超时
|
||||
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() {
|
||||
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 = {}) {
|
||||
const baseUrl = options.baseUrl || DEFAULT_BASE_URL;
|
||||
const useBrowser = options.useBrowser ?? false;
|
||||
const mode = normalizeMode(options.mode);
|
||||
const taskId = generateTaskId();
|
||||
const logPrefix = options.logPrefix || '[Agent]';
|
||||
|
||||
const res = await fetchWithRetry(`${baseUrl}/agent/createTask`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ taskId, prompt, useBrowser }),
|
||||
body: JSON.stringify({ taskId, prompt, useBrowser, mode }),
|
||||
}, MAX_FETCH_RETRIES, logPrefix);
|
||||
|
||||
if (!res.ok) {
|
||||
@@ -97,12 +105,14 @@ async function checkTask(taskId, options = {}) {
|
||||
export async function runAgentTask(prompt, options = {}) {
|
||||
const baseUrl = options.baseUrl || DEFAULT_BASE_URL;
|
||||
const useBrowser = options.useBrowser ?? false;
|
||||
const mode = normalizeMode(options.mode);
|
||||
const pollInterval = options.pollInterval || DEFAULT_POLL_INTERVAL;
|
||||
const timeout = options.timeout || DEFAULT_TIMEOUT;
|
||||
const logPrefix = options.logPrefix || '[Agent]';
|
||||
|
||||
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}`);
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
@@ -10,6 +10,12 @@ const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
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() {
|
||||
try {
|
||||
@@ -47,10 +53,13 @@ function appendResult(result) {
|
||||
|
||||
async function runTask(task, agentCfg) {
|
||||
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}:mode=${mode}`);
|
||||
const { results } = await runAgentTask(task.prompt, {
|
||||
baseUrl: agentCfg.baseUrl,
|
||||
useBrowser,
|
||||
mode,
|
||||
pollInterval: agentCfg.pollInterval,
|
||||
timeout: agentCfg.timeout,
|
||||
logPrefix: `[定时任务][Agent][${task.city}]`,
|
||||
|
||||
@@ -18,6 +18,7 @@ const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const CONFIG_PATH = join(__dirname, '..', 'config.json');
|
||||
const RESULTS_PATH = join(__dirname, '..', 'results.json');
|
||||
const DEFAULT_TASK_MODE = 'qwen3.5-plus';
|
||||
|
||||
function readConfig() {
|
||||
return JSON.parse(readFileSync(CONFIG_PATH, 'utf-8'));
|
||||
@@ -31,6 +32,11 @@ function normalizeUseBrowser(value) {
|
||||
return value === true || value === 'true';
|
||||
}
|
||||
|
||||
function normalizeTaskMode(value) {
|
||||
if (typeof value === 'string' && value.trim()) return value.trim();
|
||||
return DEFAULT_TASK_MODE;
|
||||
}
|
||||
|
||||
// ========== 抓取结果存取 ==========
|
||||
|
||||
function readResults() {
|
||||
@@ -119,7 +125,8 @@ app.get('/api/results/filters', (req, res) => {
|
||||
app.get('/api/tasks', (req, res) => {
|
||||
try {
|
||||
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) {
|
||||
res.status(500).json({ success: false, error: e.message });
|
||||
}
|
||||
@@ -135,6 +142,7 @@ app.post('/api/tasks', (req, res) => {
|
||||
prompt: req.body.prompt || '',
|
||||
enabled: req.body.enabled !== false,
|
||||
useBrowser: normalizeUseBrowser(req.body.useBrowser),
|
||||
mode: normalizeTaskMode(req.body.mode),
|
||||
};
|
||||
cfg.tasks.push(item);
|
||||
saveConfig(cfg);
|
||||
@@ -153,6 +161,9 @@ app.put('/api/tasks/:id', (req, res) => {
|
||||
if (Object.prototype.hasOwnProperty.call(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 };
|
||||
saveConfig(cfg);
|
||||
res.json({ success: true, data: cfg.tasks[idx] });
|
||||
@@ -183,11 +194,14 @@ async function runTask(task) {
|
||||
const cfg = readConfig();
|
||||
const agentCfg = cfg.agent || {};
|
||||
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}:mode=${mode}`);
|
||||
const { results } = await runAgentTask(task.prompt, {
|
||||
baseUrl: agentCfg.baseUrl,
|
||||
useBrowser,
|
||||
mode,
|
||||
pollInterval: agentCfg.pollInterval,
|
||||
timeout: agentCfg.timeout,
|
||||
logPrefix: `[Agent][${task.city}]`,
|
||||
|
||||
Reference in New Issue
Block a user