feat(chat): Gedanken-Stream (App + Diagnostic)
Persistentes chronologisches Log was ARIA intern macht — gefuettert aus agent_activity-Events (thinking/tool/assistant/idle). Bleibt zwischen Denk-Phasen stehen, neue Eintraege kommen unten dran, lange Pausen werden mit Trennlinie + Minuten-Hint sichtbar gemacht. App (ChatScreen.tsx): - 💭-Icon in der Statusleiste neben 🗂️ und 🔍, zeigt Eintrags-Anzahl - Bottom-Sheet (60% Hoehe) mit chronologischer Liste, Tap auf Hintergrund schliesst, 🗑-Confirm zum Leeren - Persistierung in AsyncStorage (aria_thought_stream, capped 500) - Dedup gegen direkt aufeinanderfolgende identische Events Diagnostic (index.html): - 💭 Gedanken-Button im Chat-Test-Header neben „Vollbild" - Zentrales Modal (720px x 70vh), Live-Update wenn neue Eintraege kommen (autoscroll ans Ende), 🗑 Leeren-Button mit Confirm - Persistierung in localStorage, gleiche cap/dedup-Logik wie App Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -301,6 +301,7 @@
|
||||
<input type="checkbox" id="gps-debug-toggle" onchange="toggleGpsDebug()" style="margin-right:4px;vertical-align:middle;">
|
||||
GPS-Position einblenden
|
||||
</label>
|
||||
<button class="btn secondary" onclick="openThoughtStream()" id="btn-thoughts" title="Gedanken-Stream — was ARIA intern tut" style="padding:4px 10px;font-size:11px;">💭 Gedanken <span id="thoughts-count" style="color:#8888AA;"></span></button>
|
||||
<button class="btn secondary" onclick="toggleChatFullscreen()" id="btn-chat-fs" style="padding:4px 10px;font-size:11px;">Vollbild</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -342,6 +343,22 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Gedanken-Stream Modal — chronologisches Log was ARIA intern tut.
|
||||
Zentrales Modal (max 720px breit), Liste mit Auto-Scroll ans Ende
|
||||
wenn neue Eintraege reinkommen. -->
|
||||
<div id="thought-stream-modal" style="display:none;position:fixed;top:0;left:0;width:100vw;height:100vh;background:rgba(0,0,0,0.7);z-index:1100;align-items:center;justify-content:center;padding:24px;" onclick="if(event.target===this) closeThoughtStream();">
|
||||
<div style="background:#0D0D1A;border:1px solid #1E1E2E;border-radius:12px;width:100%;max-width:720px;height:70vh;display:flex;flex-direction:column;">
|
||||
<div style="display:flex;align-items:center;padding:14px;border-bottom:1px solid #1E1E2E;">
|
||||
<h2 style="margin:0;color:#FFD60A;flex:1;font-size:16px;">💭 Gedanken-Stream <span id="thoughts-count-modal" style="color:#8888AA;font-weight:normal;"></span></h2>
|
||||
<button class="btn secondary" onclick="clearThoughtStream()" id="btn-clear-thoughts" title="Stream leeren" style="padding:4px 10px;font-size:11px;color:#FF3B30;border-color:#FF3B30;margin-right:6px;">🗑 Leeren</button>
|
||||
<button class="btn secondary" onclick="closeThoughtStream()" style="padding:4px 12px;">Schliessen</button>
|
||||
</div>
|
||||
<div id="thought-stream-list" style="flex:1;overflow-y:auto;padding:8px 0;font-size:13px;font-family:monospace;">
|
||||
<!-- gefuellt durch renderThoughtStream() -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sessions + alter Brain-Viewer entfernt — Memories laufen jetzt
|
||||
komplett ueber den Gehirn-Tab gegen die Vector-DB im aria-brain. -->
|
||||
|
||||
@@ -2166,6 +2183,9 @@
|
||||
}
|
||||
|
||||
function updateThinkingIndicator(msg) {
|
||||
// Gedanken-Stream fuettern — JEDES Event (auch idle als ✓ fertig)
|
||||
pushThought(msg.activity || '', msg.tool || '');
|
||||
|
||||
const indicators = [
|
||||
document.getElementById('thinking-indicator'),
|
||||
document.getElementById('thinking-indicator-fs'),
|
||||
@@ -2202,6 +2222,112 @@
|
||||
}, 120000);
|
||||
}
|
||||
|
||||
// ── Gedanken-Stream ─────────────────────────────
|
||||
// Chronologisches Log von agent_activity-Events. Wird in localStorage
|
||||
// persistiert (ueberlebt Page-Reload), capped auf MAX_THOUGHTS.
|
||||
const THOUGHT_STORAGE_KEY = 'aria_thought_stream';
|
||||
const MAX_THOUGHTS = 500;
|
||||
let thoughtStream = [];
|
||||
let lastThoughtKey = '';
|
||||
let _thoughtSaveTimer = null;
|
||||
|
||||
function loadThoughtStream() {
|
||||
try {
|
||||
const raw = localStorage.getItem(THOUGHT_STORAGE_KEY);
|
||||
if (!raw) return;
|
||||
const parsed = JSON.parse(raw);
|
||||
if (Array.isArray(parsed)) thoughtStream = parsed.slice(-MAX_THOUGHTS);
|
||||
} catch {}
|
||||
updateThoughtsBadge();
|
||||
}
|
||||
|
||||
function persistThoughtStream() {
|
||||
if (_thoughtSaveTimer) clearTimeout(_thoughtSaveTimer);
|
||||
_thoughtSaveTimer = setTimeout(() => {
|
||||
try {
|
||||
if (thoughtStream.length === 0) localStorage.removeItem(THOUGHT_STORAGE_KEY);
|
||||
else localStorage.setItem(THOUGHT_STORAGE_KEY, JSON.stringify(thoughtStream.slice(-MAX_THOUGHTS)));
|
||||
} catch {}
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function pushThought(activity, tool) {
|
||||
// Dedup gegen direkt aufeinanderfolgende identische Events
|
||||
const key = `${activity}|${tool || ''}`;
|
||||
if (key === lastThoughtKey) return;
|
||||
lastThoughtKey = key;
|
||||
thoughtStream.push({ ts: Date.now(), activity, tool: tool || '' });
|
||||
if (thoughtStream.length > MAX_THOUGHTS) thoughtStream = thoughtStream.slice(-MAX_THOUGHTS);
|
||||
updateThoughtsBadge();
|
||||
// Wenn das Modal offen ist: live nachrendern + ans Ende scrollen
|
||||
const modal = document.getElementById('thought-stream-modal');
|
||||
if (modal && modal.style.display !== 'none') renderThoughtStream(true);
|
||||
persistThoughtStream();
|
||||
}
|
||||
|
||||
function updateThoughtsBadge() {
|
||||
const a = document.getElementById('thoughts-count');
|
||||
if (a) a.textContent = thoughtStream.length ? `(${thoughtStream.length})` : '';
|
||||
const b = document.getElementById('thoughts-count-modal');
|
||||
if (b) b.textContent = thoughtStream.length ? `(${thoughtStream.length})` : '';
|
||||
}
|
||||
|
||||
function openThoughtStream() {
|
||||
const modal = document.getElementById('thought-stream-modal');
|
||||
if (!modal) return;
|
||||
modal.style.display = 'flex';
|
||||
renderThoughtStream(true);
|
||||
}
|
||||
|
||||
function closeThoughtStream() {
|
||||
const modal = document.getElementById('thought-stream-modal');
|
||||
if (modal) modal.style.display = 'none';
|
||||
}
|
||||
|
||||
function clearThoughtStream() {
|
||||
if (thoughtStream.length === 0) return;
|
||||
if (!confirm(`Gedanken-Stream leeren? ${thoughtStream.length} Eintraege werden geloescht.`)) return;
|
||||
thoughtStream = [];
|
||||
lastThoughtKey = '';
|
||||
updateThoughtsBadge();
|
||||
renderThoughtStream(false);
|
||||
persistThoughtStream();
|
||||
}
|
||||
|
||||
function _escapeHtml(s) {
|
||||
return String(s).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
||||
}
|
||||
|
||||
function renderThoughtStream(autoscroll) {
|
||||
const list = document.getElementById('thought-stream-list');
|
||||
if (!list) return;
|
||||
if (thoughtStream.length === 0) {
|
||||
list.innerHTML = '<div style="padding:24px;text-align:center;color:#555570;font-style:italic;">Noch keine Gedanken aufgezeichnet.<br>Sobald ARIA was tut, taucht\'s hier auf.</div>';
|
||||
return;
|
||||
}
|
||||
const rows = [];
|
||||
let prevTs = 0;
|
||||
for (const t of thoughtStream) {
|
||||
const gapMin = prevTs ? Math.floor((t.ts - prevTs) / 60000) : 0;
|
||||
if (gapMin >= 1) {
|
||||
const label = gapMin < 60 ? `${gapMin} Min` : `${Math.floor(gapMin/60)}h ${gapMin%60}m`;
|
||||
rows.push(`<div style="display:flex;align-items:center;padding:6px 16px;gap:8px;"><div style="flex:1;height:1px;background:#1E1E2E;"></div><span style="color:#555570;font-size:10px;">${label}</span><div style="flex:1;height:1px;background:#1E1E2E;"></div></div>`);
|
||||
}
|
||||
prevTs = t.ts;
|
||||
const d = new Date(t.ts);
|
||||
const time = `${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}:${String(d.getSeconds()).padStart(2,'0')}`;
|
||||
let icon, label, color;
|
||||
if (t.activity === 'idle') { icon = '✓'; label = 'fertig'; color = '#34C759'; }
|
||||
else if (t.activity === 'tool') { icon = '🔧'; label = t.tool || 'tool'; color = '#E0E0F0'; }
|
||||
else if (t.activity === 'assistant'){ icon = '✍️'; label = 'schreibt'; color = '#E0E0F0'; }
|
||||
else if (t.activity === 'thinking'){ icon = '💭'; label = 'denkt'; color = '#E0E0F0'; }
|
||||
else { icon = '•'; label = t.activity; color = '#E0E0F0'; }
|
||||
rows.push(`<div style="display:flex;padding:4px 16px;align-items:baseline;"><span style="color:#555570;width:78px;font-size:11px;">${time}</span><span style="width:24px;">${icon}</span><span style="color:${color};flex:1;">${_escapeHtml(label)}</span></div>`);
|
||||
}
|
||||
list.innerHTML = rows.join('');
|
||||
if (autoscroll) list.scrollTop = list.scrollHeight;
|
||||
}
|
||||
|
||||
// ── XTTS Panel ─────────────────────────────
|
||||
function renderVoiceList(voices) {
|
||||
const box = document.getElementById('xtts-voice-list');
|
||||
@@ -4696,6 +4822,7 @@
|
||||
});
|
||||
}
|
||||
|
||||
loadThoughtStream();
|
||||
connectWS();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user