/** * TASKMATE - Assistant Manager * ============================ * Claude Assistant Chat Interface */ import api from './api.js'; import syncManager from './sync.js'; import { $ } from './utils.js'; class AssistantManager { constructor() { this.sessions = []; this.currentSessionId = null; this.sessionStatus = null; // running, ended, stopped, error this.streamingMessageEl = null; this.streamingContent = ''; this.initialized = false; } async init() { if (this.initialized) return; // DOM Elements this.sessionsListEl = $('#assistant-sessions-list'); this.messagesEl = $('#assistant-messages'); this.inputEl = $('#assistant-input'); this.sendBtn = $('#btn-send-message'); this.newSessionBtn = $('#btn-new-session'); this.chatTitleEl = $('#assistant-chat-title'); this.statusBadgeEl = $('#assistant-status-badge'); this.chatEl = document.querySelector('.assistant-chat'); this.emptyEl = $('#assistant-empty'); this.bindEvents(); this.setupSocketListeners(); this.updateChatState(); this.initialized = true; console.log('[Assistant] Initialized'); } bindEvents() { // New session this.newSessionBtn?.addEventListener('click', () => this.startSession()); // Send message this.sendBtn?.addEventListener('click', () => this.sendMessage()); // Textarea: Enter sends, Shift+Enter new line, auto-resize this.inputEl?.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); this.sendMessage(); } }); this.inputEl?.addEventListener('input', () => { this.autoResizeInput(); }); } setupSocketListeners() { const socket = syncManager.socket; if (!socket) { // Socket not yet connected, wait for it const checkSocket = setInterval(() => { if (syncManager.socket) { clearInterval(checkSocket); this._attachSocketEvents(syncManager.socket); } }, 500); return; } this._attachSocketEvents(socket); } _attachSocketEvents(socket) { // Streaming output socket.on('assistant:output', (data) => { if (data.sessionId !== this.currentSessionId) return; this.handleOutput(data.content); }); // Status changes socket.on('assistant:status', (data) => { if (data.sessionId && data.sessionId !== this.currentSessionId) return; if (data.status === 'error') { this.setStatus('error'); this.showToast(data.error || 'Fehler beim Assistenten', 'error'); return; } this.setStatus(data.status); // If session ended, finalize streaming if (data.status === 'ended' || data.status === 'stopped') { this.finalizeStreaming(); this.loadSessions(); } }); } // ===================== // SHOW / HIDE // ===================== async show() { await this.loadSessions(); } hide() { // Nothing to clean up } // ===================== // SESSIONS // ===================== async loadSessions() { try { this.sessions = await api.getAssistantSessions(); this.renderSessionsList(); } catch (err) { console.error('[Assistant] Fehler beim Laden der Sessions:', err); } } renderSessionsList() { if (!this.sessionsListEl) return; if (this.sessions.length === 0) { this.sessionsListEl.innerHTML = '
${code.trim()}`;
});
// Inline code
html = html.replace(/`([^`]+)`/g, '$1');
// Bold
html = html.replace(/\*\*(.+?)\*\*/g, '$1');
// Italic
html = html.replace(/\*(.+?)\*/g, '$1');
// Headers
html = html.replace(/^#### (.+)$/gm, '$1'); // Line breaks (but not inside pre) html = html.replace(/\n/g, '
([\s\S]*?)<\/code><\/pre>/g, (match) => {
return match.replace(/
/g, '\n');
});
return html;
}
// =====================
// UI HELPERS
// =====================
updateChatState() {
if (!this.chatEl) return;
if (this.currentSessionId) {
this.chatEl.classList.remove('no-session');
} else {
this.chatEl.classList.add('no-session');
}
}
setStatus(status) {
this.sessionStatus = status;
if (!this.statusBadgeEl) return;
// Remove all status classes
this.statusBadgeEl.className = 'assistant-status-badge';
if (!status) {
this.statusBadgeEl.textContent = '';
return;
}
const labels = {
running: 'Aktiv',
thinking: 'Denkt...',
ended: 'Beendet',
stopped: 'Gestoppt',
error: 'Fehler'
};
this.statusBadgeEl.classList.add(`status-${status}`);
this.statusBadgeEl.textContent = labels[status] || status;
}
autoResizeInput() {
if (!this.inputEl) return;
this.inputEl.style.height = 'auto';
this.inputEl.style.height = Math.min(this.inputEl.scrollHeight, 150) + 'px';
}
scrollToBottom() {
if (!this.messagesEl) return;
requestAnimationFrame(() => {
this.messagesEl.scrollTop = this.messagesEl.scrollHeight;
});
}
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
async handleTaskHandover(taskContext) {
// Switch to assistant view
const assistantTab = document.querySelector('.view-tab[data-view="assistant"]');
if (assistantTab) assistantTab.click();
// Ensure initialized
await this.init();
// Start new session with task context
await this.startSession(taskContext);
}
showToast(message, type = 'info') {
window.dispatchEvent(new CustomEvent('toast:show', {
detail: { message, type }
}));
}
}
// Singleton
const assistantManager = new AssistantManager();
export default assistantManager;