feat: v5.0 全面进化 — 浏览器控制 + 训练数据导出 + Cron 定时任务
新增功能: 1. 🌐 浏览器控制(8 个工具) - 主进程 browser.ts: 基于隐藏 BrowserWindow 的浏览器引擎 - browser_open: 加载 URL,返回标题和最终 URL - browser_screenshot: 页面截图(base64 PNG) - browser_evaluate: 执行任意 JS,支持 DOM 操作 - browser_extract: 提取页面文本 + 链接(最多 50 条) - browser_click: CSS 选择器点击 - browser_type: 输入框文本输入 - browser_scroll: 上下滚动/顶部/底部 - browser_close: 关闭浏览器释放资源 - 应用退出时自动清理 (before-quit) - 工具总数:34 个 2. 📚 训练数据导出 - training-export.ts: 从 traces + messages 提取 SFT 格式训练数据 - 输出 JSONL 格式(instruction/input/output/tools/observations/metadata) - 设置面板一键导出,Electron 原生文件对话框选择保存位置 3. ⏰ Cron 定时任务 - cron-manager.ts: one-shot(指定时间)+ recurring(周期性) - 任务配置持久化到 settings - 设置面板添加/删除/启用/禁用 - 任务触发时自动填入聊天输入框 + toast 通知 - 应用启动时自动恢复所有已启用任务 新增文件:browser.ts, cron-manager.ts, training-export.ts 修改文件:ipc.ts, main.ts, settings-modal.ts, index.html, main.ts(渲染), tool-registry.ts
This commit is contained in:
@@ -151,6 +151,42 @@ export function initSettingsModal(): void {
|
||||
logSetting('检查间隔', `${ms / 60000} 分钟`);
|
||||
});
|
||||
|
||||
// ── v5.0 训练数据导出 ──
|
||||
document.querySelector('#btnExportTraining')!.addEventListener('click', async () => {
|
||||
const { saveTrainingData } = await import('../services/training-export.js');
|
||||
showToast('正在导出训练数据...', 'info');
|
||||
const ok = await saveTrainingData();
|
||||
if (ok) showToast('训练数据导出成功', 'success');
|
||||
else showToast('导出取消或无数据', 'warning');
|
||||
});
|
||||
|
||||
// ── v5.0 Cron 定时任务 ──
|
||||
document.querySelector('#btnAddCronTask')!.addEventListener('click', async () => {
|
||||
const name = (document.querySelector('#inputCronName') as HTMLInputElement).value.trim();
|
||||
const type = (document.querySelector('#selectCronType') as HTMLSelectElement).value as 'oneshot' | 'recurring';
|
||||
const schedule = (document.querySelector('#inputCronSchedule') as HTMLInputElement).value.trim();
|
||||
const message = (document.querySelector('#inputCronMessage') as HTMLInputElement).value.trim();
|
||||
|
||||
if (!name || !schedule || !message) {
|
||||
showToast('请填写任务名称、时间和描述', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
const { addCronTask } = await import('../services/cron-manager.js');
|
||||
await addCronTask({ name, type, schedule, message, enabled: true });
|
||||
|
||||
// 清空输入框
|
||||
(document.querySelector('#inputCronName') as HTMLInputElement).value = '';
|
||||
(document.querySelector('#inputCronSchedule') as HTMLInputElement).value = '';
|
||||
(document.querySelector('#inputCronMessage') as HTMLInputElement).value = '';
|
||||
|
||||
showToast(`定时任务已添加: ${name}`, 'success');
|
||||
renderCronTaskList();
|
||||
});
|
||||
|
||||
// 初始化 cron 列表
|
||||
renderCronTaskList();
|
||||
|
||||
document.querySelector('#btnReleaseVRAM')!.addEventListener('click', async () => {
|
||||
const api = state.get<OllamaAPI>(KEYS.API);
|
||||
const model = (document.querySelector('#modelSelect') as HTMLSelectElement).value;
|
||||
@@ -582,3 +618,39 @@ export function restoreHeartbeat(): void {
|
||||
if (select) select.value = String(interval);
|
||||
}
|
||||
}
|
||||
|
||||
/** 渲染定时任务列表 */
|
||||
async function renderCronTaskList(): Promise<void> {
|
||||
const container = document.querySelector('#cronTaskList');
|
||||
if (!container) return;
|
||||
|
||||
const { getCronTasks, removeCronTask, toggleCronTask } = await import('../services/cron-manager.js');
|
||||
const tasks = await getCronTasks();
|
||||
|
||||
if (tasks.length === 0) {
|
||||
container.innerHTML = '<p class="text-muted">无定时任务</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = tasks.map(t => `
|
||||
<div style="display:flex;align-items:center;gap:6px;padding:4px 0;border-bottom:1px solid var(--border-subtle);">
|
||||
<span>${t.enabled ? '✅' : '⏸️'}</span>
|
||||
<span style="flex:1;font-weight:500;">${t.name}</span>
|
||||
<span class="text-muted" style="font-size:11px;">${t.type === 'oneshot' ? '一次性' : '周期'}</span>
|
||||
<button class="btn btn-sm" onclick="window.__toggleCron('${t.id}')" style="padding:2px 6px;font-size:11px;">${t.enabled ? '禁用' : '启用'}</button>
|
||||
<button class="btn btn-sm btn-danger" onclick="window.__deleteCron('${t.id}')" style="padding:2px 6px;font-size:11px;">删除</button>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// 全局方法供内联 onclick 调用
|
||||
(window as any).__toggleCron = async (id: string) => {
|
||||
const { toggleCronTask } = await import('../services/cron-manager.js');
|
||||
await toggleCronTask(id);
|
||||
renderCronTaskList();
|
||||
};
|
||||
(window as any).__deleteCron = async (id: string) => {
|
||||
const { removeCronTask } = await import('../services/cron-manager.js');
|
||||
await removeCronTask(id);
|
||||
renderCronTaskList();
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user