From d5f2f542ceb0433b5ce4bd14044a97cda54e6def Mon Sep 17 00:00:00 2001 From: kempersc Date: Wed, 24 Dec 2025 22:26:22 +0100 Subject: [PATCH] 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. --- .../src/lib/semantic-cache.ts | 2 + apps/archief-assistent/src/pages/ChatPage.tsx | 82 ++++++++++++++++++- 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/apps/archief-assistent/src/lib/semantic-cache.ts b/apps/archief-assistent/src/lib/semantic-cache.ts index ad907bac14..52b50607d5 100644 --- a/apps/archief-assistent/src/lib/semantic-cache.ts +++ b/apps/archief-assistent/src/lib/semantic-cache.ts @@ -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, diff --git a/apps/archief-assistent/src/pages/ChatPage.tsx b/apps/archief-assistent/src/pages/ChatPage.tsx index 0c7ab13178..11b815cab9 100644 --- a/apps/archief-assistent/src/pages/ChatPage.tsx +++ b/apps/archief-assistent/src/pages/ChatPage.tsx @@ -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(null) + const [copiedQueryId, setCopiedQueryId] = useState(null) // Track which query was copied const [selectedModel, setSelectedModel] = useState(() => { 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 | 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() { ))} )} + + {/* SPARQL Query Display */} + {message.role === 'assistant' && !message.isLoading && message.sparqlQuery && ( + + + + + + SPARQL Query + + + + handleCopyQuery(message.id, message.sparqlQuery!)} + sx={{ + width: 28, + height: 28, + color: copiedQueryId === message.id ? naColors.green : 'text.secondary', + }} + > + {copiedQueryId === message.id ? ( + + ) : ( + + )} + + + + + + {message.sparqlQuery} + + + + )}