From 278d5aa25c8f80ab1daa37b0115bc217a419df12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=AC=E6=98=8E?= <83812544+Ed1s0nZ@users.noreply.github.com> Date: Mon, 11 May 2026 19:52:39 +0800 Subject: [PATCH] Add files via upload --- web/static/css/style.css | 124 +++++++++++++++++++++++++++++- web/static/i18n/en-US.json | 18 +++++ web/static/i18n/zh-CN.json | 18 +++++ web/static/js/chat.js | 150 +++++++++++++++++++++++++++++++++++++ web/static/js/monitor.js | 34 ++++++--- web/static/js/roles.js | 3 + web/static/js/settings.js | 32 +++++++- web/static/js/webshell.js | 34 ++++++--- web/templates/index.html | 79 +++++++++++++++++++ 9 files changed, 469 insertions(+), 23 deletions(-) diff --git a/web/static/css/style.css b/web/static/css/style.css index 4938163b..da1d7897 100644 --- a/web/static/css/style.css +++ b/web/static/css/style.css @@ -2391,7 +2391,118 @@ header { box-sizing: border-box; } -.chat-input-container > .chat-input-with-files { +.chat-input-primary-row { + display: flex; + flex-direction: row; + align-items: flex-end; + gap: 8px; + flex: 1; + min-width: 0; + width: 100%; +} + +.chat-input-leading { + display: flex; + flex-direction: row; + align-items: flex-end; + gap: 8px; + flex-shrink: 0; +} + +/* Eino:模型推理收进浮层,保持主输入行简洁 */ +.chat-reasoning-wrapper { + flex-shrink: 0; +} + +.chat-reasoning-inner { + position: relative; +} + +.chat-reasoning-btn { + max-width: 10.5rem; + padding-left: 0.5rem; + padding-right: 0.45rem; +} + +.chat-reasoning-btn .chat-reasoning-btn-icon { + flex-shrink: 0; + font-size: 0.95rem; + line-height: 1; + opacity: 0.95; +} + +.chat-reasoning-btn.active .chat-reasoning-btn-icon { + opacity: 1; +} + +.chat-reasoning-btn .chat-reasoning-btn-summary { + max-width: 7.6rem; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.chat-reasoning-btn.active { + border-color: rgba(49, 130, 206, 0.45); + background: rgba(49, 130, 206, 0.06); +} + +.chat-reasoning-panel { + position: absolute; + bottom: calc(100% + 8px); + left: 0; + width: 288px; + max-width: calc(100vw - 32px); + background: #ffffff; + border: 1px solid rgba(0, 0, 0, 0.08); + border-radius: 16px; + padding: 12px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12), 0 4px 16px rgba(0, 0, 0, 0.08), 0 0 0 1px rgba(0, 0, 0, 0.04); + z-index: 1000; + display: flex; + flex-direction: column; + gap: 10px; + text-align: left; +} + +.chat-reasoning-panel-header { + margin-bottom: 0; +} + +.chat-reasoning-panel-hint { + font-size: 0.75rem; + color: var(--text-muted, #718096); + margin: 0; + line-height: 1.45; +} + +.chat-reasoning-fields { + display: flex; + flex-direction: column; + gap: 12px; +} + +.chat-reasoning-field-label { + display: block; + font-size: 0.75rem; + font-weight: 600; + color: var(--text-muted, #718096); + margin-bottom: 6px; +} + +.chat-reasoning-select { + width: 100%; + box-sizing: border-box; + padding: 0.45rem 0.6rem; + font-size: 0.8125rem; + border: 1px solid var(--border-color, #e2e8f0); + border-radius: 8px; + background: var(--card-bg, #fff); + color: var(--text-color, #2d3748); +} + +.chat-input-container .chat-input-with-files, +.chat-input-primary-row .chat-input-with-files { flex: 1; display: flex; flex-direction: column; @@ -2399,7 +2510,8 @@ header { gap: 6px; } -.chat-input-container > .chat-input-field { +.chat-input-container > .chat-input-field, +.chat-input-primary-row .chat-input-field { flex: 1; display: flex; min-width: 0; @@ -3568,6 +3680,11 @@ header { background: rgba(156, 39, 176, 0.05); } +.timeline-item-reasoning_chain { + border-left-color: #5c6bc0; + background: rgba(92, 107, 192, 0.06); +} + .timeline-item-tool_call { border-left-color: #ff9800; background: rgba(255, 152, 0, 0.05); @@ -12294,6 +12411,9 @@ header { .webshell-ai-process-block .webshell-ai-timeline-thinking { border-left-color: #9c27b0; } +.webshell-ai-process-block .webshell-ai-timeline-reasoning_chain { + border-left-color: #5c6bc0; +} .webshell-ai-process-block .webshell-ai-timeline-tool_call, .webshell-ai-process-block .webshell-ai-timeline-tool_calls_detected { border-left-color: #ff9800; diff --git a/web/static/i18n/en-US.json b/web/static/i18n/en-US.json index 8661d3d4..7e9be4f1 100644 --- a/web/static/i18n/en-US.json +++ b/web/static/i18n/en-US.json @@ -277,6 +277,7 @@ "planExecuteStreamPhase": "Phase output", "einoSubAgentStep": "Sub-agent {{agent}} · step {{n}}", "aiThinking": "AI thinking", + "reasoningChain": "Reasoning process", "planning": "Planning", "assistantStreamPhase": "Assistant output", "toolCallsDetected": "Detected {{count}} tool call(s)", @@ -329,6 +330,19 @@ "agentModeMulti": "Multi-agent", "agentModeSingleHint": "Single-model ReAct loop for chat and tool use", "agentModeMultiHint": "Eino prebuilt orchestration (deep / plan_execute / supervisor) for complex tasks", + "reasoningModeLabel": "Model reasoning", + "reasoningEffortLabel": "Reasoning effort", + "reasoningModeDefault": "Use system default", + "reasoningModeOff": "Off", + "reasoningModeOn": "On", + "reasoningModeAuto": "Auto", + "reasoningEffortUnset": "Unspecified", + "reasoningCompactLabel": "Reasoning", + "reasoningCompactAria": "Open model reasoning options", + "reasoningPanelTitle": "Model reasoning", + "reasoningPanelHint": "Only Eino single- and multi-agent requests use these; merged with defaults in Settings.", + "reasoningSummaryFollow": "System", + "reasoningSummaryDash": "—", "agentModeOrchPlanExecute": "Plan-Exec", "agentModeOrchSupervisor": "Supervisor", "hitlTitle": "Human-in-the-loop", @@ -1592,6 +1606,10 @@ "maxTotalTokens": "Max Context Tokens", "maxTotalTokensPlaceholder": "120000", "maxTotalTokensHint": "Shared by memory compression and attack chain building. Default: 120000", + "openaiReasoningTitle": "Model reasoning (Eino)", + "openaiReasoningHint": "Applies to Eino single-agent and multi-agent only; works with chat-page reasoning controls.", + "openaiReasoningProfile": "Wire profile", + "openaiReasoningAllowClient": "Allow chat page to override reasoning options", "fofaBaseUrlPlaceholder": "https://fofa.info/api/v1/search/all (optional)", "fofaBaseUrlHint": "Leave empty for default.", "email": "Email", diff --git a/web/static/i18n/zh-CN.json b/web/static/i18n/zh-CN.json index a6a3b4f4..ff74901f 100644 --- a/web/static/i18n/zh-CN.json +++ b/web/static/i18n/zh-CN.json @@ -266,6 +266,7 @@ "planExecuteStreamPhase": "阶段输出", "einoSubAgentStep": "子代理 {{agent}} · 第 {{n}} 步", "aiThinking": "AI思考", + "reasoningChain": "推理过程", "planning": "规划中", "assistantStreamPhase": "助手输出", "toolCallsDetected": "检测到 {{count}} 个工具调用", @@ -318,6 +319,19 @@ "agentModeMulti": "多代理", "agentModeSingleHint": "单模型 ReAct 循环,适合常规对话与工具调用", "agentModeMultiHint": "Eino 预置编排(deep / plan_execute / supervisor),适合复杂任务", + "reasoningModeLabel": "模型推理", + "reasoningEffortLabel": "推理强度", + "reasoningModeDefault": "跟随系统", + "reasoningModeOff": "关闭", + "reasoningModeOn": "开启", + "reasoningModeAuto": "自动", + "reasoningEffortUnset": "不指定", + "reasoningCompactLabel": "推理", + "reasoningCompactAria": "打开模型推理选项", + "reasoningPanelTitle": "模型推理", + "reasoningPanelHint": "仅 Eino 单代理与多代理请求会带上这些参数;与系统设置中的默认值合并。", + "reasoningSummaryFollow": "系统", + "reasoningSummaryDash": "—", "agentModeOrchPlanExecute": "Plan-Exec", "agentModeOrchSupervisor": "Supervisor", "hitlTitle": "人机协同", @@ -1581,6 +1595,10 @@ "maxTotalTokens": "最大上下文 Token 数", "maxTotalTokensPlaceholder": "120000", "maxTotalTokensHint": "内存压缩和攻击链构建共用此配置,默认 120000", + "openaiReasoningTitle": "模型推理(Eino)", + "openaiReasoningHint": "仅 Eino 单代理与多代理请求生效;与对话页「模型推理」下拉配合使用。", + "openaiReasoningProfile": "线路 profile", + "openaiReasoningAllowClient": "允许对话页覆盖推理选项", "fofaBaseUrlPlaceholder": "https://fofa.info/api/v1/search/all(可选)", "fofaBaseUrlHint": "留空则使用默认地址。", "email": "Email", diff --git a/web/static/js/chat.js b/web/static/js/chat.js index a768488f..b0505aaf 100644 --- a/web/static/js/chat.js +++ b/web/static/js/chat.js @@ -40,6 +40,8 @@ let chatAttachmentSeq = 0; // 对话模式:react = 原生 ReAct(/agent-loop);eino_single = Eino ADK 单代理(/api/eino-agent/stream);deep / plan_execute / supervisor = Eino 多代理(/api/multi-agent/stream,请求体 orchestration) const AGENT_MODE_STORAGE_KEY = 'cyberstrike-chat-agent-mode'; +const REASONING_MODE_LS = 'cyberstrike-chat-reasoning-mode'; +const REASONING_EFFORT_LS = 'cyberstrike-chat-reasoning-effort'; const CHAT_AGENT_MODE_REACT = 'react'; const CHAT_AGENT_MODE_EINO_SINGLE = 'eino_single'; const CHAT_AGENT_EINO_MODES = ['deep', 'plan_execute', 'supervisor']; @@ -492,6 +494,132 @@ function syncAgentModeFromValue(value) { const v = el.getAttribute('data-value'); el.classList.toggle('selected', v === value); }); + syncReasoningRowVisibility(value); +} + +function syncReasoningRowVisibility(modeVal) { + const wrap = document.getElementById('chat-reasoning-wrapper'); + if (!wrap) return; + const show = modeVal === CHAT_AGENT_MODE_EINO_SINGLE || (multiAgentAPIEnabled && chatAgentModeIsEino(modeVal)); + wrap.style.display = show ? '' : 'none'; + if (!show) { + closeChatReasoningPanel(); + } else { + updateChatReasoningSummary(); + } +} + +function reasoningSummaryModeLabel(mode) { + const m = (mode || 'default').trim(); + const t = (typeof window.t === 'function') ? window.t : function (k) { return k; }; + switch (m) { + case 'off': return t('chat.reasoningModeOff'); + case 'on': return t('chat.reasoningModeOn'); + case 'auto': return t('chat.reasoningModeAuto'); + default: return t('chat.reasoningSummaryFollow'); + } +} + +function updateChatReasoningSummary() { + const el = document.getElementById('chat-reasoning-summary'); + const modeEl = document.getElementById('chat-reasoning-mode'); + const effEl = document.getElementById('chat-reasoning-effort'); + if (!el || !modeEl) return; + const mode = (modeEl.value || 'default').trim(); + const effort = effEl && effEl.value ? String(effEl.value).trim() : ''; + const t = (typeof window.t === 'function') ? window.t : function (k) { return k; }; + const modePart = reasoningSummaryModeLabel(mode); + const effPart = effort || t('chat.reasoningSummaryDash'); + el.textContent = modePart + ' / ' + effPart; +} + +function closeChatReasoningPanel() { + const panel = document.getElementById('chat-reasoning-panel'); + const btn = document.getElementById('chat-reasoning-btn'); + if (panel) panel.style.display = 'none'; + if (btn) { + btn.classList.remove('active'); + btn.setAttribute('aria-expanded', 'false'); + } +} + +function toggleChatReasoningPanel() { + const panel = document.getElementById('chat-reasoning-panel'); + const btn = document.getElementById('chat-reasoning-btn'); + if (!panel || !btn) return; + const isOpen = panel.style.display === 'flex'; + if (isOpen) { + closeChatReasoningPanel(); + return; + } + if (typeof closeAgentModePanel === 'function') { + closeAgentModePanel(); + } + if (typeof closeRoleSelectionPanel === 'function') { + closeRoleSelectionPanel(); + } + updateChatReasoningSummary(); + panel.style.display = 'flex'; + btn.classList.add('active'); + btn.setAttribute('aria-expanded', 'true'); +} + +function restoreChatReasoningControlsFromStorage() { + try { + const m = document.getElementById('chat-reasoning-mode'); + const e = document.getElementById('chat-reasoning-effort'); + if (m) { + const v = localStorage.getItem(REASONING_MODE_LS); + if (v && ['default', 'off', 'on', 'auto'].indexOf(v) !== -1) { + m.value = v; + } + } + if (e) { + const v = localStorage.getItem(REASONING_EFFORT_LS); + if (v !== null && ['', 'low', 'medium', 'high', 'max'].indexOf(v) !== -1) { + e.value = v; + } + } + updateChatReasoningSummary(); + } catch (err) { /* ignore */ } +} + +function persistChatReasoningPrefs() { + try { + const m = document.getElementById('chat-reasoning-mode'); + const elEff = document.getElementById('chat-reasoning-effort'); + if (m) localStorage.setItem(REASONING_MODE_LS, m.value || 'default'); + if (elEff) localStorage.setItem(REASONING_EFFORT_LS, elEff.value || ''); + updateChatReasoningSummary(); + } catch (err) { /* ignore */ } +} + +/** 供 WebShell 等复用:在 Eino 路径下返回 reasoning 请求片段或 undefined */ +function buildReasoningRequestPayload() { + const wrap = document.getElementById('chat-reasoning-wrapper'); + if (!wrap || wrap.style.display === 'none') { + return undefined; + } + const modeEl = document.getElementById('chat-reasoning-mode'); + const effEl = document.getElementById('chat-reasoning-effort'); + if (!modeEl) return undefined; + const mode = (modeEl.value || 'default').trim(); + const effort = effEl && effEl.value ? String(effEl.value).trim() : ''; + if (mode === 'default' && !effort) { + return undefined; + } + const o = {}; + if (mode !== 'default') o.mode = mode; + if (effort) o.effort = effort; + return Object.keys(o).length ? o : undefined; +} + +if (typeof window !== 'undefined') { + window.persistChatReasoningPrefs = persistChatReasoningPrefs; + window.buildReasoningRequestPayload = buildReasoningRequestPayload; + window.closeChatReasoningPanel = closeChatReasoningPanel; + window.toggleChatReasoningPanel = toggleChatReasoningPanel; + window.updateChatReasoningSummary = updateChatReasoningSummary; } function closeAgentModePanel() { @@ -513,6 +641,9 @@ function toggleAgentModePanel() { closeAgentModePanel(); return; } + if (typeof closeChatReasoningPanel === 'function') { + closeChatReasoningPanel(); + } if (typeof closeRoleSelectionPanel === 'function') { closeRoleSelectionPanel(); } @@ -563,6 +694,8 @@ async function initChatAgentModeFromConfig() { } catch (e) { /* ignore */ } sel.value = stored; syncAgentModeFromValue(stored); + restoreChatReasoningControlsFromStorage(); + syncReasoningRowVisibility(stored); } catch (e) { console.warn('initChatAgentModeFromConfig', e); } @@ -575,6 +708,9 @@ document.addEventListener('languagechange', function () { if (v === CHAT_AGENT_MODE_REACT || chatAgentModeIsEinoSingle(v) || chatAgentModeIsEino(v)) { syncAgentModeFromValue(v); } + if (typeof updateChatReasoningSummary === 'function') { + updateChatReasoningSummary(); + } }); // 保存输入框草稿到localStorage(防抖版本) @@ -760,6 +896,10 @@ async function sendMessage() { serverPath: a.serverPath })); } + const reasoningPayload = buildReasoningRequestPayload(); + if (reasoningPayload) { + body.reasoning = reasoningPayload; + } // 发送后清空附件列表 chatAttachments = []; renderChatFileChips(); @@ -2228,6 +2368,8 @@ function renderProcessDetails(messageId, processDetails) { } } else if (eventType === 'thinking') { itemTitle = agPx + '🤔 ' + (typeof window.t === 'function' ? window.t('chat.aiThinking') : 'AI思考'); + } else if (eventType === 'reasoning_chain') { + itemTitle = agPx + '🔗 ' + (typeof window.t === 'function' ? window.t('chat.reasoningChain') : '推理过程'); } else if (eventType === 'planning') { if (typeof window.einoMainStreamPlanningTitle === 'function') { itemTitle = window.einoMainStreamPlanningTitle(data); @@ -7233,6 +7375,14 @@ document.addEventListener('click', function(event) { closeAgentModePanel(); } } + + const reasoningWrap = document.getElementById('chat-reasoning-wrapper'); + const reasoningPanel = document.getElementById('chat-reasoning-panel'); + if (reasoningWrap && reasoningPanel && reasoningPanel.style.display === 'flex') { + if (!reasoningWrap.contains(event.target)) { + closeChatReasoningPanel(); + } + } }); // 创建分组 diff --git a/web/static/js/monitor.js b/web/static/js/monitor.js index 386a3724..f67f84af 100644 --- a/web/static/js/monitor.js +++ b/web/static/js/monitor.js @@ -1223,11 +1223,14 @@ function handleStreamEvent(event, progressElement, progressId, break; } - case 'thinking_stream_start': { + case 'thinking_stream_start': + case 'reasoning_chain_stream_start': { const d = event.data || {}; const streamId = d.streamId || null; if (!streamId) break; + const timelineType = event.type === 'reasoning_chain_stream_start' ? 'reasoning_chain' : 'thinking'; + let state = thinkingStreamStateByProgressId.get(progressId); if (!state) { state = new Map(); @@ -1246,9 +1249,12 @@ function handleStreamEvent(event, progressElement, progressId, } break; } - const thinkBase = typeof window.t === 'function' ? window.t('chat.aiThinking') : 'AI思考'; - const title = timelineAgentBracketPrefix(d) + '🤔 ' + thinkBase; - const itemId = addTimelineItem(timeline, 'thinking', { + const labelBase = typeof window.t === 'function' + ? window.t(timelineType === 'reasoning_chain' ? 'chat.reasoningChain' : 'chat.aiThinking') + : (timelineType === 'reasoning_chain' ? '推理过程' : 'AI思考'); + const emoji = timelineType === 'reasoning_chain' ? '🔗' : '🤔'; + const title = timelineAgentBracketPrefix(d) + emoji + ' ' + labelBase; + const itemId = addTimelineItem(timeline, timelineType, { title: title, message: ' ', data: d @@ -1257,7 +1263,8 @@ function handleStreamEvent(event, progressElement, progressId, break; } - case 'thinking_stream_delta': { + case 'thinking_stream_delta': + case 'reasoning_chain_stream_delta': { const d = event.data || {}; const streamId = d.streamId || null; if (!streamId) break; @@ -1281,7 +1288,9 @@ function handleStreamEvent(event, progressElement, progressId, } case 'thinking': - // 如果本 thinking 是由 thinking_stream_* 聚合出来的(带 streamId),避免重复创建 timeline item + case 'reasoning_chain': { + const timelineType = event.type === 'reasoning_chain' ? 'reasoning_chain' : 'thinking'; + // 若已由 *_stream_* 聚合(带 streamId),避免重复创建 timeline item if (event.data && event.data.streamId) { const streamId = event.data.streamId; const state = thinkingStreamStateByProgressId.get(progressId); @@ -1303,12 +1312,17 @@ function handleStreamEvent(event, progressElement, progressId, } } - addTimelineItem(timeline, 'thinking', { - title: timelineAgentBracketPrefix(event.data) + '🤔 ' + (typeof window.t === 'function' ? window.t('chat.aiThinking') : 'AI思考'), + const labelBase = typeof window.t === 'function' + ? window.t(timelineType === 'reasoning_chain' ? 'chat.reasoningChain' : 'chat.aiThinking') + : (timelineType === 'reasoning_chain' ? '推理过程' : 'AI思考'); + const emoji = timelineType === 'reasoning_chain' ? '🔗' : '🤔'; + addTimelineItem(timeline, timelineType, { + title: timelineAgentBracketPrefix(event.data) + emoji + ' ' + labelBase, message: event.message, data: event.data }); break; + } case 'tool_calls_detected': addTimelineItem(timeline, 'tool_calls_detected', { @@ -2475,7 +2489,7 @@ function addTimelineItem(timeline, type, options) { `; // 根据类型添加详细内容 - if ((type === 'thinking' || type === 'planning') && options.message) { + if ((type === 'thinking' || type === 'reasoning_chain' || type === 'planning') && options.message) { const streamBody = typeof formatTimelineStreamBody === 'function' ? formatTimelineStreamBody(options.message, options.data) : options.message; @@ -3410,6 +3424,8 @@ function refreshProgressAndTimelineI18n() { } else { titleSpan.textContent = ap + '\uD83E\uDD14 ' + _t('chat.aiThinking'); } + } else if (type === 'reasoning_chain') { + titleSpan.textContent = ap + '\uD83D\uDD17 ' + _t('chat.reasoningChain'); } else if (type === 'planning') { if (item.dataset.orchestration && typeof einoMainStreamPlanningTitle === 'function') { titleSpan.textContent = einoMainStreamPlanningTitle({ diff --git a/web/static/js/roles.js b/web/static/js/roles.js index 0aec161b..890c1a6b 100644 --- a/web/static/js/roles.js +++ b/web/static/js/roles.js @@ -256,6 +256,9 @@ function toggleRoleSelectionPanel() { if (typeof closeAgentModePanel === 'function') { closeAgentModePanel(); } + if (typeof closeChatReasoningPanel === 'function') { + closeChatReasoningPanel(); + } panel.style.display = 'flex'; // 使用flex布局 // 添加打开状态的视觉反馈 if (roleSelectorBtn) { diff --git a/web/static/js/settings.js b/web/static/js/settings.js index da45e95e..a1bb65d0 100644 --- a/web/static/js/settings.js +++ b/web/static/js/settings.js @@ -159,6 +159,27 @@ async function loadConfig(loadTools = true) { if (maxTokensEl) { maxTokensEl.value = currentConfig.openai.max_total_tokens || 120000; } + const orm = currentConfig.openai && currentConfig.openai.reasoning ? currentConfig.openai.reasoning : {}; + const orModeEl = document.getElementById('openai-reasoning-mode'); + if (orModeEl) { + const mv = (orm.mode || 'auto').toString().trim().toLowerCase(); + orModeEl.value = ['auto', 'on', 'off'].includes(mv) ? mv : 'auto'; + } + const orEffEl = document.getElementById('openai-reasoning-effort'); + if (orEffEl) { + const ev = (orm.effort || '').toString().trim().toLowerCase(); + orEffEl.value = ['', 'low', 'medium', 'high', 'max'].includes(ev) ? ev : ''; + } + const orProfEl = document.getElementById('openai-reasoning-profile'); + if (orProfEl) { + const pv = (orm.profile || 'auto').toString().trim().toLowerCase(); + const ok = ['auto', 'deepseek_compat', 'openai_compat', 'output_config_effort']; + orProfEl.value = ok.includes(pv) ? pv : 'auto'; + } + const orAllowEl = document.getElementById('openai-reasoning-allow-client'); + if (orAllowEl) { + orAllowEl.checked = orm.allow_client_reasoning !== false; + } // 填充FOFA配置 const fofa = currentConfig.fofa || {}; @@ -1065,13 +1086,22 @@ async function applySettings() { }; const wecomAgentIdVal = document.getElementById('robot-wecom-agent-id')?.value.trim(); + const prevOpenai = (currentConfig && currentConfig.openai) ? currentConfig.openai : {}; const config = { openai: { + ...prevOpenai, provider: provider, api_key: apiKey, base_url: baseUrl, model: model, - max_total_tokens: parseInt(document.getElementById('openai-max-total-tokens')?.value) || 120000 + max_total_tokens: parseInt(document.getElementById('openai-max-total-tokens')?.value) || 120000, + reasoning: { + ...(prevOpenai.reasoning || {}), + mode: document.getElementById('openai-reasoning-mode')?.value || 'auto', + effort: (document.getElementById('openai-reasoning-effort')?.value || '').trim(), + profile: document.getElementById('openai-reasoning-profile')?.value || 'auto', + allow_client_reasoning: document.getElementById('openai-reasoning-allow-client')?.checked !== false + } }, fofa: { email: document.getElementById('fofa-email')?.value.trim() || '', diff --git a/web/static/js/webshell.js b/web/static/js/webshell.js index 52fbb69f..cc5ffca1 100644 --- a/web/static/js/webshell.js +++ b/web/static/js/webshell.js @@ -1658,6 +1658,8 @@ function buildWebshellTimelineItemFromDetail(detail) { title = ap + ((typeof window.t === 'function') ? window.t('chat.iterationRound', { n: data.iteration || 1 }) : ('第 ' + (data.iteration || 1) + ' 轮迭代')); } else if (eventType === 'thinking') { title = ap + '🤔 ' + ((typeof window.t === 'function') ? window.t('chat.aiThinking') : 'AI 思考'); + } else if (eventType === 'reasoning_chain') { + title = ap + '🔗 ' + ((typeof window.t === 'function') ? window.t('chat.reasoningChain') : '推理过程'); } else if (eventType === 'tool_calls_detected') { title = ap + '🔧 ' + ((typeof window.t === 'function') ? window.t('chat.toolCallsDetected', { count: data.count || 0 }) : ('检测到 ' + (data.count || 0) + ' 个工具调用')); } else if (eventType === 'tool_call') { @@ -2847,6 +2849,12 @@ function runWebshellAiSend(conn, inputEl, sendBtn, messagesContainer) { if (info && info.orchestration) { body.orchestration = info.orchestration; } + if (typeof window.buildReasoningRequestPayload === 'function') { + var rp = window.buildReasoningRequestPayload(); + if (rp) { + body.reasoning = rp; + } + } return apiFetch(info.path, { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -2953,17 +2961,19 @@ function runWebshellAiSend(conn, inputEl, sendBtn, messagesContainer) { appendTimelineItem('iteration', '🔍 ' + iterTitle, iterMessage, _ed); if (!streamingTarget) assistantDiv.textContent = '…'; - // ─── Thinking (non-stream + stream) ─── - } else if (_et === 'thinking_stream_start' && _ed.streamId) { + // ─── Thinking / reasoning_chain(推理过程,reasoning_content) ─── + } else if ((_et === 'thinking_stream_start' || _et === 'reasoning_chain_stream_start') && _ed.streamId) { + var isRcStart = _et === 'reasoning_chain_stream_start'; if (wsThinkingStreams.has(_ed.streamId)) { var tsExist = wsThinkingStreams.get(_ed.streamId); tsExist.buf = ''; if (tsExist.body) tsExist.body.textContent = ''; } else { - var thinkSLabel = wsTOr('chat.aiThinking', 'AI 思考'); + var thinkSLabel = wsTOr(isRcStart ? 'chat.reasoningChain' : 'chat.aiThinking', isRcStart ? '推理过程' : 'AI 思考'); + var thinkEmoji = isRcStart ? '🔗' : '🤔'; var thinkSItem = document.createElement('div'); - thinkSItem.className = 'webshell-ai-timeline-item webshell-ai-timeline-thinking'; - thinkSItem.innerHTML = '' + escapeHtml(webshellAgentPx(_ed) + '🤔 ' + thinkSLabel) + ''; + thinkSItem.className = 'webshell-ai-timeline-item webshell-ai-timeline-' + (isRcStart ? 'reasoning_chain' : 'thinking'); + thinkSItem.innerHTML = '' + escapeHtml(webshellAgentPx(_ed) + thinkEmoji + ' ' + thinkSLabel) + ''; var thinkSPre = document.createElement('div'); thinkSPre.className = 'webshell-ai-timeline-msg webshell-thinking-stream-body'; thinkSItem.appendChild(thinkSPre); @@ -2972,7 +2982,7 @@ function runWebshellAiSend(conn, inputEl, sendBtn, messagesContainer) { wsThinkingStreams.set(_ed.streamId, { el: thinkSItem, body: thinkSPre, buf: '' }); } if (!streamingTarget) assistantDiv.textContent = '…'; - } else if (_et === 'thinking_stream_delta' && _ed.streamId) { + } else if ((_et === 'thinking_stream_delta' || _et === 'reasoning_chain_stream_delta') && _ed.streamId) { var tsD = wsThinkingStreams.get(_ed.streamId); if (tsD) { var normT = (typeof window.normalizeStreamingDeltaJs === 'function') @@ -2985,7 +2995,7 @@ function runWebshellAiSend(conn, inputEl, sendBtn, messagesContainer) { } } if (!streamingTarget) assistantDiv.textContent = '…'; - } else if (_et === 'thinking_stream_end' && _ed.streamId) { + } else if ((_et === 'thinking_stream_end' || _et === 'reasoning_chain_stream_end') && _ed.streamId) { var tsE = wsThinkingStreams.get(_ed.streamId); if (tsE) { var fullThink = (_em != null && _em !== '') ? String(_em) : tsE.buf; @@ -2996,13 +3006,15 @@ function runWebshellAiSend(conn, inputEl, sendBtn, messagesContainer) { } wsThinkingStreams.delete(_ed.streamId); } - } else if (_et === 'thinking' && _em) { + } else if ((_et === 'thinking' || _et === 'reasoning_chain') && _em) { // 如果有 streamId 且已存在流式条目,跳过避免重复 if (_ed.streamId && wsThinkingStreams.has(_ed.streamId)) { - // 已由 thinking_stream_* 处理 + // 已由 *_stream_* 处理 } else { - var thinkLabel = wsTOr('chat.aiThinking', 'AI 思考'); - appendTimelineItem('thinking', webshellAgentPx(_ed) + '🤔 ' + thinkLabel, _em, _ed); + var isRc = _et === 'reasoning_chain'; + var thinkLabel = wsTOr(isRc ? 'chat.reasoningChain' : 'chat.aiThinking', isRc ? '推理过程' : 'AI 思考'); + var thinkEm = isRc ? '🔗' : '🤔'; + appendTimelineItem(isRc ? 'reasoning_chain' : 'thinking', webshellAgentPx(_ed) + thinkEm + ' ' + thinkLabel, _em, _ed); } if (!streamingTarget) assistantDiv.textContent = '…'; diff --git a/web/templates/index.html b/web/templates/index.html index 216b2d52..2108d5dd 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -894,6 +894,8 @@
+
+
+ +
@@ -1989,6 +2036,38 @@ 内存压缩和攻击链构建共用此配置,默认 120000 +
+ + 仅影响 Eino 单代理与多代理;对话页可覆盖(见下方「允许对话覆盖」)。 +
+ + + + + + +
+ +
测试连接