feat(v2): Pinia stores (agents/tasks/chat) + live backend integration, remove mock data
This commit is contained in:
@@ -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
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user