mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-10-20 06:27:53 +02:00
125 lines
5.3 KiB
JavaScript
125 lines
5.3 KiB
JavaScript
// Compressed context remains separate from attack nodes and original user authorization.
|
|
(function () {
|
|
'use strict';
|
|
|
|
let labels = [];
|
|
|
|
function translate(key, values) {
|
|
if (typeof window.t === 'function') return window.t('attackChainModal.' + key, values);
|
|
const fallback = {
|
|
contextTitle: '压缩摘要与证据线索 ({{count}})',
|
|
contextNotice: '当前会话轨迹中的系统生成摘要,可能晚于图的生成时间。仅供回溯与检索,不代表用户的新授权或已核实工具证据。可按摘要内的快照路径读取原始证据。',
|
|
contextSummary: '压缩摘要 {{number}}',
|
|
contextCopy: '复制完整摘要',
|
|
contextCopied: '已复制',
|
|
contextCopyFailed: '复制失败,可选中文本手动复制。'
|
|
};
|
|
let text = fallback[key] || key;
|
|
Object.entries(values || {}).forEach(([name, value]) => {
|
|
text = text.replace('{{' + name + '}}', String(value));
|
|
});
|
|
return text;
|
|
}
|
|
|
|
function label(element, key, values) {
|
|
const refresh = () => { element.textContent = translate(key, values); };
|
|
labels.push(refresh);
|
|
refresh();
|
|
}
|
|
|
|
function clear() {
|
|
labels = [];
|
|
const card = document.getElementById('attack-chain-context');
|
|
const contents = document.getElementById('attack-chain-context-content');
|
|
if (contents) contents.replaceChildren();
|
|
if (card) {
|
|
card.hidden = true;
|
|
card.open = false;
|
|
}
|
|
}
|
|
|
|
async function copyText(text) {
|
|
if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
|
|
try {
|
|
await navigator.clipboard.writeText(text);
|
|
return;
|
|
} catch (_) {
|
|
// Plain HTTP deployments may deny clipboard access; retain manual-copy fallback.
|
|
}
|
|
}
|
|
const previousFocus = document.activeElement;
|
|
const textarea = document.createElement('textarea');
|
|
textarea.value = text;
|
|
textarea.readOnly = true;
|
|
textarea.style.position = 'fixed';
|
|
textarea.style.opacity = '0';
|
|
document.body.appendChild(textarea);
|
|
try {
|
|
textarea.focus();
|
|
textarea.select();
|
|
if (!document.execCommand('copy')) throw new Error('copy failed');
|
|
} finally {
|
|
textarea.remove();
|
|
if (previousFocus && typeof previousFocus.focus === 'function') {
|
|
previousFocus.focus({ preventScroll: true });
|
|
}
|
|
}
|
|
}
|
|
|
|
function render(chainData) {
|
|
clear();
|
|
const card = document.getElementById('attack-chain-context');
|
|
const contents = document.getElementById('attack-chain-context-content');
|
|
if (!card || !contents) return;
|
|
|
|
// Only explicit server provenance qualifies; summary-looking user text stays user text.
|
|
const summaries = Array.isArray(chainData && chainData.context_summaries)
|
|
? chainData.context_summaries.filter(item => item &&
|
|
item.provenance === 'system_generated_summary' &&
|
|
typeof item.content === 'string' && item.content.trim())
|
|
: [];
|
|
if (!summaries.length) return;
|
|
|
|
label(document.getElementById('attack-chain-context-title'), 'contextTitle', { count: summaries.length });
|
|
label(document.getElementById('attack-chain-context-notice'), 'contextNotice');
|
|
summaries.forEach((item, index) => {
|
|
const entry = document.createElement('details');
|
|
entry.className = 'attack-chain-context-entry';
|
|
const heading = document.createElement('summary');
|
|
label(heading, 'contextSummary', { number: index + 1 });
|
|
const body = document.createElement('pre');
|
|
body.className = 'attack-chain-context-body';
|
|
// Do not render Markdown/HTML or turn evidence paths into clickable URLs.
|
|
body.textContent = item.content;
|
|
const button = document.createElement('button');
|
|
button.type = 'button';
|
|
button.className = 'btn-secondary attack-chain-context-copy';
|
|
label(button, 'contextCopy');
|
|
const status = document.createElement('span');
|
|
status.className = 'attack-chain-context-copy-status';
|
|
status.setAttribute('role', 'status');
|
|
let statusKey = '';
|
|
labels.push(() => { status.textContent = statusKey ? translate(statusKey) : ''; });
|
|
button.addEventListener('click', async () => {
|
|
button.disabled = true;
|
|
status.textContent = '';
|
|
try {
|
|
await copyText(item.content);
|
|
statusKey = 'contextCopied';
|
|
} catch (_) {
|
|
statusKey = 'contextCopyFailed';
|
|
} finally {
|
|
status.textContent = translate(statusKey);
|
|
button.disabled = false;
|
|
}
|
|
});
|
|
entry.append(heading, body, button, status);
|
|
contents.appendChild(entry);
|
|
});
|
|
card.hidden = false;
|
|
}
|
|
|
|
document.addEventListener('languagechange', () => labels.forEach(refresh => refresh()));
|
|
window.CyberStrikeAttackChainContext = { render, clear };
|
|
})();
|