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 */
|
/* 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
In neuem Issue referenzieren
Einen Benutzer sperren