This commit is contained in:
@@ -47,3 +47,6 @@ body.honeycomb-view .legend{opacity:.58}body.honeycomb-view .mode-status small:a
|
||||
@media(max-width:780px){.settings-panel{left:10px;width:auto}.dock{max-width:calc(100vw - 20px)}.dock-separator{display:none}}
|
||||
|
||||
.performance-settings{flex:0 0 auto}.number-setting{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:10px;margin:7px 0;border:1px solid rgba(133,200,255,.1);border-radius:12px;background:rgba(255,255,255,.022)}.number-setting b{display:block;font-size:11px}.number-setting small{display:block;margin-top:3px;max-width:250px;color:#718b9e;font-size:9px;line-height:1.4}.number-setting input{width:112px;flex:0 0 auto;border:1px solid rgba(82,231,255,.2);background:rgba(2,8,17,.72);color:var(--text);border-radius:10px;padding:9px 8px;text-align:right;font-variant-numeric:tabular-nums;outline:0}.number-setting input:focus{border-color:rgba(82,231,255,.48);box-shadow:0 0 0 3px rgba(82,231,255,.07)}.node-limit-presets{display:flex;flex-wrap:wrap;gap:5px;margin-top:8px}.node-limit-presets button{border:1px solid rgba(133,200,255,.12);background:rgba(255,255,255,.025);color:#7893a6;border-radius:999px;padding:5px 8px;font-size:8px;cursor:pointer}.node-limit-presets button:hover{color:var(--cyan);border-color:rgba(82,231,255,.3);background:rgba(82,231,255,.08)}.setting-hint{margin:8px 2px 0;color:#6f899d;font-size:9px;line-height:1.4}
|
||||
.activity-item .sources{display:grid;gap:4px;margin-top:7px;padding-top:6px;border-top:1px solid rgba(93,255,189,.1)}
|
||||
.activity-item .sources span{display:flex;align-items:flex-start;gap:6px;color:#b7d9ca;font-size:9px;line-height:1.35}
|
||||
.activity-item .sources i{display:inline-flex;align-items:center;justify-content:center;flex:0 0 15px;height:15px;border-radius:50%;border:1px solid rgba(93,255,189,.24);background:rgba(93,255,189,.07);color:#86ffd0;font-style:normal;font-size:8px}
|
||||
|
||||
+268
-4
@@ -43,7 +43,8 @@
|
||||
edgeRenderMap: new Map(), renderActive: new Map(), renderEdgeActive: new Map(), renderStats: {nodes: 0, edges: 0, hiddenNodes: 0, hiddenEdges: 0},
|
||||
fullSnapshot: null, fullNodeById: new Map(), runtimeSettings: {learning_enabled: true, thinking_enabled: true, learning_categories: [], display_categories: [], thinking_categories: [], view_mode: 'neural', max_display_nodes: 0},
|
||||
availableCategories: [], viewMode: 'neural', honeycombNodes: [], honeycombSpacing: 0, settingsOpen: false, settingsDraft: null,
|
||||
forcedDisplayUntil: new Map(), nextDisplayLimitExpiry: 0, graphVersion: null, displaySignature: '', displayLimitStats: {limit: 0, eligible: 0, shown: 0}
|
||||
forcedDisplayUntil: new Map(), nextDisplayLimitExpiry: 0, graphVersion: null, displaySignature: '', displayLimitStats: {limit: 0, eligible: 0, shown: 0},
|
||||
researchAnimations: new Map(), researchSequence: 0
|
||||
};
|
||||
|
||||
function resize() {
|
||||
@@ -1236,6 +1237,259 @@
|
||||
return rgba((MODE_CONFIG[mode] || MODE_CONFIG.living).color, 0.92);
|
||||
}
|
||||
|
||||
function researchEventID(evt) {
|
||||
return String(evt?.metadata?.research_id || `${evt?.type || 'research'}:${evt?.metadata?.research_query || evt?.query || ++state.researchSequence}`);
|
||||
}
|
||||
|
||||
function researchResultIDs(evt) {
|
||||
const explicit = Array.isArray(evt?.metadata?.result_node_ids) ? evt.metadata.result_node_ids : [];
|
||||
if (explicit.length) return explicit.map(String);
|
||||
return (evt?.node_ids || []).filter(id => state.fullNodeById.get(id)?.kind === 'external' || state.nodeById.get(id)?.kind === 'external');
|
||||
}
|
||||
|
||||
function researchSourceIDs(evt) {
|
||||
const explicit = Array.isArray(evt?.metadata?.source_node_ids) ? evt.metadata.source_node_ids : [];
|
||||
if (explicit.length) return explicit.map(String);
|
||||
const resultIDs = new Set(researchResultIDs(evt));
|
||||
return (evt?.node_ids || []).filter(id => !resultIDs.has(id));
|
||||
}
|
||||
|
||||
function brainViewportCenter() {
|
||||
const panelOffset = state.width > 1000 ? 110 : state.width > 780 ? 60 : 0;
|
||||
return {x: state.width / 2 + panelOffset, y: state.height / 2 - 4};
|
||||
}
|
||||
|
||||
function visibleScreenForNode(id) {
|
||||
const visibleID = state.visibleForNode.get(id);
|
||||
const renderNode = visibleID ? state.renderNodeById.get(visibleID) : null;
|
||||
if (renderNode?.screen) return renderNode.screen;
|
||||
const node = state.nodeById.get(id);
|
||||
if (node?.screen) return node.screen;
|
||||
return null;
|
||||
}
|
||||
|
||||
function averageScreen(ids, fallback) {
|
||||
let x = 0, y = 0, count = 0;
|
||||
for (const id of ids || []) {
|
||||
const point = visibleScreenForNode(id);
|
||||
if (!point) continue;
|
||||
x += point.x;
|
||||
y += point.y;
|
||||
count++;
|
||||
}
|
||||
return count ? {x: x / count, y: y / count} : fallback;
|
||||
}
|
||||
|
||||
function researchSourcePoints(animation, count) {
|
||||
const desired = Math.max(3, Math.min(7, count || 5));
|
||||
if (animation.sourcePoints?.length === desired) return animation.sourcePoints;
|
||||
const center = brainViewportCenter();
|
||||
const rx = Math.min(state.width * 0.34, state.height * 0.43);
|
||||
const ry = Math.min(state.height * 0.39, state.width * 0.28);
|
||||
const seed = hashString(animation.id);
|
||||
animation.sourcePoints = Array.from({length: desired}, (_, index) => {
|
||||
const angle = (index / desired) * Math.PI * 2 + (seed % 1000) / 1000 * Math.PI * 2;
|
||||
return {
|
||||
x: center.x + Math.cos(angle) * rx * (0.9 + pseudo(animation.id, index + 40) * 0.18),
|
||||
y: center.y + Math.sin(angle) * ry * (0.88 + pseudo(animation.id, index + 70) * 0.2),
|
||||
phase: pseudo(animation.id, index + 100) * Math.PI * 2
|
||||
};
|
||||
});
|
||||
return animation.sourcePoints;
|
||||
}
|
||||
|
||||
function handleResearchAnimation(evt) {
|
||||
if (!evt?.type?.includes('research')) return;
|
||||
const timestamp = Date.parse(evt.timestamp || '') || Date.now();
|
||||
const id = researchEventID(evt);
|
||||
let animation = state.researchAnimations.get(id);
|
||||
if (!animation && Date.now() - timestamp > 30000) return;
|
||||
const now = performance.now();
|
||||
if (!animation) {
|
||||
animation = {
|
||||
id,
|
||||
stage: 'searching',
|
||||
startedAt: now,
|
||||
updatedAt: now,
|
||||
until: Number.POSITIVE_INFINITY,
|
||||
query: String(evt.metadata?.research_query || evt.query || ''),
|
||||
sourceNodeIDs: researchSourceIDs(evt),
|
||||
resultNodeIDs: [],
|
||||
resultCount: 0,
|
||||
titles: [],
|
||||
domains: [],
|
||||
sourcePoints: [],
|
||||
fallbackAnchor: brainViewportCenter()
|
||||
};
|
||||
state.researchAnimations.set(id, animation);
|
||||
}
|
||||
animation.updatedAt = now;
|
||||
animation.query = String(evt.metadata?.research_query || animation.query || '');
|
||||
const sourceIDs = researchSourceIDs(evt);
|
||||
if (sourceIDs.length) animation.sourceNodeIDs = [...new Set([...(animation.sourceNodeIDs || []), ...sourceIDs])];
|
||||
const minMS = Math.max(2000, Number(evt.metadata?.animation_min_ms || 2000));
|
||||
if (evt.type.endsWith('.started')) {
|
||||
animation.stage = 'searching';
|
||||
animation.until = Number.POSITIVE_INFINITY;
|
||||
} else if (evt.type.endsWith('.results')) {
|
||||
animation.resultCount = Number(evt.metadata?.result_count || 0);
|
||||
animation.titles = Array.isArray(evt.metadata?.result_titles) ? evt.metadata.result_titles.map(String).slice(0, 5) : [];
|
||||
animation.domains = Array.isArray(evt.metadata?.result_domains) ? evt.metadata.result_domains.map(String).slice(0, 5) : [];
|
||||
animation.stage = animation.resultCount > 0 ? 'results' : 'empty';
|
||||
animation.resultsAt = now;
|
||||
animation.until = now + minMS;
|
||||
researchSourcePoints(animation, animation.resultCount || 4);
|
||||
} else if (evt.type.endsWith('.ingested')) {
|
||||
const resultIDs = researchResultIDs(evt);
|
||||
animation.resultNodeIDs = [...new Set([...(animation.resultNodeIDs || []), ...resultIDs])];
|
||||
animation.resultCount = Number(evt.metadata?.result_count || animation.resultCount || resultIDs.length);
|
||||
animation.titles = Array.isArray(evt.metadata?.result_titles) ? evt.metadata.result_titles.map(String).slice(0, 5) : animation.titles;
|
||||
animation.domains = Array.isArray(evt.metadata?.result_domains) ? evt.metadata.result_domains.map(String).slice(0, 5) : animation.domains;
|
||||
animation.stage = 'ingested';
|
||||
animation.ingestedAt = now;
|
||||
animation.until = Number.POSITIVE_INFINITY;
|
||||
researchSourcePoints(animation, animation.resultCount || resultIDs.length || 4);
|
||||
refreshResearchNodes(animation, minMS);
|
||||
} else if (evt.type.endsWith('.failed')) {
|
||||
animation.stage = 'failed';
|
||||
animation.error = String(evt.metadata?.error || 'Recherche fehlgeschlagen');
|
||||
animation.until = now + minMS;
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshResearchNodes(animation, minMS) {
|
||||
try {
|
||||
await loadGraph();
|
||||
const ids = animation.resultNodeIDs || [];
|
||||
forceDisplayNodes(ids, Math.max(6000, minMS + 4000));
|
||||
if (state.fullSnapshot && ids.some(id => !state.nodeById.has(id))) applyGraphSnapshot(state.fullSnapshot, true);
|
||||
for (const id of ids) {
|
||||
state.active.set(id, Math.max(state.active.get(id) || 0, 1.15));
|
||||
const visibleID = state.visibleForNode.get(id);
|
||||
if (visibleID) state.renderActive.set(visibleID, Math.max(state.renderActive.get(visibleID) || 0, 1.15));
|
||||
}
|
||||
animation.stage = 'ingested';
|
||||
animation.ingestedAt = performance.now();
|
||||
animation.until = animation.ingestedAt + Math.max(2000, minMS);
|
||||
} catch {
|
||||
animation.until = Math.max(animation.until || 0, performance.now() + Math.max(2000, minMS));
|
||||
}
|
||||
}
|
||||
|
||||
function drawResearchHex(x, y, radius, color, alpha, rotation = 0) {
|
||||
ctx.beginPath();
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const angle = rotation + i / 6 * Math.PI * 2;
|
||||
const px = x + Math.cos(angle) * radius;
|
||||
const py = y + Math.sin(angle) * radius;
|
||||
if (i === 0) ctx.moveTo(px, py); else ctx.lineTo(px, py);
|
||||
}
|
||||
ctx.closePath();
|
||||
ctx.strokeStyle = rgba(color, alpha);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
function renderResearchAnimations(now) {
|
||||
if (!state.researchAnimations.size) return;
|
||||
const green = MODE_CONFIG.researching.color;
|
||||
ctx.save();
|
||||
ctx.globalCompositeOperation = 'screen';
|
||||
for (const [id, animation] of state.researchAnimations) {
|
||||
if (Number.isFinite(animation.until) && now > animation.until) {
|
||||
state.researchAnimations.delete(id);
|
||||
continue;
|
||||
}
|
||||
const anchor = averageScreen(animation.sourceNodeIDs, animation.fallbackAnchor || brainViewportCenter());
|
||||
animation.fallbackAnchor = anchor;
|
||||
const elapsed = Math.max(0, (now - animation.startedAt) / 1000);
|
||||
const pulse = 0.5 + 0.5 * Math.sin(elapsed * 6.2);
|
||||
const searching = animation.stage === 'searching';
|
||||
const failed = animation.stage === 'failed';
|
||||
const empty = animation.stage === 'empty';
|
||||
const color = failed || empty ? [255, 180, 82] : green;
|
||||
const ringAlpha = searching ? 0.34 : 0.22;
|
||||
ctx.lineWidth = 1.1;
|
||||
ctx.setLineDash([4, 8]);
|
||||
for (let ring = 0; ring < 3; ring++) {
|
||||
const radius = 24 + ring * 13 + pulse * 4;
|
||||
ctx.strokeStyle = rgba(color, ringAlpha - ring * 0.055);
|
||||
ctx.beginPath();
|
||||
ctx.arc(anchor.x, anchor.y, radius, elapsed * (0.7 + ring * 0.18), elapsed * (0.7 + ring * 0.18) + Math.PI * (1.15 + ring * 0.12));
|
||||
ctx.stroke();
|
||||
}
|
||||
ctx.setLineDash([]);
|
||||
const sweepAngle = elapsed * 2.7;
|
||||
ctx.strokeStyle = rgba(color, 0.35 + pulse * 0.18);
|
||||
ctx.lineWidth = 0.9;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(anchor.x, anchor.y);
|
||||
ctx.lineTo(anchor.x + Math.cos(sweepAngle) * 58, anchor.y + Math.sin(sweepAngle) * 58);
|
||||
ctx.stroke();
|
||||
|
||||
const sourceCount = searching ? 5 : Math.max(1, animation.resultCount || animation.resultNodeIDs?.length || 1);
|
||||
const sourcePoints = researchSourcePoints(animation, sourceCount);
|
||||
const resultTargets = (animation.resultNodeIDs || []).map(visibleScreenForNode).filter(Boolean);
|
||||
for (let index = 0; index < sourcePoints.length; index++) {
|
||||
const source = sourcePoints[index];
|
||||
const target = resultTargets[index % Math.max(1, resultTargets.length)] || anchor;
|
||||
const sourcePulse = 0.5 + 0.5 * Math.sin(elapsed * 5 + source.phase);
|
||||
ctx.strokeStyle = rgba(color, searching ? 0.055 + sourcePulse * 0.04 : 0.12 + sourcePulse * 0.06);
|
||||
ctx.lineWidth = 0.65;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(source.x, source.y);
|
||||
const mx = (source.x + target.x) / 2 + Math.sin(source.phase) * 24;
|
||||
const my = (source.y + target.y) / 2 - 22;
|
||||
ctx.quadraticCurveTo(mx, my, target.x, target.y);
|
||||
ctx.stroke();
|
||||
drawResearchHex(source.x, source.y, 4.2 + sourcePulse * 1.7, color, 0.28 + sourcePulse * 0.34, elapsed * 0.35 + source.phase);
|
||||
if (!searching && !failed && !empty) {
|
||||
const phase = ((now - (animation.resultsAt || animation.startedAt)) / 760 + index * 0.17) % 1;
|
||||
const eased = phase * phase * (3 - 2 * phase);
|
||||
const packetX = source.x + (target.x - source.x) * eased;
|
||||
const packetY = source.y + (target.y - source.y) * eased - Math.sin(phase * Math.PI) * 26;
|
||||
const glow = ctx.createRadialGradient(packetX, packetY, 0, packetX, packetY, 9);
|
||||
glow.addColorStop(0, 'rgba(255,255,255,.96)');
|
||||
glow.addColorStop(0.22, rgba(color, 0.85));
|
||||
glow.addColorStop(1, rgba(color, 0));
|
||||
ctx.fillStyle = glow;
|
||||
ctx.beginPath();
|
||||
ctx.arc(packetX, packetY, 9, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
|
||||
if (animation.stage === 'ingested') {
|
||||
const settle = Math.max(0, Math.min(1, (now - animation.ingestedAt) / 2000));
|
||||
for (let index = 0; index < resultTargets.length; index++) {
|
||||
const target = resultTargets[index];
|
||||
const radius = 12 + (1 - settle) * 22 + Math.sin(elapsed * 8 + index) * 2;
|
||||
ctx.lineWidth = 1.2;
|
||||
drawResearchHex(target.x, target.y, radius, green, 0.5 * (1 - settle * 0.45), elapsed * 0.5 + index);
|
||||
ctx.strokeStyle = rgba(green, 0.24 * (1 - settle));
|
||||
ctx.beginPath();
|
||||
ctx.arc(target.x, target.y, radius * 1.45, 0, Math.PI * 2);
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
ctx.save();
|
||||
ctx.globalCompositeOperation = 'source-over';
|
||||
const label = searching ? 'SEARXNG · QUELLENSUCHE' : failed ? 'SEARXNG · FEHLER' : empty ? 'SEARXNG · 0 QUELLEN' : animation.stage === 'ingested' ? `SEARXNG · ${animation.resultCount} QUELLEN VERKNÜPFT` : `SEARXNG · ${animation.resultCount} QUELLEN`;
|
||||
ctx.font = '700 9px Inter, system-ui';
|
||||
const width = ctx.measureText(label).width + 16;
|
||||
ctx.fillStyle = 'rgba(2,10,16,.88)';
|
||||
ctx.fillRect(anchor.x - width / 2, anchor.y + 49, width, 18);
|
||||
ctx.strokeStyle = rgba(color, 0.35);
|
||||
ctx.strokeRect(anchor.x - width / 2, anchor.y + 49, width, 18);
|
||||
ctx.fillStyle = rgba(color, 0.95);
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText(label, anchor.x, anchor.y + 58);
|
||||
ctx.restore();
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
function projectAnimatedNode(node, now) {
|
||||
const honeycomb = state.viewMode === 'honeycomb' && Number.isFinite(node.honeyX);
|
||||
const cluster = honeycomb ? null : node.cluster;
|
||||
@@ -1630,6 +1884,7 @@
|
||||
renderParticles(dt);
|
||||
}
|
||||
renderNodes(now, dt);
|
||||
renderResearchAnimations(now);
|
||||
if (state.viewMode === 'neural') {
|
||||
renderWaves(dt);
|
||||
renderCortexLabels();
|
||||
@@ -1650,6 +1905,7 @@
|
||||
requestAnimationFrame(frame);
|
||||
|
||||
function activate(evt) {
|
||||
handleResearchAnimation(evt);
|
||||
const requestedDuration = evt?.type?.includes('think') || evt?.type?.includes('research') ? 45000 : 32000;
|
||||
forceDisplayNodes(evt?.node_ids || [], requestedDuration);
|
||||
const strength = Math.max(0.15, Math.min(1.4, evt.strength || 0.6));
|
||||
@@ -1707,7 +1963,7 @@
|
||||
|
||||
function shouldLog(evt) {
|
||||
if (!evt || evt.type === 'brain.idle' || evt.type === 'node.activated' || evt.type === 'edges.traversed') return false;
|
||||
const important = new Set(['scan.started', 'graph.updated', 'embedding.batch', 'query.started', 'query.completed', 'think.queued', 'think.cycle.started', 'think.cycle.completed', 'think.cycle.failed', 'think.no_candidate', 'think.started', 'think.relation.created', 'think.rejected', 'think.failed', 'think.paused', 'research.started', 'article.plan.started', 'article.plan.skipped', 'article.research.started', 'article.research.failed', 'article.draft.started', 'article.draft.rejected', 'article.created', 'article.duplicate', 'article.skipped', 'article.failed', 'agent.run', 'glpi.kb.synced', 'glpi.kb.failed', 'persistence.flushed', 'persistence.failed']);
|
||||
const important = new Set(['scan.started', 'graph.updated', 'embedding.batch', 'query.started', 'query.completed', 'think.queued', 'think.cycle.started', 'think.cycle.completed', 'think.cycle.failed', 'think.no_candidate', 'think.started', 'think.relation.created', 'think.rejected', 'think.failed', 'think.paused', 'research.started', 'research.results', 'research.ingested', 'research.failed', 'article.plan.started', 'article.plan.skipped', 'article.research.started', 'article.research.results', 'article.research.ingested', 'article.research.failed', 'article.draft.started', 'article.draft.rejected', 'article.created', 'article.duplicate', 'article.skipped', 'article.failed', 'agent.run', 'glpi.kb.synced', 'glpi.kb.failed', 'persistence.flushed', 'persistence.failed']);
|
||||
if (!important.has(evt.type) && !(evt.source === 'agent' || evt.source === 'knowledgebase' || evt.source === 'external' || evt.query)) return false;
|
||||
const fingerprint = `${evt.type}|${evt.message || ''}|${evt.query || ''}|${evt.source || ''}`;
|
||||
const last = state.lastLogFingerprint.get(fingerprint) || 0;
|
||||
@@ -1740,9 +1996,14 @@
|
||||
'think.failed': 'AI-THINK Fehler',
|
||||
'think.paused': 'AI-THINK pausiert',
|
||||
'research.started': 'Relationsrecherche gestartet',
|
||||
'research.results': 'SearXNG-Ergebnisse empfangen',
|
||||
'research.ingested': 'Webquellen im Graph verknüpft',
|
||||
'research.failed': 'Relationsrecherche fehlgeschlagen',
|
||||
'article.plan.started': 'Artikelmehrwert wird geprüft',
|
||||
'article.plan.skipped': 'Artikelsynthese übersprungen',
|
||||
'article.research.started': 'Artikelrecherche gestartet',
|
||||
'article.research.results': 'Artikelquellen gefunden',
|
||||
'article.research.ingested': 'Artikelquellen verknüpft',
|
||||
'article.research.failed': 'Artikelrecherche fehlgeschlagen',
|
||||
'article.draft.started': 'KB-Entwurf wird geschrieben',
|
||||
'article.draft.rejected': 'KB-Entwurf abgelehnt',
|
||||
@@ -1771,6 +2032,8 @@
|
||||
if (evt.metadata?.confidence !== undefined) meta.push(`${Math.round(Number(evt.metadata.confidence) * 100)}% Konfidenz`);
|
||||
if (evt.metadata?.relation_type) meta.push(String(evt.metadata.relation_type));
|
||||
if (evt.metadata?.research_result_count) meta.push(`${Number(evt.metadata.research_result_count)} Webquellen`);
|
||||
if (evt.metadata?.result_count !== undefined) meta.push(`${Number(evt.metadata.result_count)} SearXNG-Treffer`);
|
||||
if (Array.isArray(evt.metadata?.result_domains) && evt.metadata.result_domains.length) meta.push(evt.metadata.result_domains.slice(0, 3).join(' · '));
|
||||
if (evt.metadata?.trigger) meta.push(evt.metadata.trigger === 'manual' ? 'manuell' : 'automatisch');
|
||||
if (evt.metadata?.batch_size) meta.push(`${Number(evt.metadata.batch_size)} Schritte`);
|
||||
if (evt.metadata?.checked !== undefined) meta.push(`${Number(evt.metadata.checked)} geprüft`);
|
||||
@@ -1800,7 +2063,8 @@
|
||||
} else if (evt.type === 'query.started' && evt.query) {
|
||||
message = `${evt.source === 'agent' ? 'Agent' : evt.source === 'knowledgebase' ? 'Knowledgebase' : 'Brain'} verarbeitet eine Anfrage.`;
|
||||
}
|
||||
return {time, title, message, meta, query: eventQuery, regions, cls: evt.type?.includes('think') || evt.type?.startsWith('article.') ? (evt.type?.includes('research') ? 'research' : 'think') : evt.type?.includes('research') ? 'research' : evt.type === 'graph.updated' || evt.type === 'scan.started' || evt.type?.startsWith('glpi.kb') || evt.type?.startsWith('persistence.') ? 'graph' : evt.source === 'agent' ? 'agent' : ''};
|
||||
const sources = Array.isArray(evt.metadata?.result_titles) ? evt.metadata.result_titles.map(String).slice(0, 4) : [];
|
||||
return {time, title, message, meta, query: eventQuery, regions, sources, cls: evt.type?.includes('think') || evt.type?.startsWith('article.') ? (evt.type?.includes('research') ? 'research' : 'think') : evt.type?.includes('research') ? 'research' : evt.type === 'graph.updated' || evt.type === 'scan.started' || evt.type?.startsWith('glpi.kb') || evt.type?.startsWith('persistence.') ? 'graph' : evt.source === 'agent' ? 'agent' : ''};
|
||||
}
|
||||
|
||||
function addLog(evt) {
|
||||
@@ -1808,7 +2072,7 @@
|
||||
const out = formatEvent(evt);
|
||||
const item = document.createElement('div');
|
||||
item.className = 'activity-item ' + out.cls;
|
||||
item.innerHTML = `<b>${escapeHTML(out.title)}</b><time>${out.time}</time><p>${escapeHTML(out.message)}</p>${out.regions.length ? `<div class="region">Cortex: ${out.regions.map(escapeHTML).join(' · ')}</div>` : ''}${out.meta.length ? `<div class="meta">${out.meta.map(v => `<span>${escapeHTML(v)}</span>`).join('')}</div>` : ''}${out.query ? `<div class="query">${escapeHTML(out.query)}</div>` : ''}`;
|
||||
item.innerHTML = `<b>${escapeHTML(out.title)}</b><time>${out.time}</time><p>${escapeHTML(out.message)}</p>${out.regions.length ? `<div class="region">Cortex: ${out.regions.map(escapeHTML).join(' · ')}</div>` : ''}${out.meta.length ? `<div class="meta">${out.meta.map(v => `<span>${escapeHTML(v)}</span>`).join('')}</div>` : ''}${out.sources?.length ? `<div class="sources">${out.sources.map((source, index) => `<span><i>${index + 1}</i>${escapeHTML(source)}</span>`).join('')}</div>` : ''}${out.query ? `<div class="query">${escapeHTML(out.query)}</div>` : ''}`;
|
||||
const log = $('activityLog');
|
||||
log.prepend(item);
|
||||
while (log.children.length > 18) log.removeChild(log.lastChild);
|
||||
|
||||
Reference in New Issue
Block a user