v0.11.5: 搜索抓取增强 + 流式进度修复 + 超时/写入容错
feat: 搜索面板新增认证类型 Bearer/Basic,自动抓取条数/类型(顺序/随机) feat: 随机抓取失败自动从未抓取池补1条,上限移除 fix: write_file 缺 path 时返回引导性报错(含工作空间路径+平台示例) fix: Ollama tool_calls arguments JSON字符串解析 fix: 流式进度日志不再删除,改为完整时间线记录 fix: 进度日志同步appendChild确保排在工具日志之前 fix: 超时设置面板/主进程 remove max上限 chore: 版本号 0.11.4 → 0.11.5
This commit is contained in:
@@ -15,7 +15,7 @@ import {
|
||||
import { searchMemories, buildMemoryContext, markMemoryUsed, isMemoryEnabled } from './memory-manager.js';
|
||||
import { extractSkillsFromToolRecords, matchSkills, buildSkillContext } from './skill-manager.js';
|
||||
import { showToast } from '../components/toast.js';
|
||||
import { logInfo, logWarn, logSuccess, logError, logToolStart, logToolResult, logAgentLoop, logModelResponse, logStreamProgress } from './log-service.js';
|
||||
import { logInfo, logWarn, logSuccess, logError, logToolStart, logToolResult, logAgentLoop, logModelResponse, logStreamProgress, resetStreamProgress } from './log-service.js';
|
||||
import { getWorkspaceDirPath } from '../components/workspace-panel.js';
|
||||
import { generateId } from '../utils/utils.js';
|
||||
import { buildContext, estimateTokens, shouldAutoCompress, compressWithLLM, AUTO_COMPRESS_THRESHOLD, recordActualTokens } from './context-manager.js';
|
||||
@@ -720,6 +720,9 @@ Shell: ${osInfo.shell}
|
||||
let lastContentTime = streamStartTime; // 追踪最后一次收到新内容的时间
|
||||
const PROGRESS_INTERVAL = 15000; // 每15秒输出一次流式进度
|
||||
|
||||
// ── 同步创建本轮的进度条目(appendChild,保证在后续工具日志之前)──
|
||||
resetStreamProgress();
|
||||
|
||||
// ── 独立定时器:即使没有新 chunk 也会持续输出进度 ──
|
||||
progressTimer = setInterval(() => {
|
||||
const elapsed = Date.now() - streamStartTime;
|
||||
@@ -736,6 +739,9 @@ Shell: ${osInfo.shell}
|
||||
if (idleSec >= 10) {
|
||||
logStreamProgress(`⚠️ ${idleSec}s 无新内容,当前 ${content.length} 字`);
|
||||
}
|
||||
} else {
|
||||
// 模型尚未开始输出(可能在思考/加载模型/生成大参数)→ 显示等待状态
|
||||
logStreamProgress(`⏳ 等待模型响应… ${sec}s`);
|
||||
}
|
||||
}, PROGRESS_INTERVAL);
|
||||
|
||||
@@ -766,26 +772,42 @@ Shell: ${osInfo.shell}
|
||||
if (chunk.message?.tool_calls?.length) {
|
||||
for (const tc of chunk.message.tool_calls) {
|
||||
if (tc.function?.name) {
|
||||
// Ollama 可能返回 arguments 为 JSON 字符串,统一转为对象
|
||||
let parsedArgs: Record<string, unknown> = {};
|
||||
if (tc.function.arguments) {
|
||||
if (typeof tc.function.arguments === 'string') {
|
||||
try { parsedArgs = JSON.parse(tc.function.arguments); } catch { parsedArgs = {}; }
|
||||
} else if (typeof tc.function.arguments === 'object') {
|
||||
parsedArgs = tc.function.arguments as Record<string, unknown>;
|
||||
}
|
||||
}
|
||||
// 首次看到工具名 → 提前在工作空间创建"准备中"卡片
|
||||
// 此时参数还没生成完,但用户可以知道 AI 打算调哪个工具
|
||||
const isNewTool = !toolCalls.some(existing => existing.function.name === tc.function.name);
|
||||
if (isNewTool && callbacks.onToolCallPrepare) {
|
||||
callbacks.onToolCallPrepare({
|
||||
type: 'function',
|
||||
function: { name: tc.function.name, arguments: tc.function.arguments || {} }
|
||||
function: { name: tc.function.name, arguments: parsedArgs }
|
||||
});
|
||||
}
|
||||
toolCalls.push({
|
||||
type: 'function',
|
||||
function: {
|
||||
name: tc.function.name,
|
||||
arguments: tc.function.arguments || {}
|
||||
arguments: parsedArgs
|
||||
}
|
||||
});
|
||||
} else if (toolCalls.length > 0) {
|
||||
const last = toolCalls[toolCalls.length - 1];
|
||||
if (tc.function?.arguments && typeof tc.function.arguments === 'object') {
|
||||
Object.assign(last.function.arguments, tc.function.arguments);
|
||||
if (tc.function?.arguments) {
|
||||
let chunkArgs: Record<string, unknown> | null = null;
|
||||
if (typeof tc.function.arguments === 'string') {
|
||||
try { chunkArgs = JSON.parse(tc.function.arguments); } catch { /* ignore */ }
|
||||
} else if (typeof tc.function.arguments === 'object') {
|
||||
chunkArgs = tc.function.arguments as Record<string, unknown>;
|
||||
}
|
||||
if (chunkArgs) {
|
||||
Object.assign(last.function.arguments, chunkArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,25 +176,23 @@ export function logThink(thinking: string): void {
|
||||
|
||||
export function logStream(msg: string): void { addLog('stream', msg); }
|
||||
|
||||
/** 更新流式进度日志(原地更新,不追加新条目) */
|
||||
let _progressCreated = false;
|
||||
/** 本轮流式开始:同步创建新进度条目到日志底部(不删旧,日志就是完整记录) */
|
||||
export function resetStreamProgress(): void {
|
||||
if (!logBodyEl) return;
|
||||
const entry = document.createElement('div');
|
||||
entry.className = 'log-entry log-stream log-stream-progress';
|
||||
entry.innerHTML = `<span class="log-time">${formatTime(Date.now())}</span><span class="log-icon">📡</span><span class="log-msg">⏳ 等待模型响应… 0s</span>`;
|
||||
logBodyEl.appendChild(entry);
|
||||
}
|
||||
|
||||
/** 更新**最新**一条流式进度日志(原地更新消息,不改变 DOM 位置和时间戳) */
|
||||
export function logStreamProgress(msg: string): void {
|
||||
if (logBodyEl) {
|
||||
const existing = document.getElementById('log-stream-progress');
|
||||
if (existing) {
|
||||
// 已在 DOM 中 → 原地更新
|
||||
const timeSpan = existing.querySelector('.log-time');
|
||||
if (timeSpan) timeSpan.textContent = formatTime(Date.now());
|
||||
const msgSpan = existing.querySelector('.log-msg');
|
||||
if (msgSpan) msgSpan.textContent = msg;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// DOM 中不存在,且还没创建过 → addLog(customId 确保渲染后 ID 正确)
|
||||
// _progressCreated 防止 addLog 在未渲染前被重复调用
|
||||
if (!_progressCreated) {
|
||||
_progressCreated = true;
|
||||
addLog('stream', msg, undefined, undefined, 'log-stream-progress');
|
||||
// 找最后一条进度日志(当前轮)
|
||||
const all = logBodyEl?.querySelectorAll?.('.log-stream-progress');
|
||||
if (all && all.length > 0) {
|
||||
const latest = all[all.length - 1] as HTMLElement;
|
||||
const msgSpan = latest.querySelector('.log-msg');
|
||||
if (msgSpan) msgSpan.textContent = msg;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,12 +33,12 @@ export const TOOL_DEFINITIONS: ToolDefinition[] = [
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'write_file',
|
||||
description: 'Write content to a local file. Creates parent directories automatically. Supports text (utf-8/latin1) and binary (base64) modes. Use mode="append" to add to existing file instead of overwriting. Max 10MB (overwrite) or 5MB (append). Returns file stats including overwrite info.',
|
||||
description: 'Write content to a local file. Creates parent directories automatically. The "path" parameter is REQUIRED — always specify a file path (relative to workspace or absolute). For example: path="output.txt" writes to workspace. Supports text (utf-8/latin1) and binary (base64). Use mode="append" to add to existing file. Max 10MB overwrite / 5MB append.',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
required: ['path', 'content'],
|
||||
properties: {
|
||||
path: { type: 'string', description: 'The file path to write to. Absolute or relative to workspace.' },
|
||||
path: { type: 'string', description: 'REQUIRED — File path. Relative paths use workspace as base. E.g. "output.txt", "docs/report.md", or absolute "/home/user/file.txt".' },
|
||||
content: { type: 'string', description: 'The content to write. For binary files, pass base64-encoded string with encoding="base64".' },
|
||||
encoding: { type: 'string', enum: ['utf-8', 'latin1', 'base64'], description: 'File encoding. Use "base64" to write binary content (images, PDFs, etc). Default: utf-8.' },
|
||||
mode: { type: 'string', enum: ['overwrite', 'append'], description: 'Write mode. overwrite = replace file, append = add to end. Default: overwrite.' }
|
||||
@@ -345,7 +345,7 @@ export const TOOL_DEFINITIONS: ToolDefinition[] = [
|
||||
max_results: { type: 'integer', description: 'Maximum number of results to return. Default: 30, max: 30.' },
|
||||
time_range: { type: 'string', enum: ['day', 'week', 'month', 'year'], description: 'Filter results by time. Supported by Bing and Google. Use for recent news, latest version, etc.' },
|
||||
enhance_snippets: { type: 'boolean', description: 'Auto-fetch detailed snippet for results with too-short descriptions. Default: true.' },
|
||||
fetch_top: { type: 'integer', description: 'Number of top results to auto-fetch full content for. Min: 3, max: 10. Results shorter than this are useless — snippets lack detail.' }
|
||||
fetch_top: { type: 'integer', description: 'Number of top results to auto-fetch full content for. Min: 3. Results shorter than this are useless — snippets lack detail.' }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user