feat: add source-grounded evolution and Ollama assistant
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { formatError } from '../lib/formatError'
|
||||
import { assistantApi } from '../services/api/assistant'
|
||||
import type {
|
||||
AssistantChatMessage,
|
||||
AssistantModelRead,
|
||||
AssistantQueryResponse,
|
||||
AssistantStatus,
|
||||
VectorSelectionBBox,
|
||||
} from '../types'
|
||||
|
||||
export interface GeoAssistantMessage extends AssistantChatMessage {
|
||||
id: string
|
||||
response?: AssistantQueryResponse
|
||||
}
|
||||
|
||||
interface UseGeoAssistantOptions {
|
||||
selectedProjectId: string | null
|
||||
selectedAreaId: string | null
|
||||
selectionBbox: VectorSelectionBBox | null
|
||||
}
|
||||
|
||||
export function useGeoAssistant({ selectedProjectId, selectedAreaId, selectionBbox }: UseGeoAssistantOptions) {
|
||||
const [status, setStatus] = useState<AssistantStatus | null>(null)
|
||||
const [models, setModels] = useState<AssistantModelRead[]>([])
|
||||
const [selectedModel, setSelectedModel] = useState('')
|
||||
const [messages, setMessages] = useState<GeoAssistantMessage[]>([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [loadingModels, setLoadingModels] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
const loadModels = async () => {
|
||||
setLoadingModels(true)
|
||||
setError(null)
|
||||
try {
|
||||
const currentStatus = await assistantApi.status()
|
||||
setStatus(currentStatus)
|
||||
if (!currentStatus.enabled || !currentStatus.reachable) {
|
||||
setModels([])
|
||||
setSelectedModel('')
|
||||
return
|
||||
}
|
||||
const result = await assistantApi.models()
|
||||
setModels(result.items)
|
||||
setSelectedModel((current) => {
|
||||
if (current && result.items.some((model) => model.name === current)) return current
|
||||
if (result.default_model && result.items.some((model) => model.name === result.default_model)) return result.default_model
|
||||
return result.items[0]?.name ?? ''
|
||||
})
|
||||
} catch (requestError) {
|
||||
setStatus(null)
|
||||
setModels([])
|
||||
setError(formatError(requestError, 'De lokale AI-assistent kon niet worden bereikt.'))
|
||||
} finally {
|
||||
setLoadingModels(false)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
void loadModels()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
setMessages([])
|
||||
setError(null)
|
||||
}, [selectedProjectId])
|
||||
|
||||
const ask = async (question: string): Promise<boolean> => {
|
||||
const trimmed = question.trim()
|
||||
if (!selectedProjectId || !trimmed || !selectedModel) return false
|
||||
const userMessage: GeoAssistantMessage = { id: crypto.randomUUID(), role: 'user', content: trimmed }
|
||||
setMessages((current) => [...current, userMessage])
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
try {
|
||||
const history = messages.slice(-6).map(({ role, content }) => ({ role, content }))
|
||||
const result = await assistantApi.query(selectedProjectId, {
|
||||
question: trimmed,
|
||||
model: selectedModel,
|
||||
bbox: selectionBbox,
|
||||
area_id: selectedAreaId,
|
||||
history,
|
||||
})
|
||||
setMessages((current) => [
|
||||
...current,
|
||||
{ id: crypto.randomUUID(), role: 'assistant', content: result.answer, response: result },
|
||||
])
|
||||
return true
|
||||
} catch (requestError) {
|
||||
setError(formatError(requestError, 'GeoIntel kon de vraag niet beantwoorden.'))
|
||||
return false
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const clear = () => {
|
||||
setMessages([])
|
||||
setError(null)
|
||||
}
|
||||
|
||||
return {
|
||||
status,
|
||||
models,
|
||||
selectedModel,
|
||||
messages,
|
||||
loading,
|
||||
loadingModels,
|
||||
error,
|
||||
loadModels,
|
||||
ask,
|
||||
clear,
|
||||
setSelectedModel,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user