feat(archief-assistent): preserve SPARQL queries in semantic cache

- Add sparqlQuery field to CachedResponse interface
- Extract SPARQL query before cache storage (not after)
- Include sparqlQuery in cache HIT message objects
- Handle both snake_case (server) and camelCase field names

SPARQL queries are now displayed for both fresh API responses
and cached responses, improving debugging and transparency.
This commit is contained in:
kempersc 2025-12-24 22:26:22 +01:00
parent c3387ef3f1
commit d5f2f542ce
2 changed files with 83 additions and 1 deletions

View file

@ -54,6 +54,7 @@ export interface CachedResponse {
website?: string;
score?: number;
}>;
sparqlQuery?: string; // SPARQL query used for knowledge graph search
}
export interface CacheConfig {
@ -436,6 +437,7 @@ export class SemanticCache {
answer: data.entry.response.answer,
sources: data.entry.response.sources || [],
institutions: data.entry.response.institutions || [],
sparqlQuery: data.entry.response.sparql_query || data.entry.response.sparqlQuery,
},
timestamp: data.entry.timestamp,
hitCount: data.entry.hit_count || 0,

View file

@ -38,6 +38,9 @@ import BusinessIcon from '@mui/icons-material/Business'
import OpenInNewIcon from '@mui/icons-material/OpenInNew'
import BoltIcon from '@mui/icons-material/Bolt'
import DeleteSweepIcon from '@mui/icons-material/DeleteSweep'
import ContentCopyIcon from '@mui/icons-material/ContentCopy'
import CheckIcon from '@mui/icons-material/Check'
import CodeIcon from '@mui/icons-material/Code'
// Import semantic cache
import {
@ -206,6 +209,7 @@ interface Message {
cacheMethod?: 'semantic' | 'fuzzy' | 'exact'
cacheSimilarity?: number
lookupTimeMs?: number
sparqlQuery?: string // SPARQL query used for knowledge graph search
}
interface Source {
@ -354,6 +358,7 @@ function ChatPage() {
const [input, setInput] = useState('')
const [isLoading, setIsLoading] = useState(false)
const [expandedMessage, setExpandedMessage] = useState<string | null>(null)
const [copiedQueryId, setCopiedQueryId] = useState<string | null>(null) // Track which query was copied
const [selectedModel, setSelectedModel] = useState<string>(() => {
const saved = localStorage.getItem(STORAGE_KEY_LLM_MODEL)
return saved || 'glm-4.6'
@ -392,6 +397,13 @@ function ChatPage() {
updateCacheStats()
}, [updateCacheStats])
// Copy SPARQL query to clipboard
const handleCopyQuery = useCallback((messageId: string, query: string) => {
navigator.clipboard.writeText(query)
setCopiedQueryId(messageId)
setTimeout(() => setCopiedQueryId(null), 2000) // Reset after 2 seconds
}, [])
const sendMessage = useCallback(async (text: string) => {
if (!text.trim() || isLoading) return
@ -452,6 +464,7 @@ function ChatPage() {
cacheMethod: cacheResult.method === 'none' ? undefined : cacheResult.method,
cacheSimilarity: cacheResult.similarity,
lookupTimeMs: cacheResult.lookupTimeMs,
sparqlQuery: cached.sparqlQuery, // Include cached SPARQL query
} : m
))
@ -680,14 +693,18 @@ function ChatPage() {
score: scores.combined as number | undefined,
}
})
// ========================================
// STORE IN CACHE (after successful response)
// ========================================
// Extract SPARQL query from visualization BEFORE caching
const sparqlQuery = (data.visualization as Record<string, unknown> | undefined)?.sparql_query as string | undefined
const cacheResponse: CachedResponse = {
answer: (data.answer as string) || '',
sources,
institutions,
sparqlQuery, // Include SPARQL query in cache
}
// Generate embedding NOW (after cache miss, for local storage)
@ -717,6 +734,7 @@ function ChatPage() {
institutions,
fromCache: false,
isStreaming: false, // Mark streaming as complete
sparqlQuery, // Include SPARQL query if available
} : m
))
} catch (error) {
@ -1036,6 +1054,68 @@ function ChatPage() {
))}
</Box>
)}
{/* SPARQL Query Display */}
{message.role === 'assistant' && !message.isLoading && message.sparqlQuery && (
<Box sx={{ mt: 2 }}>
<Box
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
mb: 1,
}}
>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
<CodeIcon sx={{ fontSize: 16, color: naColors.primary }} />
<Typography variant="caption" sx={{ fontWeight: 600, color: naColors.primary }}>
SPARQL Query
</Typography>
</Box>
<Tooltip title={copiedQueryId === message.id ? 'Gekopieerd!' : 'Kopieer query'}>
<IconButton
size="small"
onClick={() => handleCopyQuery(message.id, message.sparqlQuery!)}
sx={{
width: 28,
height: 28,
color: copiedQueryId === message.id ? naColors.green : 'text.secondary',
}}
>
{copiedQueryId === message.id ? (
<CheckIcon sx={{ fontSize: 16 }} />
) : (
<ContentCopyIcon sx={{ fontSize: 16 }} />
)}
</IconButton>
</Tooltip>
</Box>
<Paper
variant="outlined"
sx={{
p: 1.5,
bgcolor: '#1e1e1e',
borderRadius: 1,
overflow: 'auto',
maxHeight: 200,
}}
>
<Typography
component="pre"
sx={{
fontFamily: 'Monaco, Consolas, monospace',
fontSize: '0.75rem',
color: '#d4d4d4',
margin: 0,
whiteSpace: 'pre-wrap',
wordBreak: 'break-word',
}}
>
{message.sparqlQuery}
</Typography>
</Paper>
</Box>
)}
</Paper>
</Box>
</Fade>