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

Datei anzeigen

@@ -714,15 +714,6 @@
<span id="assistant-status-badge" class="assistant-status-badge"></span>
</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 class="assistant-empty-icon">
<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>
<p>Starte eine neue Session oder waehle eine bestehende aus.</p>
</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>
</div>
</div>

Datei anzeigen

@@ -94,11 +94,13 @@ class AssistantManager {
this.setStatus(data.status);
// If session ended, finalize streaming
if (data.status === 'ended' || data.status === 'stopped') {
// Finalize streaming when message processing completes
if (data.status === 'ended' || data.status === 'stopped' || data.status === 'active') {
this.finalizeStreaming();
if (data.status !== 'active') {
this.loadSessions();
}
}
});
}
@@ -178,7 +180,16 @@ class AssistantManager {
if (session) {
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();
@@ -211,7 +222,7 @@ class AssistantManager {
this.currentSessionId = session.id;
this.chatTitleEl.textContent = session.title;
this.messagesEl.innerHTML = '';
this.setStatus('running');
this.setStatus('active');
this.updateChatState();
// Start Claude process via Socket
@@ -261,10 +272,22 @@ class AssistantManager {
// MESSAGING
// =====================
sendMessage() {
async sendMessage() {
const text = this.inputEl?.value?.trim();
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
this.renderMessage('user', text);
this.inputEl.value = '';
@@ -285,6 +308,32 @@ class AssistantManager {
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) {
if (!this.streamingMessageEl) {
// Create streaming bubble if not exists
@@ -401,6 +450,7 @@ class AssistantManager {
}
const labels = {
active: 'Bereit',
running: 'Aktiv',
thinking: 'Denkt...',
ended: 'Beendet',