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:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -94,10 +94,12 @@ 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();
|
||||
this.loadSessions();
|
||||
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',
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren