mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-09-27 03:31:56 +02:00
Add files via upload
This commit is contained in:
+56
-31
@@ -766,47 +766,56 @@ async function sendMessage() {
|
|||||||
throw new Error('请求失败: ' + response.status);
|
throw new Error('请求失败: ' + response.status);
|
||||||
}
|
}
|
||||||
|
|
||||||
const reader = response.body.getReader();
|
window.__csAgentLiveStream = {
|
||||||
const decoder = new TextDecoder();
|
active: true,
|
||||||
let buffer = '';
|
conversationId: currentConversationId || null,
|
||||||
|
progressId: progressId
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const reader = response.body.getReader();
|
||||||
|
const decoder = new TextDecoder();
|
||||||
|
let buffer = '';
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const { done, value } = await reader.read();
|
const { done, value } = await reader.read();
|
||||||
if (done) break;
|
if (done) break;
|
||||||
|
|
||||||
buffer += decoder.decode(value, { stream: true });
|
buffer += decoder.decode(value, { stream: true });
|
||||||
const lines = buffer.split('\n');
|
const lines = buffer.split('\n');
|
||||||
buffer = lines.pop(); // 保留最后一个不完整的行
|
buffer = lines.pop(); // 保留最后一个不完整的行
|
||||||
|
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
if (line.startsWith('data: ')) {
|
if (line.startsWith('data: ')) {
|
||||||
try {
|
try {
|
||||||
const eventData = JSON.parse(line.slice(6));
|
const eventData = JSON.parse(line.slice(6));
|
||||||
handleStreamEvent(eventData, progressElement, progressId,
|
handleStreamEvent(eventData, progressElement, progressId,
|
||||||
() => assistantMessageId, (id) => { assistantMessageId = id; },
|
() => assistantMessageId, (id) => { assistantMessageId = id; },
|
||||||
() => mcpExecutionIds, (ids) => { mcpExecutionIds = ids; });
|
() => mcpExecutionIds, (ids) => { mcpExecutionIds = ids; });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('解析事件数据失败:', e, line);
|
console.error('解析事件数据失败:', e, line);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 处理剩余的buffer
|
// 处理剩余的buffer
|
||||||
if (buffer.trim()) {
|
if (buffer.trim()) {
|
||||||
const lines = buffer.split('\n');
|
const lines = buffer.split('\n');
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
if (line.startsWith('data: ')) {
|
if (line.startsWith('data: ')) {
|
||||||
try {
|
try {
|
||||||
const eventData = JSON.parse(line.slice(6));
|
const eventData = JSON.parse(line.slice(6));
|
||||||
handleStreamEvent(eventData, progressElement, progressId,
|
handleStreamEvent(eventData, progressElement, progressId,
|
||||||
() => assistantMessageId, (id) => { assistantMessageId = id; },
|
() => assistantMessageId, (id) => { assistantMessageId = id; },
|
||||||
() => mcpExecutionIds, (ids) => { mcpExecutionIds = ids; });
|
() => mcpExecutionIds, (ids) => { mcpExecutionIds = ids; });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('解析事件数据失败:', e, line);
|
console.error('解析事件数据失败:', e, line);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
window.__csAgentLiveStream = { active: false, conversationId: null, progressId: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
// 消息发送成功后,再次确保草稿被清除
|
// 消息发送成功后,再次确保草稿被清除
|
||||||
@@ -2922,6 +2931,22 @@ async function loadConversation(conversationId) {
|
|||||||
await window.restoreHitlInlineForConversation(conversationId);
|
await window.restoreHitlInlineForConversation(conversationId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 页面刷新后主流式连接会中断;若该会话仍在后端运行,自动挂载 task-events 补流继续更新前端迭代进度。
|
||||||
|
const skipReplay = typeof window.shouldSkipTaskEventReplayAttach === 'function'
|
||||||
|
&& window.shouldSkipTaskEventReplayAttach(conversationId);
|
||||||
|
if (
|
||||||
|
seq === loadConversationRequestSeq &&
|
||||||
|
currentConversationId === conversationId &&
|
||||||
|
typeof window.attachRunningTaskEventStream === 'function' &&
|
||||||
|
!skipReplay
|
||||||
|
) {
|
||||||
|
Promise.resolve()
|
||||||
|
.then(() => window.attachRunningTaskEventStream(conversationId))
|
||||||
|
.catch((e) => {
|
||||||
|
console.warn('attachRunningTaskEventStream on loadConversation failed', e);
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载对话失败:', error);
|
console.error('加载对话失败:', error);
|
||||||
alert('加载对话失败: ' + error.message);
|
alert('加载对话失败: ' + error.message);
|
||||||
|
|||||||
+136
-72
@@ -3,6 +3,36 @@ let activeTaskInterval = null;
|
|||||||
const ACTIVE_TASK_REFRESH_INTERVAL = 10000; // 10秒检查一次
|
const ACTIVE_TASK_REFRESH_INTERVAL = 10000; // 10秒检查一次
|
||||||
const TASK_FINAL_STATUSES = new Set(['failed', 'timeout', 'cancelled', 'completed']);
|
const TASK_FINAL_STATUSES = new Set(['failed', 'timeout', 'cancelled', 'completed']);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主对话 POST 流仍在读取时,禁止再挂 task-events 补流,否则同一事件会画两遍(与 HITL 是否开启无关)。
|
||||||
|
* window.__csAgentLiveStream 由 chat.js sendMessage 在读到 body 后设置,在 finally 中清除。
|
||||||
|
*/
|
||||||
|
function syncAgentLiveStreamConversationId(cid) {
|
||||||
|
if (!cid) return;
|
||||||
|
try {
|
||||||
|
const live = window.__csAgentLiveStream;
|
||||||
|
if (live && live.active) {
|
||||||
|
live.conversationId = cid;
|
||||||
|
}
|
||||||
|
} catch (e) { /* ignore */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldSkipTaskEventReplayAttach(conversationId) {
|
||||||
|
try {
|
||||||
|
const live = window.__csAgentLiveStream;
|
||||||
|
if (!live || !live.active || !live.progressId) return false;
|
||||||
|
if (!document.getElementById(live.progressId)) return false;
|
||||||
|
// 新会话:conversation 事件尚未到达前 conversationId 可能仍为 null,一律不补挂
|
||||||
|
if (live.conversationId == null) return true;
|
||||||
|
return live.conversationId === conversationId;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.shouldSkipTaskEventReplayAttach = shouldSkipTaskEventReplayAttach;
|
||||||
|
}
|
||||||
|
|
||||||
// 当前界面语言对应的 BCP 47 标签(与时间格式化一致)
|
// 当前界面语言对应的 BCP 47 标签(与时间格式化一致)
|
||||||
function getCurrentTimeLocale() {
|
function getCurrentTimeLocale() {
|
||||||
if (typeof window.__locale === 'string' && window.__locale.length) {
|
if (typeof window.__locale === 'string' && window.__locale.length) {
|
||||||
@@ -934,6 +964,7 @@ function handleStreamEvent(event, progressElement, progressId,
|
|||||||
|
|
||||||
// 更新当前对话ID
|
// 更新当前对话ID
|
||||||
currentConversationId = event.data.conversationId;
|
currentConversationId = event.data.conversationId;
|
||||||
|
syncAgentLiveStreamConversationId(event.data.conversationId);
|
||||||
updateActiveConversation();
|
updateActiveConversation();
|
||||||
addAttackChainButton(currentConversationId);
|
addAttackChainButton(currentConversationId);
|
||||||
loadActiveTasks();
|
loadActiveTasks();
|
||||||
@@ -1472,6 +1503,7 @@ function handleStreamEvent(event, progressElement, progressId,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
currentConversationId = responseData.conversationId;
|
currentConversationId = responseData.conversationId;
|
||||||
|
syncAgentLiveStreamConversationId(responseData.conversationId);
|
||||||
updateActiveConversation();
|
updateActiveConversation();
|
||||||
addAttackChainButton(currentConversationId);
|
addAttackChainButton(currentConversationId);
|
||||||
updateProgressConversation(progressId, responseData.conversationId);
|
updateProgressConversation(progressId, responseData.conversationId);
|
||||||
@@ -1552,6 +1584,7 @@ function handleStreamEvent(event, progressElement, progressId,
|
|||||||
}
|
}
|
||||||
|
|
||||||
currentConversationId = responseData.conversationId;
|
currentConversationId = responseData.conversationId;
|
||||||
|
syncAgentLiveStreamConversationId(responseData.conversationId);
|
||||||
updateActiveConversation();
|
updateActiveConversation();
|
||||||
addAttackChainButton(currentConversationId);
|
addAttackChainButton(currentConversationId);
|
||||||
updateProgressConversation(progressId, responseData.conversationId);
|
updateProgressConversation(progressId, responseData.conversationId);
|
||||||
@@ -1682,6 +1715,7 @@ function handleStreamEvent(event, progressElement, progressId,
|
|||||||
// 更新对话ID
|
// 更新对话ID
|
||||||
if (event.data && event.data.conversationId) {
|
if (event.data && event.data.conversationId) {
|
||||||
currentConversationId = event.data.conversationId;
|
currentConversationId = event.data.conversationId;
|
||||||
|
syncAgentLiveStreamConversationId(event.data.conversationId);
|
||||||
updateActiveConversation();
|
updateActiveConversation();
|
||||||
addAttackChainButton(currentConversationId);
|
addAttackChainButton(currentConversationId);
|
||||||
updateProgressConversation(progressId, event.data.conversationId);
|
updateProgressConversation(progressId, event.data.conversationId);
|
||||||
@@ -1982,90 +2016,120 @@ async function refreshLastAssistantProcessDetails(conversationId) {
|
|||||||
|
|
||||||
window.refreshLastAssistantProcessDetails = refreshLastAssistantProcessDetails;
|
window.refreshLastAssistantProcessDetails = refreshLastAssistantProcessDetails;
|
||||||
|
|
||||||
|
const taskEventReplayAttachState = {
|
||||||
|
conversationId: null,
|
||||||
|
inFlightPromise: null
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 订阅运行中任务的 SSE 镜像(GET /api/agent-loop/task-events),用于 HITL 通过后主连接已断开时接续 UI。
|
* 订阅运行中任务的 SSE 镜像(GET /api/agent-loop/task-events),用于 HITL 通过后主连接已断开时接续 UI。
|
||||||
*/
|
*/
|
||||||
async function attachRunningTaskEventStream(conversationId) {
|
async function attachRunningTaskEventStream(conversationId) {
|
||||||
if (!conversationId || typeof apiFetch !== 'function') return false;
|
if (!conversationId || typeof apiFetch !== 'function') return false;
|
||||||
try {
|
if (
|
||||||
const check = await apiFetch('/api/agent-loop/tasks');
|
taskEventReplayAttachState.inFlightPromise &&
|
||||||
if (!check.ok) return false;
|
taskEventReplayAttachState.conversationId === conversationId
|
||||||
const j = await check.json().catch(function () { return {}; });
|
) {
|
||||||
const active = (j.tasks || []).some(function (t) {
|
return taskEventReplayAttachState.inFlightPromise;
|
||||||
return t && t.conversationId === conversationId && (t.status === 'running' || t.status === 'cancelling');
|
}
|
||||||
});
|
if (shouldSkipTaskEventReplayAttach(conversationId)) {
|
||||||
if (!active) return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const asEl = findLastAssistantMessageElInChat();
|
const attachPromise = (async function () {
|
||||||
if (!asEl || !asEl.id) return false;
|
try {
|
||||||
const backendId = asEl.dataset && asEl.dataset.backendMessageId;
|
const check = await apiFetch('/api/agent-loop/tasks');
|
||||||
if (backendId && typeof renderProcessDetails === 'function') {
|
if (!check.ok) return false;
|
||||||
const res = await apiFetch('/api/messages/' + encodeURIComponent(String(backendId)) + '/process-details');
|
const j = await check.json().catch(function () { return {}; });
|
||||||
const jd = await res.json().catch(function () { return {}; });
|
const active = (j.tasks || []).some(function (t) {
|
||||||
if (res.ok && Array.isArray(jd.processDetails)) {
|
return t && t.conversationId === conversationId && (t.status === 'running' || t.status === 'cancelling');
|
||||||
renderProcessDetails(asEl.id, jd.processDetails);
|
});
|
||||||
}
|
if (!active) return false;
|
||||||
}
|
|
||||||
expandProcessDetailsTimeline(asEl.id);
|
|
||||||
|
|
||||||
const progressId = taskReplayProgressId(conversationId);
|
const asEl = findLastAssistantMessageElInChat();
|
||||||
beginCsTaskReplay(progressId, asEl.id, conversationId);
|
if (!asEl || !asEl.id) return false;
|
||||||
|
const backendId = asEl.dataset && asEl.dataset.backendMessageId;
|
||||||
const url = '/api/agent-loop/task-events?conversationId=' + encodeURIComponent(conversationId);
|
if (backendId && typeof renderProcessDetails === 'function') {
|
||||||
const response = await apiFetch(url, {
|
const res = await apiFetch('/api/messages/' + encodeURIComponent(String(backendId)) + '/process-details');
|
||||||
method: 'GET',
|
const jd = await res.json().catch(function () { return {}; });
|
||||||
headers: { Accept: 'text/event-stream' }
|
if (res.ok && Array.isArray(jd.processDetails)) {
|
||||||
});
|
renderProcessDetails(asEl.id, jd.processDetails);
|
||||||
if (!response.ok) {
|
// renderProcessDetails 会重建时间线节点,需重新挂载 HITL 审批入口
|
||||||
clearCsTaskReplay();
|
if (typeof window.restoreHitlInlineForConversation === 'function') {
|
||||||
if (progressTaskState.has(progressId)) {
|
await window.restoreHitlInlineForConversation(conversationId);
|
||||||
progressTaskState.delete(progressId);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mcpIds = [];
|
|
||||||
const assistantDomId = asEl.id;
|
|
||||||
const getAssistantIdFn = function () { return assistantDomId; };
|
|
||||||
const setAssistantIdFn = function () {};
|
|
||||||
|
|
||||||
const reader = response.body.getReader();
|
|
||||||
const decoder = new TextDecoder();
|
|
||||||
let buffer = '';
|
|
||||||
while (true) {
|
|
||||||
const chunk = await reader.read();
|
|
||||||
if (chunk.done) break;
|
|
||||||
buffer += decoder.decode(chunk.value, { stream: true });
|
|
||||||
const lines = buffer.split('\n');
|
|
||||||
buffer = lines.pop() || '';
|
|
||||||
for (let li = 0; li < lines.length; li++) {
|
|
||||||
const line = lines[li];
|
|
||||||
if (line.indexOf('data: ') === 0) {
|
|
||||||
try {
|
|
||||||
const eventData = JSON.parse(line.slice(6));
|
|
||||||
handleStreamEvent(eventData, null, progressId, getAssistantIdFn, setAssistantIdFn, function () { return mcpIds; }, function (ids) { mcpIds = ids; });
|
|
||||||
} catch (e) {
|
|
||||||
console.error('task-events parse', e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
expandProcessDetailsTimeline(asEl.id);
|
||||||
if (window.csTaskReplay && window.csTaskReplay.progressId === progressId) {
|
|
||||||
|
const progressId = taskReplayProgressId(conversationId);
|
||||||
|
beginCsTaskReplay(progressId, asEl.id, conversationId);
|
||||||
|
|
||||||
|
const url = '/api/agent-loop/task-events?conversationId=' + encodeURIComponent(conversationId);
|
||||||
|
const response = await apiFetch(url, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: { Accept: 'text/event-stream' }
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
clearCsTaskReplay();
|
||||||
|
if (progressTaskState.has(progressId)) {
|
||||||
|
progressTaskState.delete(progressId);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mcpIds = [];
|
||||||
|
const assistantDomId = asEl.id;
|
||||||
|
const getAssistantIdFn = function () { return assistantDomId; };
|
||||||
|
const setAssistantIdFn = function () {};
|
||||||
|
|
||||||
|
const reader = response.body.getReader();
|
||||||
|
const decoder = new TextDecoder();
|
||||||
|
let buffer = '';
|
||||||
|
while (true) {
|
||||||
|
const chunk = await reader.read();
|
||||||
|
if (chunk.done) break;
|
||||||
|
buffer += decoder.decode(chunk.value, { stream: true });
|
||||||
|
const lines = buffer.split('\n');
|
||||||
|
buffer = lines.pop() || '';
|
||||||
|
for (let li = 0; li < lines.length; li++) {
|
||||||
|
const line = lines[li];
|
||||||
|
if (line.indexOf('data: ') === 0) {
|
||||||
|
try {
|
||||||
|
const eventData = JSON.parse(line.slice(6));
|
||||||
|
handleStreamEvent(eventData, null, progressId, getAssistantIdFn, setAssistantIdFn, function () { return mcpIds; }, function (ids) { mcpIds = ids; });
|
||||||
|
} catch (e) {
|
||||||
|
console.error('task-events parse', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (window.csTaskReplay && window.csTaskReplay.progressId === progressId) {
|
||||||
|
clearCsTaskReplay();
|
||||||
|
}
|
||||||
|
if (progressTaskState.has(progressId)) {
|
||||||
|
finalizeProgressTask(progressId, typeof window.t === 'function' ? window.t('tasks.statusCompleted') : '已完成');
|
||||||
|
}
|
||||||
|
if (typeof loadActiveTasks === 'function') loadActiveTasks();
|
||||||
|
if (typeof window.loadConversation === 'function' && window.currentConversationId === conversationId) {
|
||||||
|
await window.loadConversation(conversationId);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('attachRunningTaskEventStream', e);
|
||||||
clearCsTaskReplay();
|
clearCsTaskReplay();
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
if (taskEventReplayAttachState.inFlightPromise === attachPromise) {
|
||||||
|
taskEventReplayAttachState.inFlightPromise = null;
|
||||||
|
taskEventReplayAttachState.conversationId = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (progressTaskState.has(progressId)) {
|
})();
|
||||||
finalizeProgressTask(progressId, typeof window.t === 'function' ? window.t('tasks.statusCompleted') : '已完成');
|
|
||||||
}
|
taskEventReplayAttachState.conversationId = conversationId;
|
||||||
if (typeof loadActiveTasks === 'function') loadActiveTasks();
|
taskEventReplayAttachState.inFlightPromise = attachPromise;
|
||||||
if (typeof window.loadConversation === 'function' && window.currentConversationId === conversationId) {
|
return attachPromise;
|
||||||
await window.loadConversation(conversationId);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} catch (e) {
|
|
||||||
console.warn('attachRunningTaskEventStream', e);
|
|
||||||
clearCsTaskReplay();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
window.attachRunningTaskEventStream = attachRunningTaskEventStream;
|
window.attachRunningTaskEventStream = attachRunningTaskEventStream;
|
||||||
|
|||||||
Reference in New Issue
Block a user