161 lines
4.6 KiB
TypeScript
161 lines
4.6 KiB
TypeScript
/**
|
||
* 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
|
||
}
|
||
},
|
||
},
|
||
})
|