+
{t('retrievePanel.chatMessage.thinkingInProgress', 'Thinking in progress...')}
)}
@@ -238,16 +331,9 @@ export const ChatMessage = ({
displayMode: false,
strict: false,
trust: true,
- // Add silent error handling to avoid console noise
errorCallback: (error: string, latex: string) => {
- // Only show detailed errors in development environment
if (process.env.NODE_ENV === 'development') {
- console.warn(
- 'KaTeX rendering error in thinking content:',
- error,
- 'for LaTeX:',
- latex
- )
+ console.warn('KaTeX error in thinking:', error, latex)
}
},
},
diff --git a/lightrag_webui/src/components/retrieval/CitationMarker.tsx b/lightrag_webui/src/components/retrieval/CitationMarker.tsx
new file mode 100644
index 00000000..41f0e937
--- /dev/null
+++ b/lightrag_webui/src/components/retrieval/CitationMarker.tsx
@@ -0,0 +1,161 @@
+/**
+ * CitationMarker Component
+ *
+ * Renders citation markers (e.g., [1]) as interactive hover cards
+ * showing source metadata like document title, section, page, and excerpt.
+ */
+
+import type { CitationSource } from '@/api/lightrag'
+import Badge from '@/components/ui/Badge'
+import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/HoverCard'
+import { FileTextIcon } from 'lucide-react'
+
+interface CitationMarkerProps {
+ /** The citation marker text, e.g., "[1]" or "[1,2]" */
+ marker: string
+ /** Reference IDs this marker cites */
+ referenceIds: string[]
+ /** Confidence score (0-1) */
+ confidence: number
+ /** Source metadata for hover card */
+ sources: CitationSource[]
+}
+
+/**
+ * Interactive citation marker with hover card showing source metadata
+ */
+export function CitationMarker({
+ marker,
+ referenceIds,
+ confidence,
+ sources,
+}: CitationMarkerProps) {
+ // Find sources matching our reference IDs
+ const matchingSources = sources.filter((s) => referenceIds.includes(s.reference_id))
+
+ // Confidence badge color based on score
+ const confidenceColor =
+ confidence >= 0.8
+ ? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200'
+ : confidence >= 0.6
+ ? 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200'
+ : 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200'
+
+ return (
+
+
+
+
+
+
+ {matchingSources.map((source) => (
+
+ {/* Document title */}
+
+
+
+ {source.document_title || 'Untitled Document'}
+
+
+
+ {/* Section title */}
+ {source.section_title && (
+
+ Section: {source.section_title}
+
+ )}
+
+ {/* Page range */}
+ {source.page_range && (
+
+ Pages: {source.page_range}
+
+ )}
+
+ {/* Excerpt */}
+ {source.excerpt && (
+
+ "{source.excerpt}"
+
+ )}
+
+ {/* File path */}
+
+ {source.file_path}
+
+
+ ))}
+
+ {/* Confidence badge */}
+
+ Match confidence
+
+ {(confidence * 100).toFixed(0)}%
+
+
+
+
+
+ )
+}
+
+/**
+ * Parses text containing citation markers and returns React elements
+ * with interactive CitationMarker components.
+ *
+ * @param text - Text that may contain [n] or [n,m] patterns
+ * @param sources - Array of citation sources for hover card metadata
+ * @param markers - Array of citation markers with position and confidence data
+ * @returns Array of React elements (strings and CitationMarker components)
+ */
+export function renderTextWithCitations(
+ text: string,
+ sources: CitationSource[],
+ markers: Array<{ marker: string; reference_ids: string[]; confidence: number }>
+): React.ReactNode[] {
+ // Match citation patterns like [1], [2], [1,2], etc.
+ const citationPattern = /\[(\d+(?:,\d+)*)\]/g
+ const parts: React.ReactNode[] = []
+ let lastIndex = 0
+ let match: RegExpExecArray | null
+
+ while ((match = citationPattern.exec(text)) !== null) {
+ // Add text before the citation
+ if (match.index > lastIndex) {
+ parts.push(text.slice(lastIndex, match.index))
+ }
+
+ // Parse reference IDs from the marker
+ const markerText = match[0]
+ const refIds = match[1].split(',').map((id) => id.trim())
+
+ // Find matching marker data for confidence
+ const markerData = markers.find((m) => m.marker === markerText)
+ const confidence = markerData?.confidence ?? 0.5
+
+ // Add the citation marker component
+ parts.push(
+
+ )
+
+ lastIndex = match.index + match[0].length
+ }
+
+ // Add remaining text
+ if (lastIndex < text.length) {
+ parts.push(text.slice(lastIndex))
+ }
+
+ return parts
+}
diff --git a/lightrag_webui/src/components/retrieval/QuerySettings.tsx b/lightrag_webui/src/components/retrieval/QuerySettings.tsx
index c8b0337c..5466af30 100644
--- a/lightrag_webui/src/components/retrieval/QuerySettings.tsx
+++ b/lightrag_webui/src/components/retrieval/QuerySettings.tsx
@@ -52,6 +52,8 @@ export default function QuerySettings() {
max_entity_tokens: 6000,
max_relation_tokens: 8000,
max_total_tokens: 30000,
+ citation_mode: 'none' as 'none' | 'inline' | 'footnotes',
+ citation_threshold: 0.7,
}),
[]
)
@@ -474,6 +476,87 @@ export default function QuerySettings() {
/>
>
+
+ {/* Citation Settings */}
+ <>
+
+
+ {/* Citation Threshold - only show when citation mode is not 'none' */}
+ {querySettings.citation_mode && querySettings.citation_mode !== 'none' && (
+ <>
+