/** * Chat Store – V2 Dashboard * * Fetches chat messages from /api/dashboard/chat/messages and * sends new messages via /api/dashboard/chat/send. * * Auto-refresh: every 10 seconds (incoming Iris messages). */ import { defineStore } from 'pinia' import { apiFetch } from '../services/api' import type { ChatMessage } from '../components/dashboard/v2/types' /* ── API Response Shapes ──────────────────────────── */ interface MessageEntry { role: string content: string timestamp: string } interface ChatResponse { ok: boolean reply: string | null error: string | null } export const useChatStore = defineStore('chat', { state: () => ({ messages: [] as ChatMessage[], isThinking: false, error: null as string | null, refreshInterval: null as ReturnType | null, /** Tracks last process timestamp to avoid duplicates */ lastProcessedTs: 0, }), getters: { messageList: (state) => state.messages, }, actions: { /* ── API: Fetch history ─────────────────────── */ async fetchHistory() { try { const res = await apiFetch('/api/dashboard/chat/messages?limit=50') if (!res.ok) return const data: MessageEntry[] = await res.json() // Merge new messages (avoid duplicates) let mostRecentTs = this.lastProcessedTs for (const msg of data) { const msgTs = new Date(msg.timestamp).getTime() if (msgTs <= this.lastProcessedTs) continue if (msgTs > mostRecentTs) mostRecentTs = msgTs const sender = msg.role === 'assistant' ? 'iris' as const : 'user' as const const tsFormatted = new Date(msg.timestamp).toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit', }) // Avoid appending duplicates already present const exists = this.messages.some( m => m.sender === sender && m.text === msg.content && m.ts === tsFormatted ) if (exists) continue this.messages.push({ sender, text: msg.content, ts: tsFormatted, }) } if (mostRecentTs > this.lastProcessedTs) { this.lastProcessedTs = mostRecentTs } } catch (err) { console.warn('[ChatStore] fetchHistory failed', err) } }, /* ── API: Send message ──────────────────────── */ async sendMessage(text: string) { if (!text.trim()) return const tsFormatted = new Date().toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit', }) // Optimistic add: user message this.messages.push({ sender: 'user', text: text.trim(), ts: tsFormatted, }) this.isThinking = true this.error = null try { const res = await apiFetch('/api/dashboard/chat/send', { method: 'POST', body: JSON.stringify({ message: text.trim() }), }) if (!res.ok) throw new Error(`HTTP ${res.status}`) const data: ChatResponse = await res.json() if (data.ok && data.reply) { this.messages.push({ sender: 'iris', text: data.reply, ts: new Date().toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit', }), }) } else if (data.error) { this.messages.push({ sender: 'iris', text: `⚠️ ${data.error}`, ts: new Date().toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit', }), }) } } catch (err) { console.warn('[ChatStore] sendMessage failed', err) this.messages.push({ sender: 'iris', text: '⚠️ Connection error. Please try again.', ts: new Date().toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit', }), }) } finally { this.isThinking = false } }, /* ── Polling ─────────────────────────────────── */ startPolling() { if (this.refreshInterval) return this.fetchHistory() this.refreshInterval = setInterval(() => { this.fetchHistory() }, 10000) // 10s for chat (more responsive) }, stopPolling() { if (this.refreshInterval) { clearInterval(this.refreshInterval) this.refreshInterval = null } }, }, })