diff --git a/ARCHITECTURE_QUICK.md b/ARCHITECTURE_QUICK.md index 7372489..953934e 100644 --- a/ARCHITECTURE_QUICK.md +++ b/ARCHITECTURE_QUICK.md @@ -361,7 +361,7 @@ Zusätzlich zum Worker kann eine Session ein **Panel** aus mehreren Agenten habe **SSE-Events:** `panel_start` (mode/layers), `panel_layer_start`/`panel_layer_done` (MoA-Runden), `panel_member_start` (mit `layer`), `panel_member_text`/`panel_member_tool_use`/`panel_member_tool_end`/`panel_member_tool` (live), `panel_member_done`, `panel_done`. -**Frontend:** `stream-ui.js` rendert pro Member eine `.panel-bubble`, gekeyt per `layer_index` (MoA: gleiche Member über mehrere Runden; aktive Runde pro Index wird bei `panel_member_start` gemerkt, da Runden sequenziell laufen). `panel_layer_start` zeigt bei >1 Runde einen `.panel-layer-divider`. History: `history.js _renderPanelMemberBubble` rendert `Tag="panel"`-Messages. Picker: `#multiModelMode`-Dropdown (Aus/Fusion/MoA) + `#moaLayers` + Mehrfach-Agenten-Picker (`_openMultiPickerModal`). +**Frontend:** `stream-ui.js` rendert pro Member eine `.panel-bubble`, gekeyt per `layer_index` (MoA: gleiche Member über mehrere Runden; aktive Runde pro Index wird bei `panel_member_start` gemerkt, da Runden sequenziell laufen). `panel_layer_start` zeigt bei >1 Runde einen `.panel-layer-divider`. History: `history.js _renderPanelMemberBubble` rendert `Tag="panel"`-Messages. Picker: `#multiModelMode`-Dropdown (Aus/Fusion/MoA) + `#moaLayers` + Mehrfach-Agenten-Picker (`_openMultiPickerModal`). Vereinter Composer-Toggle: **ein** Button `#planModeBtn` (chat.js `cycleCompositeMode`) schaltet zyklisch Aus→Plan→Fusion→MoA durch. Plan ist transient (Compat-Var `_planModeActive`), Fusion/MoA werden sofort via `apiUpdateSession({multiModelMode})` persistiert – gleiche Quelle wie der Picker. **Web-Search Sources:** - `web_search_result`-Messages in DB gespeichert (Sources als JSON-Array, leere werden übersprungen) diff --git a/wwwroot/css/chat.css b/wwwroot/css/chat.css index de94abb..834331c 100644 --- a/wwwroot/css/chat.css +++ b/wwwroot/css/chat.css @@ -436,12 +436,22 @@ color: var(--dangerous); } -/* Plan Mode Toggle */ +/* Unified Modus-Toggle: Aus / Plan / Fusion / MoA (ein einziger #planModeBtn) */ #planModeBtn.plan-mode-active { color: var(--primary); background: color-mix(in srgb, var(--primary) 15%, transparent); border-radius: var(--border-radius); } +#planModeBtn.panel-mode-fusion { + color: var(--primary); + background: color-mix(in srgb, var(--primary) 15%, transparent); + border-radius: var(--border-radius); +} +#planModeBtn.panel-mode-moa { + color: var(--primary); + background: color-mix(in srgb, var(--primary) 28%, transparent); + border-radius: var(--border-radius); +} /* Emoji Picker */ diff --git a/wwwroot/index.html b/wwwroot/index.html index 0ebc534..dfa54b1 100644 --- a/wwwroot/index.html +++ b/wwwroot/index.html @@ -455,7 +455,7 @@ - + diff --git a/wwwroot/js/chat.js b/wwwroot/js/chat.js index cae2986..1885c47 100644 --- a/wwwroot/js/chat.js +++ b/wwwroot/js/chat.js @@ -223,20 +223,94 @@ function handleStreamEvent(eventType, data) { } } -// ── Plan Mode ── +// ── Unified Modus-Toggle (Aus / Plan / Fusion / MoA) ── +// +// Ein einziger Button (#planModeBtn) schaltet zyklisch durch alle Betriebsmodi: +// Aus → Plan-Modus → Fusion (Panel) → MoA (Panel) → Aus +// +// Plan-Modus ist transient (wird bei Session-Wechsel zurückgesetzt, nie in der Session +// persistiert). Fusion/MoA werden in der Session persistiert (apiUpdateSession) und +// server-seitig aus ctx.MultiModelMode gelesen – daher reicht ein PATCH. +// +// _planModeActive bleibt als Compat-Variable (sendMessage + modals.js lesen sie direkt). -let _planModeActive = false; +let _compositeMode = 'off'; // 'off' | 'plan' | 'fusion' | 'moa' +let _compositeHasAgents = false; -function togglePlanMode() { - _planModeActive = !_planModeActive; +// Compat-Variablen – werden durch cycleCompositeMode / initCompositeMode synchron gehalten +let _planModeActive = false; // true genau wenn _compositeMode === 'plan' + +const _COMPOSITE_ORDER = ['off', 'plan', 'fusion', 'moa']; + +function _renderCompositeBtn() { var btn = $('#planModeBtn'); - if (btn) { - btn.classList.toggle('plan-mode-active', _planModeActive); - btn.title = _planModeActive ? 'Plan-Modus deaktivieren' : 'Plan-Modus aktivieren'; - } + if (!btn) return; + btn.classList.remove('plan-mode-active', 'panel-mode-fusion', 'panel-mode-moa'); + var icon = btn.querySelector('i'); var input = $('#input'); - if (input) { - input.placeholder = _planModeActive ? 'Plannen...' : 'Nachricht...'; + if (_compositeMode === 'plan') { + btn.classList.add('plan-mode-active'); + if (icon) icon.className = 'fa fa-map'; + btn.title = 'Plan-Modus aktiv · klicken → Fusion'; + if (input) input.placeholder = 'Planen...'; + } else if (_compositeMode === 'fusion') { + btn.classList.add('panel-mode-fusion'); + if (icon) icon.className = 'fa fa-users'; + btn.title = 'Fusion aktiv · klicken → MoA'; + if (input) input.placeholder = 'Nachricht...'; + } else if (_compositeMode === 'moa') { + btn.classList.add('panel-mode-moa'); + if (icon) icon.className = 'fa fa-clone'; + btn.title = 'MoA aktiv · klicken → Aus'; + if (input) input.placeholder = 'Nachricht...'; + } else { + if (icon) icon.className = 'fa fa-map'; + btn.title = 'Plan-Modus / Panel umschalten'; + if (input) input.placeholder = 'Nachricht...'; + } +} + +// Initialisiert den Button aus der geladenen Session (bei jedem Chat-Wechsel aufrufen). +// Plan-Modus wird immer auf 'off' zurückgesetzt (transient). +function initCompositeMode(panelMode, hasAgents) { + _compositeHasAgents = !!hasAgents; + _compositeMode = (panelMode === 'fusion' || panelMode === 'moa') ? panelMode : 'off'; + _planModeActive = false; + _renderCompositeBtn(); +} + +// Schaltet Aus → Plan → Fusion → MoA → Aus durch. +// Fusion/MoA werden sofort in die Session persistiert; Plan-Modus ist transient (kein Server-Call). +async function cycleCompositeMode() { + var idx = _COMPOSITE_ORDER.indexOf(_compositeMode); + _compositeMode = _COMPOSITE_ORDER[(idx + 1) % _COMPOSITE_ORDER.length]; + _planModeActive = (_compositeMode === 'plan'); + _renderCompositeBtn(); + + // Nur Fusion/MoA/Off brauchen einen Server-Call (Plan ist transient) + if (_compositeMode !== 'plan') { + var sid = getCurrentSessionId(); + if (sid) { + try { + await apiUpdateSession(sid, { multiModelMode: _compositeMode }); + } catch (e) { + console.error('Modus speichern fehlgeschlagen:', e); + if (typeof showToast === 'function') showToast('Modus konnte nicht gespeichert werden', 'error'); + return; + } + } + } + + if (typeof showToast === 'function') { + if (_compositeMode === 'off') { + showToast('Modus: Aus', 'info'); + } else if (_compositeMode === 'plan') { + showToast('Plan-Modus aktiv', 'info'); + } else if (!_compositeHasAgents) { + showToast('Panel: ' + (_compositeMode === 'moa' ? 'MoA' : 'Fusion') + ' aktiv – aber keine Panel-Agenten konfiguriert (im Session-Bearbeiten festlegen)', 'info'); + } else { + showToast('Panel: ' + (_compositeMode === 'moa' ? 'MoA' : 'Fusion') + ' aktiv', 'success'); + } } } diff --git a/wwwroot/js/pages/chat-page.js b/wwwroot/js/pages/chat-page.js index 780141f..cff1597 100644 --- a/wwwroot/js/pages/chat-page.js +++ b/wwwroot/js/pages/chat-page.js @@ -572,13 +572,12 @@ registerPage('chat', { }); } - // Plan Mode Toggle - const planModeBtn = document.getElementById('planModeBtn'); - if (planModeBtn) { - // Plan-Modus bei Session-Wechsel zurücksetzen - _planModeActive = false; - planModeBtn.classList.remove('plan-mode-active'); - planModeBtn.addEventListener('click', togglePlanMode); + // Unified Modus-Toggle (Aus / Plan / Fusion / MoA) – aus der Session initialisieren + const compositeModeBtn = document.getElementById('planModeBtn'); + if (compositeModeBtn) { + const hasPanelAgents = !!(session.panelAgentIds && String(session.panelAgentIds).trim()); + initCompositeMode(session.multiModelMode, hasPanelAgents); + compositeModeBtn.addEventListener('click', cycleCompositeMode); } // File Upload