From 9fcdd35ac34e21fe4b20b62ed18009961e515079 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Boschi?= Date: Wed, 3 Dec 2025 15:24:38 +0100 Subject: [PATCH 1/2] fix ci --- .../hindsight_api/engine/search/retrieval.py | 32 ++- .../src/app/api/recall/route.ts | 5 +- .../src/components/document-chunk-modal.tsx | 211 ++++++++++++++++++ .../src/components/search-debug-view.tsx | 16 +- hindsight-control-plane/src/lib/api.ts | 1 + 5 files changed, 257 insertions(+), 8 deletions(-) create mode 100644 hindsight-control-plane/src/components/document-chunk-modal.tsx diff --git a/hindsight-api/hindsight_api/engine/search/retrieval.py b/hindsight-api/hindsight_api/engine/search/retrieval.py index 2fce88313e..82ce466fc9 100644 --- a/hindsight-api/hindsight_api/engine/search/retrieval.py +++ b/hindsight-api/hindsight_api/engine/search/retrieval.py @@ -228,7 +228,7 @@ async def retrieve_temporal( start_date: datetime, end_date: datetime, budget: int, - semantic_threshold: float = 0.4 + semantic_threshold: float = 0.1 ) -> List[RetrievalResult]: """ Temporal retrieval with spreading activation. @@ -287,6 +287,9 @@ async def retrieve_temporal( query_emb_str, bank_id, fact_type, start_date, end_date, semantic_threshold ) + import logging + logger = logging.getLogger(__name__) + if not entry_points: # Check if there are ANY memories with temporal metadata for this bank total_with_dates = await conn.fetchval( @@ -295,9 +298,28 @@ async def retrieve_temporal( AND (occurred_start IS NOT NULL OR occurred_end IS NOT NULL OR mentioned_at IS NOT NULL)""", bank_id, fact_type ) - import logging - logger = logging.getLogger(__name__) - logger.info(f"[TEMPORAL] No entry points found for {bank_id}/{fact_type} in range {start_date} to {end_date}. Total facts with dates: {total_with_dates}") + # Check how many have mentioned_at in the range + in_range = await conn.fetchval( + """SELECT COUNT(*) FROM memory_units + WHERE bank_id = $1 AND fact_type = $2 + AND mentioned_at IS NOT NULL AND mentioned_at BETWEEN $3 AND $4""", + bank_id, fact_type, start_date, end_date + ) + # Check semantic similarity of those in range + sample = await conn.fetch( + """SELECT id, text, mentioned_at, 1 - (embedding <=> $1::vector) AS similarity + FROM memory_units + WHERE bank_id = $2 AND fact_type = $3 + AND mentioned_at IS NOT NULL AND mentioned_at BETWEEN $4 AND $5 + AND embedding IS NOT NULL + ORDER BY mentioned_at DESC + LIMIT 5""", + query_emb_str, bank_id, fact_type, start_date, end_date + ) + logger.info(f"[TEMPORAL] No entry points for {bank_id}/{fact_type} in {start_date} to {end_date}.") + logger.info(f"[TEMPORAL] Total with dates: {total_with_dates}, In date range: {in_range}") + for row in sample: + logger.info(f"[TEMPORAL] Sample: {row['text'][:60]}... mentioned_at={row['mentioned_at']} sim={row['similarity']:.3f}") return [] # Calculate temporal scores for entry points @@ -484,7 +506,7 @@ async def run_temporal(start_date, end_date): async with acquire_with_retry(pool) as conn: return await retrieve_temporal( conn, query_embedding_str, bank_id, fact_type, - start_date, end_date, budget=thinking_budget, semantic_threshold=0.4 + start_date, end_date, budget=thinking_budget, semantic_threshold=0.1 ) # Run retrievals in parallel with timing diff --git a/hindsight-control-plane/src/app/api/recall/route.ts b/hindsight-control-plane/src/app/api/recall/route.ts index 2b6384c7c7..2bfdbe26ef 100644 --- a/hindsight-control-plane/src/app/api/recall/route.ts +++ b/hindsight-control-plane/src/app/api/recall/route.ts @@ -5,9 +5,9 @@ export async function POST(request: NextRequest) { try { const body = await request.json(); const bankId = body.bank_id || body.agent_id || 'default'; - const { query, types, fact_type, max_tokens, trace, budget, include } = body; + const { query, types, fact_type, max_tokens, trace, budget, include, query_timestamp } = body; - console.log('[Recall API] Request:', { bankId, query, types: types || fact_type, max_tokens, trace, budget }); + console.log('[Recall API] Request:', { bankId, query, types: types || fact_type, max_tokens, trace, budget, query_timestamp }); console.log('[Recall API] Include options:', JSON.stringify(include, null, 2)); const response = await sdk.recallMemories({ @@ -20,6 +20,7 @@ export async function POST(request: NextRequest) { trace, budget: budget || 'mid', include, + query_timestamp, }, }); diff --git a/hindsight-control-plane/src/components/document-chunk-modal.tsx b/hindsight-control-plane/src/components/document-chunk-modal.tsx new file mode 100644 index 0000000000..e27b786d42 --- /dev/null +++ b/hindsight-control-plane/src/components/document-chunk-modal.tsx @@ -0,0 +1,211 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { client } from '@/lib/api'; +import { useBank } from '@/lib/bank-context'; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, +} from '@/components/ui/dialog'; + +interface DocumentChunkModalProps { + type: 'document' | 'chunk'; + id: string | null; + onClose: () => void; +} + +export function DocumentChunkModal({ type, id, onClose }: DocumentChunkModalProps) { + const { currentBank } = useBank(); + const [data, setData] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + useEffect(() => { + if (!id) return; + + const loadData = async () => { + setLoading(true); + setError(null); + try { + if (type === 'document') { + if (!currentBank) { + setError('No bank selected'); + return; + } + const doc = await client.getDocument(id, currentBank); + setData(doc); + } else { + const chunk = await client.getChunk(id); + setData(chunk); + } + } catch (err) { + console.error(`Error loading ${type}:`, err); + setError((err as Error).message); + } finally { + setLoading(false); + } + }; + + loadData(); + }, [id, type, currentBank]); + + const isOpen = id !== null; + + return ( + !open && onClose()}> + + + + {type === 'document' ? 'Document Details' : 'Chunk Details'} + + + {type === 'document' + ? 'View the original document text and metadata' + : 'View the chunk text and metadata'} + + + +
+ {loading ? ( +
+
+
+
+ Loading {type}... +
+
+
+ ) : error ? ( +
+
+
+
Error: {error}
+
+
+ ) : data ? ( +
+ {type === 'document' ? ( + <> +
+
+
+ Document ID +
+
{data.id}
+
+ {data.created_at && ( +
+
+
+ Created +
+
+ {new Date(data.created_at).toLocaleString()} +
+
+
+
+ Memory Units +
+
{data.memory_unit_count}
+
+
+ )} + {data.original_text && ( +
+
+ Text Length +
+
+ {data.original_text.length.toLocaleString()} characters +
+
+ )} +
+ + {data.original_text && ( +
+
+ Original Text +
+
+
+                          {data.original_text}
+                        
+
+
+ )} + + ) : ( + <> +
+
+
+ Chunk ID +
+
+ {data.chunk_id} +
+
+
+
+
+ Document ID +
+
+ {data.document_id} +
+
+
+
+ Chunk Index +
+
{data.chunk_index}
+
+
+ {data.created_at && ( +
+
+ Created +
+
+ {new Date(data.created_at).toLocaleString()} +
+
+ )} + {data.chunk_text && ( +
+
+ Text Length +
+
+ {data.chunk_text.length.toLocaleString()} characters +
+
+ )} +
+ + {data.chunk_text && ( +
+
+ Chunk Text +
+
+
+                          {data.chunk_text}
+                        
+
+
+ )} + + )} +
+ ) : null} +
+
+
+ ); +} diff --git a/hindsight-control-plane/src/components/search-debug-view.tsx b/hindsight-control-plane/src/components/search-debug-view.tsx index ff6ad0de93..92aa974fbf 100644 --- a/hindsight-control-plane/src/components/search-debug-view.tsx +++ b/hindsight-control-plane/src/components/search-debug-view.tsx @@ -560,7 +560,7 @@ export function SearchDebugView() { {/* Parameters Grid */} -
+
@@ -615,6 +615,20 @@ export function SearchDebugView() { />
+
+ + + updatePane(pane.id, { queryDate: e.target.value }) + } + className="w-full" + placeholder="Optional" + /> +

When is the query being asked

+
+
diff --git a/hindsight-control-plane/src/lib/api.ts b/hindsight-control-plane/src/lib/api.ts index 387268e97c..a572cc6616 100644 --- a/hindsight-control-plane/src/lib/api.ts +++ b/hindsight-control-plane/src/lib/api.ts @@ -52,6 +52,7 @@ export class ControlPlaneClient { entities?: { max_tokens: number } | null; chunks?: { max_tokens: number } | null; }; + query_timestamp?: string; }) { return this.fetchApi('/api/recall', { method: 'POST', From dec85b9446c96b16f1103a4f03a72e8e3a19ae52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Boschi?= Date: Wed, 3 Dec 2025 15:47:42 +0100 Subject: [PATCH 2/2] fix ci and release --- .github/workflows/release.yml | 2 - .github/workflows/test.yml | 2 - .../hindsight_api/engine/memory_engine.py | 16 ++++-- .../hindsight_api/engine/query_analyzer.py | 28 ++++++++++ .../engine/retain/fact_extraction.py | 13 +++-- .../hindsight_api/engine/search/retrieval.py | 8 +-- hindsight-api/tests/test_query_analyzer.py | 51 +++++++++++++++++++ .../src/components/data-view.tsx | 22 ++++++-- 8 files changed, 121 insertions(+), 21 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 94c574a9fe..b7a6d38b57 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -202,8 +202,6 @@ jobs: platforms: linux/amd64,linux/arm64 tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max package-helm-chart: runs-on: ubuntu-latest diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ceebb21bbf..c59d9a16f8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -125,8 +125,6 @@ jobs: file: docker/standalone/Dockerfile target: ${{ matrix.target }} push: false - cache-from: type=gha - cache-to: type=gha,mode=max test-api: runs-on: ubuntu-latest diff --git a/hindsight-api/hindsight_api/engine/memory_engine.py b/hindsight-api/hindsight_api/engine/memory_engine.py index e1bb27a3d7..0a24d440e0 100644 --- a/hindsight-api/hindsight_api/engine/memory_engine.py +++ b/hindsight-api/hindsight_api/engine/memory_engine.py @@ -1084,7 +1084,8 @@ async def _search_with_retries( temporal_results = [] aggregated_timings = {"semantic": 0.0, "bm25": 0.0, "graph": 0.0, "temporal": 0.0} - for idx, (ft_semantic, ft_bm25, ft_graph, ft_temporal, ft_timings) in enumerate(all_retrievals): + detected_temporal_constraint = None + for idx, (ft_semantic, ft_bm25, ft_graph, ft_temporal, ft_timings, ft_temporal_constraint) in enumerate(all_retrievals): # Log fact types in this retrieval batch ft_name = fact_type[idx] if idx < len(fact_type) else "unknown" logger.debug(f"[SEARCH {search_id}] Fact type '{ft_name}': semantic={len(ft_semantic)}, bm25={len(ft_bm25)}, graph={len(ft_graph)}, temporal={len(ft_temporal) if ft_temporal else 0}") @@ -1097,6 +1098,9 @@ async def _search_with_retries( # Track max timing for each method (since they run in parallel across fact types) for method, duration in ft_timings.items(): aggregated_timings[method] = max(aggregated_timings[method], duration) + # Capture temporal constraint (same across all fact types) + if ft_temporal_constraint: + detected_temporal_constraint = ft_temporal_constraint # If no temporal results from any fact type, set to None if not temporal_results: @@ -1120,9 +1124,13 @@ async def _search_with_retries( f"bm25={len(bm25_results)}({aggregated_timings['bm25']:.3f}s)", f"graph={len(graph_results)}({aggregated_timings['graph']:.3f}s)" ] - if temporal_results: - timing_parts.append(f"temporal={len(temporal_results)}({aggregated_timings['temporal']:.3f}s)") - log_buffer.append(f" [2] {total_retrievals}-way retrieval ({len(fact_type)} fact_types): {', '.join(timing_parts)} in {step_duration:.3f}s") + temporal_info = "" + if detected_temporal_constraint: + start_dt, end_dt = detected_temporal_constraint + temporal_count = len(temporal_results) if temporal_results else 0 + timing_parts.append(f"temporal={temporal_count}({aggregated_timings['temporal']:.3f}s)") + temporal_info = f" | temporal_range={start_dt.strftime('%Y-%m-%d')} to {end_dt.strftime('%Y-%m-%d')}" + log_buffer.append(f" [2] {total_retrievals}-way retrieval ({len(fact_type)} fact_types): {', '.join(timing_parts)} in {step_duration:.3f}s{temporal_info}") # Record retrieval results for tracer (convert typed results to old format) if tracer: diff --git a/hindsight-api/hindsight_api/engine/query_analyzer.py b/hindsight-api/hindsight_api/engine/query_analyzer.py index 8651817d9f..8ee2f4b0a3 100644 --- a/hindsight-api/hindsight_api/engine/query_analyzer.py +++ b/hindsight-api/hindsight_api/engine/query_analyzer.py @@ -184,6 +184,34 @@ def constraint(start: datetime, end: datetime) -> TemporalConstraint: if re.search(r'\b(today|hoy|oggi|aujourd\'?hui|heute)\b', query, re.IGNORECASE): return constraint(reference_date, reference_date) + # "a couple of days ago" / "a few days ago" patterns + # These are imprecise so we create a range + if re.search(r'\b(a\s+)?couple\s+(of\s+)?days?\s+ago\b', query, re.IGNORECASE): + # "a couple of days" = approximately 2 days, give range of 1-3 days + return constraint(reference_date - timedelta(days=3), reference_date - timedelta(days=1)) + + if re.search(r'\b(a\s+)?few\s+days?\s+ago\b', query, re.IGNORECASE): + # "a few days" = approximately 3-4 days, give range of 2-5 days + return constraint(reference_date - timedelta(days=5), reference_date - timedelta(days=2)) + + # "a couple of weeks ago" / "a few weeks ago" patterns + if re.search(r'\b(a\s+)?couple\s+(of\s+)?weeks?\s+ago\b', query, re.IGNORECASE): + # "a couple of weeks" = approximately 2 weeks, give range of 1-3 weeks + return constraint(reference_date - timedelta(weeks=3), reference_date - timedelta(weeks=1)) + + if re.search(r'\b(a\s+)?few\s+weeks?\s+ago\b', query, re.IGNORECASE): + # "a few weeks" = approximately 3-4 weeks, give range of 2-5 weeks + return constraint(reference_date - timedelta(weeks=5), reference_date - timedelta(weeks=2)) + + # "a couple of months ago" / "a few months ago" patterns + if re.search(r'\b(a\s+)?couple\s+(of\s+)?months?\s+ago\b', query, re.IGNORECASE): + # "a couple of months" = approximately 2 months, give range of 1-3 months + return constraint(reference_date - timedelta(days=90), reference_date - timedelta(days=30)) + + if re.search(r'\b(a\s+)?few\s+months?\s+ago\b', query, re.IGNORECASE): + # "a few months" = approximately 3-4 months, give range of 2-5 months + return constraint(reference_date - timedelta(days=150), reference_date - timedelta(days=60)) + # Last week patterns (English, Spanish, Italian, French, German) if re.search(r'\b(last\s+week|la\s+semana\s+pasada|la\s+settimana\s+scorsa|la\s+semaine\s+derni[eè]re|letzte\s+woche)\b', query, re.IGNORECASE): start = reference_date - timedelta(days=reference_date.weekday() + 7) diff --git a/hindsight-api/hindsight_api/engine/retain/fact_extraction.py b/hindsight-api/hindsight_api/engine/retain/fact_extraction.py index 5169fab2ca..c18ed5e19f 100644 --- a/hindsight-api/hindsight_api/engine/retain/fact_extraction.py +++ b/hindsight-api/hindsight_api/engine/retain/fact_extraction.py @@ -330,7 +330,9 @@ async def _extract_facts_from_chunk( For EACH fact, CAPTURE ALL DETAILS - NEVER SUMMARIZE OR OMIT: 1. **what**: WHAT happened - COMPLETE description with ALL specifics (objects, actions, quantities, details) -2. **when**: WHEN it happened - ALWAYS include temporal info (dates, times, durations, relative times) +2. **when**: WHEN it happened - ALWAYS include temporal info with DAY OF WEEK (e.g., "Monday, June 10, 2024") + - Always include the day name: Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday + - Format: "day_name, month day, year" (e.g., "Saturday, June 9, 2024") 3. **where**: WHERE it happened or is about - SPECIFIC locations, places, areas, regions (if applicable) 4. **who**: WHO is involved - ALL people/entities with FULL relationships and background 5. **why**: WHY it matters - ALL emotions, preferences, motivations, significance, nuance @@ -350,7 +352,7 @@ async def _extract_facts_from_chunk( CORRECT output: - what: "Emily got married to Sarah at a rooftop garden ceremony" -- when: "in June 2024, after dating for 5 years" +- when: "Saturday, June 8, 2024, after dating for 5 years" - where: "downtown San Francisco, at a rooftop garden venue" - who: "Emily (user's college roommate), Sarah (Emily's partner of 5 years)" - why: "User found it romantic and beautiful, dreams of similar outdoor ceremony" @@ -366,7 +368,8 @@ async def _extract_facts_from_chunk( ══════════════════════════════════════════════════════════════════════════ For EVENTS (fact_kind="event"): -- Convert relative dates → absolute: "yesterday" on March 15 → "March 14, 2024" +- Convert relative dates → absolute WITH DAY OF WEEK: "yesterday" on Saturday March 15 → "Friday, March 14, 2024" +- Always include the day name (Monday, Tuesday, etc.) in the 'when' field - Set occurred_start/occurred_end to WHEN IT HAPPENED (not when mentioned) For CONVERSATIONS (fact_kind="conversation"): @@ -468,10 +471,12 @@ async def _extract_facts_from_chunk( last_error = None # Build user message with metadata and chunk content in a clear format + # Format event_date with day of week for better temporal reasoning + event_date_formatted = event_date.strftime('%A, %B %d, %Y') # e.g., "Monday, June 10, 2024" user_message = f"""Extract facts from the following text chunk. Chunk: {chunk_index + 1}/{total_chunks} -Event Date: {event_date.isoformat()} +Event Date: {event_date_formatted} ({event_date.isoformat()}) Context: {context if context else 'none'} Text: diff --git a/hindsight-api/hindsight_api/engine/search/retrieval.py b/hindsight-api/hindsight_api/engine/search/retrieval.py index 82ce466fc9..fdb23a8ba7 100644 --- a/hindsight-api/hindsight_api/engine/search/retrieval.py +++ b/hindsight-api/hindsight_api/engine/search/retrieval.py @@ -452,7 +452,7 @@ async def retrieve_parallel( thinking_budget: int, question_date: Optional[datetime] = None, query_analyzer: Optional["QueryAnalyzer"] = None -) -> Tuple[List[RetrievalResult], List[RetrievalResult], List[RetrievalResult], Optional[List[RetrievalResult]], Dict[str, float]]: +) -> Tuple[List[RetrievalResult], List[RetrievalResult], List[RetrievalResult], Optional[List[RetrievalResult]], Dict[str, float], Optional[Tuple[datetime, datetime]]]: """ Run 3-way or 4-way parallel retrieval (adds temporal if detected). @@ -467,10 +467,11 @@ async def retrieve_parallel( query_analyzer: Query analyzer to use (defaults to TransformerQueryAnalyzer) Returns: - Tuple of (semantic_results, bm25_results, graph_results, temporal_results, timings) + Tuple of (semantic_results, bm25_results, graph_results, temporal_results, timings, temporal_constraint) Each results list contains RetrievalResult objects temporal_results is None if no temporal constraint detected timings is a dict with per-method latencies in seconds + temporal_constraint is the (start_date, end_date) tuple if detected, else None """ # Detect temporal constraint from .temporal_extraction import extract_temporal_constraint @@ -481,7 +482,6 @@ async def retrieve_parallel( temporal_constraint = extract_temporal_constraint( query_text, reference_date=question_date, analyzer=query_analyzer ) - logger.info(f"[TEMPORAL] Query: {query_text[:50]}... -> constraint={temporal_constraint}") # Wrapper to track timing for each retrieval method async def timed_retrieval(name: str, coro): @@ -534,4 +534,4 @@ async def run_temporal(start_date, end_date): graph_results, _, timings["graph"] = results[2] temporal_results = None - return semantic_results, bm25_results, graph_results, temporal_results, timings + return semantic_results, bm25_results, graph_results, temporal_results, timings, temporal_constraint diff --git a/hindsight-api/tests/test_query_analyzer.py b/hindsight-api/tests/test_query_analyzer.py index 591120eab9..2f892ffbe7 100644 --- a/hindsight-api/tests/test_query_analyzer.py +++ b/hindsight-api/tests/test_query_analyzer.py @@ -232,3 +232,54 @@ def test_query_analyzer_last_weekend(query_analyzer): assert analysis.temporal_constraint.end_date.day == 12 # Sunday +def test_query_analyzer_couple_days_ago(query_analyzer): + """Test extraction of 'a couple of days ago' colloquial expression.""" + reference_date = datetime(2025, 1, 15, 12, 0, 0) + + query = "I mentioned cooking something for my friend a couple of days ago. What was it?" + analysis = query_analyzer.analyze(query, reference_date) + + print(f"\nQuery: '{query}'") + print(f"Reference date: {reference_date.strftime('%A, %Y-%m-%d')}") + print(f"Analysis: {analysis}") + + assert analysis.temporal_constraint is not None, "Should extract temporal constraint for 'a couple of days ago'" + # Range should be 1-3 days ago: Jan 12-14 + assert analysis.temporal_constraint.start_date.day == 12 + assert analysis.temporal_constraint.end_date.day == 14 + + +def test_query_analyzer_few_days_ago(query_analyzer): + """Test extraction of 'a few days ago' colloquial expression.""" + reference_date = datetime(2025, 1, 15, 12, 0, 0) + + query = "What did I do a few days ago?" + analysis = query_analyzer.analyze(query, reference_date) + + print(f"\nQuery: '{query}'") + print(f"Reference date: {reference_date.strftime('%A, %Y-%m-%d')}") + print(f"Analysis: {analysis}") + + assert analysis.temporal_constraint is not None, "Should extract temporal constraint for 'a few days ago'" + # Range should be 2-5 days ago: Jan 10-13 + assert analysis.temporal_constraint.start_date.day == 10 + assert analysis.temporal_constraint.end_date.day == 13 + + +def test_query_analyzer_couple_weeks_ago(query_analyzer): + """Test extraction of 'a couple of weeks ago' colloquial expression.""" + reference_date = datetime(2025, 1, 15, 12, 0, 0) + + query = "a couple of weeks ago we discussed this" + analysis = query_analyzer.analyze(query, reference_date) + + print(f"\nQuery: '{query}'") + print(f"Reference date: {reference_date.strftime('%A, %Y-%m-%d')}") + print(f"Analysis: {analysis}") + + assert analysis.temporal_constraint is not None, "Should extract temporal constraint for 'a couple of weeks ago'" + # Range should be 1-3 weeks ago + assert analysis.temporal_constraint.start_date.month == 12 # Dec 25 (3 weeks before Jan 15) + assert analysis.temporal_constraint.end_date.month == 1 # Jan 8 (1 week before Jan 15) + + diff --git a/hindsight-control-plane/src/components/data-view.tsx b/hindsight-control-plane/src/components/data-view.tsx index 64e7f86ddb..10dfe590fc 100644 --- a/hindsight-control-plane/src/components/data-view.tsx +++ b/hindsight-control-plane/src/components/data-view.tsx @@ -320,8 +320,8 @@ export function DataView({ factType }: DataViewProps) { )} {viewMode === 'table' && ( -
-
+
+
ID Text Context - Occurred + Occurred + Mentioned Actions @@ -367,6 +368,9 @@ export function DataView({ factType }: DataViewProps) { const occurredDisplay = row.occurred_start ? new Date(row.occurred_start).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }) : null; + const mentionedDisplay = row.mentioned_at + ? new Date(row.mentioned_at).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }) + : null; return ( ) : '-'} + + {mentionedDisplay ? ( + + + {mentionedDisplay} + + ) : '-'} +
- {/* Memory Detail Panel for Table View */} + {/* Memory Detail Panel for Table View - Fixed on Right */} {selectedTableMemory && ( -
+
setSelectedTableMemory(null)}