Files
ai-teacher/frontend/src/stores/chatStore.ts
T
2026-04-07 22:39:28 +02:00

156 lines
3.7 KiB
TypeScript

import { defineStore } from 'pinia'
import { ref } from 'vue'
import { api } from '@/services/api'
export interface ChatSource {
type: 'TEXT' | 'FIGURE'
bookId?: string
bookTitle: string
page: number | null
refLabel?: string
// TEXT-specific
chunkText?: string
// FIGURE-specific
figureId?: string
label?: string
caption?: string
figureType?: string
imageUrl?: string
}
export interface ChatMessage {
id: string
role: 'USER' | 'ASSISTANT'
content: string
sources: ChatSource[]
createdAt: string
}
export interface ChatSession {
sessionId: string
topicId: string | null
createdAt: string
}
export const useChatStore = defineStore('chat', () => {
const session = ref<ChatSession | null>(null)
const messages = ref<ChatMessage[]>([])
const loading = ref(false)
const sending = ref(false)
const error = ref<string | null>(null)
async function createSession(topicId?: string): Promise<boolean> {
loading.value = true
error.value = null
try {
const body = topicId ? { topicId } : {}
const response = await api.post<ChatSession>('/chat/sessions', body)
session.value = response.data
messages.value = []
return true
} catch (err: any) {
error.value = err.message
return false
} finally {
loading.value = false
}
}
async function loadMessages(): Promise<void> {
if (!session.value) return
loading.value = true
error.value = null
try {
const response = await api.get<ChatMessage[]>(
`/chat/sessions/${session.value.sessionId}/messages`
)
messages.value = response.data
} catch (err: any) {
error.value = err.message
} finally {
loading.value = false
}
}
async function sendMessage(content: string): Promise<boolean> {
if (!session.value) return false
sending.value = true
error.value = null
// Optimistically add user message
const tempUserMsg: ChatMessage = {
id: crypto.randomUUID(),
role: 'USER',
content,
sources: [],
createdAt: new Date().toISOString()
}
messages.value.push(tempUserMsg)
try {
const response = await api.post<ChatMessage>(
`/chat/sessions/${session.value.sessionId}/messages`,
{ content }
)
// Replace temp message & add assistant response
messages.value = messages.value.filter((m) => m.id !== tempUserMsg.id)
// Reload full conversation to stay in sync
await loadMessages()
return true
} catch (err: any) {
// Remove optimistic message on failure
messages.value = messages.value.filter((m) => m.id !== tempUserMsg.id)
error.value = err.message
return false
} finally {
sending.value = false
}
}
function leaveSession(): void {
session.value = null
messages.value = []
error.value = null
}
async function fetchSessionsByTopic(topicId: string): Promise<ChatSession[]> {
try {
const response = await api.get<ChatSession[]>('/chat/sessions', { params: { topicId } })
return response.data
} catch {
return []
}
}
async function deleteSession(): Promise<boolean> {
if (!session.value) return false
loading.value = true
error.value = null
try {
await api.delete(`/chat/sessions/${session.value.sessionId}`)
session.value = null
messages.value = []
return true
} catch (err: any) {
error.value = err.message
return false
} finally {
loading.value = false
}
}
return {
session,
messages,
loading,
sending,
error,
createSession,
loadMessages,
sendMessage,
leaveSession,
fetchSessionsByTopic,
deleteSession
}
})