Assistent: Layout-Fix, Auth-Fix, Session-Reaktivierung

- CSS: Feste Hoehe, overflow hidden, Input-Bar immer sichtbar
- Auth: Prueft username UND displayName gegen E-Mail und Name
- Beendete Sessions werden automatisch reaktiviert beim Senden

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dieser Commit ist enthalten in:
Server Deploy
2026-03-19 23:16:17 +01:00
Ursprung ecd8158532
Commit 4040b5f306
3 geänderte Dateien mit 83 neuen und 26 gelöschten Zeilen

Datei anzeigen

@@ -5,10 +5,18 @@
*/ */
/* Layout */ /* Layout */
.view.view-assistant {
height: calc(100vh - var(--header-height) - 52px);
overflow: hidden;
display: flex;
flex-direction: column;
}
.assistant-layout { .assistant-layout {
display: grid; display: grid;
grid-template-columns: 280px 1fr; grid-template-columns: 280px 1fr;
height: calc(100vh - 120px); flex: 1;
min-height: 0;
overflow: hidden; overflow: hidden;
} }
@@ -110,6 +118,7 @@
.assistant-chat { .assistant-chat {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100%;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
background: var(--bg-main); background: var(--bg-main);
@@ -201,6 +210,7 @@
/* Messages Area */ /* Messages Area */
.assistant-messages { .assistant-messages {
flex: 1; flex: 1;
min-height: 0;
overflow-y: auto; overflow-y: auto;
padding: 20px; padding: 20px;
display: flex; display: flex;
@@ -365,10 +375,12 @@
/* Empty State */ /* Empty State */
.assistant-empty { .assistant-empty {
position: absolute; flex: 1;
top: 50%; min-height: 0;
left: 50%; display: flex;
transform: translate(-50%, -50%); flex-direction: column;
align-items: center;
justify-content: center;
text-align: center; text-align: center;
color: var(--text-muted); color: var(--text-muted);
pointer-events: none; pointer-events: none;
@@ -391,12 +403,7 @@
} }
/* Hide empty state when messages present */ /* Hide empty state when messages present */
.assistant-messages:not(:empty) ~ .assistant-empty { .assistant-messages:not(:empty) + .assistant-empty {
display: none;
}
/* Hide input bar when no session */
.assistant-chat.no-session .assistant-input-bar {
display: none; display: none;
} }

Datei anzeigen

@@ -714,15 +714,6 @@
<span id="assistant-status-badge" class="assistant-status-badge"></span> <span id="assistant-status-badge" class="assistant-status-badge"></span>
</div> </div>
<div id="assistant-messages" class="assistant-messages"></div> <div id="assistant-messages" class="assistant-messages"></div>
<div class="assistant-input-bar">
<textarea id="assistant-input" class="assistant-input" placeholder="Nachricht eingeben..." rows="1"></textarea>
<button id="btn-send-message" class="btn btn-primary assistant-send-btn" title="Senden">
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="22" y1="2" x2="11" y2="13"/>
<polygon points="22 2 15 22 11 13 2 9 22 2"/>
</svg>
</button>
</div>
<div id="assistant-empty" class="assistant-empty"> <div id="assistant-empty" class="assistant-empty">
<div class="assistant-empty-icon"> <div class="assistant-empty-icon">
<svg viewBox="0 0 24 24" width="64" height="64" fill="none" stroke="currentColor" stroke-width="1.5"> <svg viewBox="0 0 24 24" width="64" height="64" fill="none" stroke="currentColor" stroke-width="1.5">
@@ -732,6 +723,15 @@
<h3>Claude Assistent</h3> <h3>Claude Assistent</h3>
<p>Starte eine neue Session oder waehle eine bestehende aus.</p> <p>Starte eine neue Session oder waehle eine bestehende aus.</p>
</div> </div>
<div class="assistant-input-bar">
<textarea id="assistant-input" class="assistant-input" placeholder="Nachricht eingeben..." rows="1"></textarea>
<button id="btn-send-message" class="btn btn-primary assistant-send-btn" title="Senden">
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="22" y1="2" x2="11" y2="13"/>
<polygon points="22 2 15 22 11 13 2 9 22 2"/>
</svg>
</button>
</div>
</main> </main>
</div> </div>
</div> </div>

Datei anzeigen

@@ -94,10 +94,12 @@ class AssistantManager {
this.setStatus(data.status); this.setStatus(data.status);
// If session ended, finalize streaming // Finalize streaming when message processing completes
if (data.status === 'ended' || data.status === 'stopped') { if (data.status === 'ended' || data.status === 'stopped' || data.status === 'active') {
this.finalizeStreaming(); this.finalizeStreaming();
this.loadSessions(); if (data.status !== 'active') {
this.loadSessions();
}
} }
}); });
} }
@@ -178,7 +180,16 @@ class AssistantManager {
if (session) { if (session) {
this.chatTitleEl.textContent = session.title; this.chatTitleEl.textContent = session.title;
this.setStatus(session.status === 'active' ? 'ended' : session.status);
if (session.status === 'active') {
// Aktive Session mit Backend verbinden
const socket = syncManager.socket;
if (socket) {
socket.emit('assistant:start', { sessionId: id });
}
} else {
this.setStatus(session.status);
}
} }
this.renderSessionsList(); this.renderSessionsList();
@@ -211,7 +222,7 @@ class AssistantManager {
this.currentSessionId = session.id; this.currentSessionId = session.id;
this.chatTitleEl.textContent = session.title; this.chatTitleEl.textContent = session.title;
this.messagesEl.innerHTML = ''; this.messagesEl.innerHTML = '';
this.setStatus('running'); this.setStatus('active');
this.updateChatState(); this.updateChatState();
// Start Claude process via Socket // Start Claude process via Socket
@@ -261,10 +272,22 @@ class AssistantManager {
// MESSAGING // MESSAGING
// ===================== // =====================
sendMessage() { async sendMessage() {
const text = this.inputEl?.value?.trim(); const text = this.inputEl?.value?.trim();
if (!text || !this.currentSessionId) return; if (!text || !this.currentSessionId) return;
// Beendete/gestoppte Session zuerst reaktivieren
if (this.sessionStatus === 'ended' || this.sessionStatus === 'stopped') {
try {
await this._reactivateSession();
this.loadSessions();
} catch (err) {
console.error('[Assistant] Reaktivierung fehlgeschlagen:', err);
this.showToast('Session konnte nicht reaktiviert werden', 'error');
return;
}
}
// Render user message // Render user message
this.renderMessage('user', text); this.renderMessage('user', text);
this.inputEl.value = ''; this.inputEl.value = '';
@@ -285,6 +308,32 @@ class AssistantManager {
this.setStatus('thinking'); this.setStatus('thinking');
} }
_reactivateSession() {
return new Promise((resolve, reject) => {
const socket = syncManager.socket;
if (!socket) return reject(new Error('Kein Socket'));
const onStatus = (data) => {
if (data.sessionId !== this.currentSessionId) return;
socket.off('assistant:status', onStatus);
clearTimeout(timeout);
if (data.status === 'error') {
reject(new Error(data.error || 'Fehler'));
} else {
resolve();
}
};
const timeout = setTimeout(() => {
socket.off('assistant:status', onStatus);
reject(new Error('Timeout'));
}, 10000);
socket.on('assistant:status', onStatus);
socket.emit('assistant:start', { sessionId: this.currentSessionId });
});
}
handleOutput(content) { handleOutput(content) {
if (!this.streamingMessageEl) { if (!this.streamingMessageEl) {
// Create streaming bubble if not exists // Create streaming bubble if not exists
@@ -401,6 +450,7 @@ class AssistantManager {
} }
const labels = { const labels = {
active: 'Bereit',
running: 'Aktiv', running: 'Aktiv',
thinking: 'Denkt...', thinking: 'Denkt...',
ended: 'Beendet', ended: 'Beendet',