feat(v2): Pinia stores (agents/tasks/chat) + live backend integration, remove mock data
CI - Build & Test / Backend (.NET) (push) Failing after 22s
CI - Build & Test / Frontend (Vue/TS) (push) Successful in 15s
CI - Build & Test / Security Check (push) Successful in 3s

This commit is contained in:
2026-06-12 00:57:28 +02:00
parent 9330de7af0
commit 676dbd7589
4 changed files with 648 additions and 227 deletions
+160
View File
@@ -0,0 +1,160 @@
/**
* 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<typeof setInterval> | 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
}
},
},
})