mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-05-14 20:48:10 +02:00
Add files via upload
This commit is contained in:
@@ -3623,6 +3623,12 @@ header {
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* 流式增量阶段纯文本展示(避免半段 Markdown 反复解析) */
|
||||
.timeline-item-content.timeline-stream-plain {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.tool-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -18300,6 +18306,10 @@ button.chat-files-dropdown-item:hover:not(:disabled) {
|
||||
transform: translateX(-50%) translateY(0);
|
||||
}
|
||||
|
||||
.chat-files-toast.chat-toast--error {
|
||||
background: #b91c1c;
|
||||
}
|
||||
|
||||
/* 对话附件读取 / 文件管理上传 进度条 */
|
||||
/* [hidden] 默认会被本类的 display:flex 覆盖,须显式隐藏否则空闲时仍露出灰条 */
|
||||
.chat-upload-progress-row[hidden] {
|
||||
|
||||
+25
-3
@@ -51,6 +51,28 @@ const HITL_MODE_REVIEW_EDIT = 'review_edit';
|
||||
const HITL_MODE_OPTIONS = [HITL_MODE_OFF, HITL_MODE_APPROVAL, HITL_MODE_REVIEW_EDIT];
|
||||
let hitlApplyFeedbackTimer = null;
|
||||
|
||||
/** 非阻塞提示(与 chat-files-toast 样式共用) */
|
||||
function showChatToast(message, type) {
|
||||
const text = message == null ? '' : String(message);
|
||||
if (!text) return;
|
||||
const el = document.createElement('div');
|
||||
el.className = 'chat-files-toast' + (type === 'error' ? ' chat-toast--error' : '');
|
||||
el.setAttribute('role', 'status');
|
||||
el.textContent = text;
|
||||
document.body.appendChild(el);
|
||||
requestAnimationFrame(function () {
|
||||
el.classList.add('chat-files-toast-visible');
|
||||
});
|
||||
const hideMs = type === 'error' ? 4500 : 2600;
|
||||
setTimeout(function () {
|
||||
el.classList.remove('chat-files-toast-visible');
|
||||
setTimeout(function () { el.remove(); }, 300);
|
||||
}, hideMs);
|
||||
}
|
||||
if (typeof window !== 'undefined') {
|
||||
window.showChatToast = showChatToast;
|
||||
}
|
||||
|
||||
function normalizeOrchestrationClient(s) {
|
||||
const v = String(s || '').trim().toLowerCase().replace(/-/g, '_');
|
||||
if (v === 'plan_execute' || v === 'planexecute' || v === 'pe') return 'plan_execute';
|
||||
@@ -293,7 +315,7 @@ function showHitlApplyFeedback(text, isError, partial) {
|
||||
}
|
||||
if (!el) {
|
||||
if (text && isError) {
|
||||
alert(text);
|
||||
showChatToast(text, 'error');
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -2853,7 +2875,7 @@ async function loadConversation(conversationId) {
|
||||
const conversation = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
alert('加载对话失败: ' + (conversation.error || '未知错误'));
|
||||
showChatToast('加载对话失败: ' + (conversation.error || '未知错误'), 'error');
|
||||
return;
|
||||
}
|
||||
if (seq !== loadConversationRequestSeq) {
|
||||
@@ -3061,7 +3083,7 @@ async function loadConversation(conversationId) {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载对话失败:', error);
|
||||
alert('加载对话失败: ' + error.message);
|
||||
showChatToast('加载对话失败: ' + (error && error.message ? error.message : String(error)), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+79
-24
@@ -273,6 +273,47 @@ function escapeHtmlLocal(text) {
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
/**
|
||||
* 与 internal/openai.normalizeStreamingDelta 一致:兼容网关/模型返回「累计全文」或整包重发,
|
||||
* 避免前端 buffer += chunk 与后端已归一化的增量叠加导致逐段重复(如「响应中显示了响应中显示了」)。
|
||||
* @returns {[string, string]} [nextBuffer, effectiveDelta]
|
||||
*/
|
||||
function normalizeStreamingDeltaJs(current, incoming) {
|
||||
const cur = current == null ? '' : String(current);
|
||||
const inc = incoming == null ? '' : String(incoming);
|
||||
if (inc === '') {
|
||||
return [cur, ''];
|
||||
}
|
||||
if (cur === '') {
|
||||
return [inc, inc];
|
||||
}
|
||||
if (inc.startsWith(cur) && inc.length > cur.length) {
|
||||
return [inc, inc.slice(cur.length)];
|
||||
}
|
||||
const runeCount = Array.from(cur).length;
|
||||
if (inc === cur && runeCount > 1) {
|
||||
return [cur, ''];
|
||||
}
|
||||
return [cur + inc, inc];
|
||||
}
|
||||
if (typeof window !== 'undefined') {
|
||||
window.normalizeStreamingDeltaJs = normalizeStreamingDeltaJs;
|
||||
}
|
||||
|
||||
/** 流式 delta:纯文本,避免每条全量 marked + DOMPurify */
|
||||
function setTimelineItemContentStreamPlain(contentEl, text) {
|
||||
if (!contentEl) return;
|
||||
contentEl.classList.add('timeline-stream-plain');
|
||||
contentEl.textContent = text == null ? '' : String(text);
|
||||
}
|
||||
|
||||
/** 流结束或非流式:富文本(已消毒的 HTML 字符串) */
|
||||
function setTimelineItemContentStreamRich(contentEl, html) {
|
||||
if (!contentEl) return;
|
||||
contentEl.classList.remove('timeline-stream-plain');
|
||||
contentEl.innerHTML = html;
|
||||
}
|
||||
|
||||
function formatAssistantMarkdownContent(text) {
|
||||
const raw = text == null ? '' : String(text);
|
||||
if (typeof marked !== 'undefined') {
|
||||
@@ -1160,7 +1201,19 @@ function handleStreamEvent(event, progressElement, progressId,
|
||||
state = new Map();
|
||||
thinkingStreamStateByProgressId.set(progressId, state);
|
||||
}
|
||||
// 若已存在,重置 buffer
|
||||
// 同一 streamId 重复 start:复用已有条目,避免孤儿卡片 + 新条目重复收 delta
|
||||
if (state.has(streamId)) {
|
||||
const ex = state.get(streamId);
|
||||
ex.buffer = '';
|
||||
const existingItem = document.getElementById(ex.itemId);
|
||||
if (existingItem) {
|
||||
const contentEl = existingItem.querySelector('.timeline-item-content');
|
||||
if (contentEl) {
|
||||
setTimelineItemContentStreamPlain(contentEl, '');
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
const thinkBase = typeof window.t === 'function' ? window.t('chat.aiThinking') : 'AI思考';
|
||||
const title = timelineAgentBracketPrefix(d) + '🤔 ' + thinkBase;
|
||||
const itemId = addTimelineItem(timeline, 'thinking', {
|
||||
@@ -1182,17 +1235,14 @@ function handleStreamEvent(event, progressElement, progressId,
|
||||
const s = state.get(streamId);
|
||||
|
||||
const delta = event.message || '';
|
||||
s.buffer += delta;
|
||||
const merged = normalizeStreamingDeltaJs(s.buffer, delta);
|
||||
s.buffer = merged[0];
|
||||
|
||||
const item = document.getElementById(s.itemId);
|
||||
if (item) {
|
||||
const contentEl = item.querySelector('.timeline-item-content');
|
||||
if (contentEl) {
|
||||
if (typeof formatMarkdown === 'function') {
|
||||
contentEl.innerHTML = formatMarkdown(s.buffer);
|
||||
} else {
|
||||
contentEl.textContent = s.buffer;
|
||||
}
|
||||
setTimelineItemContentStreamPlain(contentEl, s.buffer);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -1210,11 +1260,10 @@ function handleStreamEvent(event, progressElement, progressId,
|
||||
if (item) {
|
||||
const contentEl = item.querySelector('.timeline-item-content');
|
||||
if (contentEl) {
|
||||
// contentEl.innerHTML 用于兼容 Markdown 展示
|
||||
if (typeof formatMarkdown === 'function') {
|
||||
contentEl.innerHTML = formatMarkdown(s.buffer);
|
||||
setTimelineItemContentStreamRich(contentEl, formatMarkdown(s.buffer));
|
||||
} else {
|
||||
contentEl.textContent = s.buffer;
|
||||
setTimelineItemContentStreamPlain(contentEl, s.buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1456,6 +1505,18 @@ function handleStreamEvent(event, progressElement, progressId,
|
||||
stateMap = new Map();
|
||||
einoAgentReplyStreamStateByProgressId.set(progressId, stateMap);
|
||||
}
|
||||
if (stateMap.has(streamId)) {
|
||||
const ex = stateMap.get(streamId);
|
||||
ex.buffer = '';
|
||||
const existingItem = document.getElementById(ex.itemId);
|
||||
if (existingItem) {
|
||||
let contentEl = existingItem.querySelector('.timeline-item-content');
|
||||
if (contentEl) {
|
||||
setTimelineItemContentStreamPlain(contentEl, '');
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
const streamingLabel = typeof window.t === 'function' ? window.t('timeline.running') : '执行中...';
|
||||
const replyTitleBase = typeof window.t === 'function' ? window.t('chat.einoAgentReplyTitle') : '子代理回复';
|
||||
const itemId = addTimelineItem(timeline, 'eino_agent_reply', {
|
||||
@@ -1477,7 +1538,8 @@ function handleStreamEvent(event, progressElement, progressId,
|
||||
const stateMap = einoAgentReplyStreamStateByProgressId.get(progressId);
|
||||
if (!stateMap || !stateMap.has(streamId)) break;
|
||||
const s = stateMap.get(streamId);
|
||||
s.buffer += delta;
|
||||
const merged = normalizeStreamingDeltaJs(s.buffer, delta);
|
||||
s.buffer = merged[0];
|
||||
const item = document.getElementById(s.itemId);
|
||||
if (item) {
|
||||
let contentEl = item.querySelector('.timeline-item-content');
|
||||
@@ -1490,11 +1552,7 @@ function handleStreamEvent(event, progressElement, progressId,
|
||||
}
|
||||
}
|
||||
if (contentEl) {
|
||||
if (typeof formatMarkdown === 'function') {
|
||||
contentEl.innerHTML = formatMarkdown(s.buffer);
|
||||
} else {
|
||||
contentEl.textContent = s.buffer;
|
||||
}
|
||||
setTimelineItemContentStreamPlain(contentEl, s.buffer);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -1522,9 +1580,9 @@ function handleStreamEvent(event, progressElement, progressId,
|
||||
item.appendChild(contentEl);
|
||||
}
|
||||
if (typeof formatMarkdown === 'function') {
|
||||
contentEl.innerHTML = formatMarkdown(full);
|
||||
setTimelineItemContentStreamRich(contentEl, formatMarkdown(full));
|
||||
} else {
|
||||
contentEl.textContent = full;
|
||||
setTimelineItemContentStreamPlain(contentEl, full);
|
||||
}
|
||||
if (d.einoAgent != null && String(d.einoAgent).trim() !== '') {
|
||||
item.dataset.einoAgent = String(d.einoAgent).trim();
|
||||
@@ -1665,7 +1723,8 @@ function handleStreamEvent(event, progressElement, progressId,
|
||||
}
|
||||
|
||||
const deltaContent = event.message || '';
|
||||
state.buffer += deltaContent;
|
||||
const mergedResp = normalizeStreamingDeltaJs(state.buffer, deltaContent);
|
||||
state.buffer = mergedResp[0];
|
||||
|
||||
// 更新时间线条目内容
|
||||
if (state.itemId) {
|
||||
@@ -1675,11 +1734,7 @@ function handleStreamEvent(event, progressElement, progressId,
|
||||
if (contentEl) {
|
||||
const meta = state.streamMeta || responseData;
|
||||
const body = formatTimelineStreamBody(state.buffer, meta);
|
||||
if (typeof formatMarkdown === 'function') {
|
||||
contentEl.innerHTML = formatMarkdown(body);
|
||||
} else {
|
||||
contentEl.textContent = body;
|
||||
}
|
||||
setTimelineItemContentStreamPlain(contentEl, body);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2898,7 +2898,10 @@ function runWebshellAiSend(conn, inputEl, sendBtn, messagesContainer) {
|
||||
} else if (_et === 'response_delta') {
|
||||
var deltaText = (_em != null && _em !== '') ? String(_em) : '';
|
||||
if (deltaText) {
|
||||
streamingTarget += deltaText;
|
||||
var normR = (typeof window.normalizeStreamingDeltaJs === 'function')
|
||||
? window.normalizeStreamingDeltaJs(streamingTarget, deltaText)
|
||||
: [streamingTarget + deltaText, deltaText];
|
||||
streamingTarget = normR[0];
|
||||
webshellStreamingTypingId += 1;
|
||||
streamingTypingId = webshellStreamingTypingId;
|
||||
runWebshellAiStreamingTyping(assistantDiv, streamingTarget, streamingTypingId, messagesContainer);
|
||||
@@ -2952,6 +2955,11 @@ function runWebshellAiSend(conn, inputEl, sendBtn, messagesContainer) {
|
||||
|
||||
// ─── Thinking (non-stream + stream) ───
|
||||
} else if (_et === 'thinking_stream_start' && _ed.streamId) {
|
||||
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 thinkSItem = document.createElement('div');
|
||||
thinkSItem.className = 'webshell-ai-timeline-item webshell-ai-timeline-thinking';
|
||||
@@ -2962,11 +2970,14 @@ function runWebshellAiSend(conn, inputEl, sendBtn, messagesContainer) {
|
||||
timelineContainer.appendChild(thinkSItem);
|
||||
timelineContainer.classList.add('has-items');
|
||||
wsThinkingStreams.set(_ed.streamId, { el: thinkSItem, body: thinkSPre, buf: '' });
|
||||
}
|
||||
if (!streamingTarget) assistantDiv.textContent = '…';
|
||||
} else if (_et === 'thinking_stream_delta' && _ed.streamId) {
|
||||
var tsD = wsThinkingStreams.get(_ed.streamId);
|
||||
if (tsD) {
|
||||
tsD.buf += (_em || '');
|
||||
var normT = (typeof window.normalizeStreamingDeltaJs === 'function')
|
||||
? window.normalizeStreamingDeltaJs(tsD.buf, _em || '') : [tsD.buf + (_em || ''), _em || ''];
|
||||
tsD.buf = normT[0];
|
||||
if (typeof formatMarkdown === 'function') {
|
||||
tsD.body.innerHTML = formatMarkdown(tsD.buf);
|
||||
} else {
|
||||
@@ -3076,6 +3087,12 @@ function runWebshellAiSend(conn, inputEl, sendBtn, messagesContainer) {
|
||||
|
||||
// ─── Eino sub-agent reply streaming ───
|
||||
} else if (_et === 'eino_agent_reply_stream_start' && _ed.streamId) {
|
||||
if (einoSubReplyStreams.has(_ed.streamId)) {
|
||||
var stExist = einoSubReplyStreams.get(_ed.streamId);
|
||||
stExist.buf = '';
|
||||
var preExist = stExist.el && stExist.el.querySelector('.webshell-eino-reply-stream-body');
|
||||
if (preExist) preExist.textContent = '';
|
||||
} else {
|
||||
var repTS = wsTOr('chat.einoAgentReplyTitle', '子代理回复');
|
||||
var runTS = wsTOr('timeline.running', '执行中...');
|
||||
var itemS = document.createElement('div');
|
||||
@@ -3084,11 +3101,14 @@ function runWebshellAiSend(conn, inputEl, sendBtn, messagesContainer) {
|
||||
timelineContainer.appendChild(itemS);
|
||||
timelineContainer.classList.add('has-items');
|
||||
einoSubReplyStreams.set(_ed.streamId, { el: itemS, buf: '' });
|
||||
}
|
||||
if (!streamingTarget) assistantDiv.textContent = '…';
|
||||
} else if (_et === 'eino_agent_reply_stream_delta' && _ed.streamId) {
|
||||
var stD = einoSubReplyStreams.get(_ed.streamId);
|
||||
if (stD) {
|
||||
stD.buf += (_em || '');
|
||||
var normS = (typeof window.normalizeStreamingDeltaJs === 'function')
|
||||
? window.normalizeStreamingDeltaJs(stD.buf, _em || '') : [stD.buf + (_em || ''), _em || ''];
|
||||
stD.buf = normS[0];
|
||||
var preD = stD.el.querySelector('.webshell-eino-reply-stream-body');
|
||||
if (!preD) {
|
||||
preD = document.createElement('pre');
|
||||
|
||||
Reference in New Issue
Block a user