fleet-memory/hindsight-control-plane/src/components/documents-view.tsx
DK09876 dfccbf29f1
Added hindsight_liteLLM implementation (#17)
* Added hindsight_liteLLM implementation

* Add instructions for entity vs bank id

* Add another line about entity

* Address PR review comments and enhance litellm integration

- Remove deprecated limit parameter from recall() and arecall() functions
  since Hindsight uses budget/max_tokens for result control
- Remove dead MODEL_MAX_OUTPUT_TOKENS dict and max_output_tokens property
  from LLMProvider (superseded by hardcoded max_completion_tokens)
- Add test-litellm-integration job to CI workflow
- Add reflect API support with use_reflect config option
- Add verbose mode debug info via get_last_injection_debug()
- Add entity_id support for multi-user memory isolation
- Add retain() and reflect() wrapper functions
- Update docstrings and examples

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Make max_memories optional to allow unlimited memory injection

- Change max_memories default from 10 to None (no limit)
- When max_memories is None, all results from the API are used
- Fix recall result handling to properly detect list vs object return
- Update wrappers (OpenAI, Anthropic) with same optional behavior

This allows users to control memory limits via max_memory_tokens
and recall_budget without an artificial count limit.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Remove entity_id from hindsight_litellm; add gpt-4o token cap

Multi-user support now uses separate bank_ids per user instead of
entity_id scoping (e.g., bank_id=f"user-{user_id}"). This simplifies
the API and aligns with the Hindsight architecture.

Also fixes max_completion_tokens error for gpt-4o models by capping
the value at 16384 (gpt-4o's limit) instead of sending the default
65000 which exceeds the model's supported maximum.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Fix dark mode styling across Control Plane UI components

Improvements to ensure proper text visibility and contrast in both light
and dark modes:

- Add global CSS rules for datetime-local calendar picker icon visibility
  using filter: invert() for both light (0.5) and dark (1) modes
- Fix text colors in dialog components to use theme-aware foreground colors
- Update memory detail panel, document/chunk modals, and data views to use
  proper dark mode text classes (text-foreground, text-card-foreground)
- Fix form labels, headings, and content text in bank selector dialogs
- Update entities view and documents view table styling for dark mode
- Bump package versions to 0.1.4

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Remove session_id feature and add How It Works section to README

- Remove session_id and session management (new_session, set_session,
  get_session) from config.py, callbacks.py, and __init__.py
- Session management was a client-only abstraction not backed by core API
- Add "How It Works" section to README with visual flow diagram
- Update README to remove session management documentation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Fix readme example

* Add dark mode again

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-15 10:42:19 +01:00

260 lines
11 KiB
TypeScript

'use client';
import { useState, useEffect } from 'react';
import { client } from '@/lib/api';
import { useBank } from '@/lib/bank-context';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import { X } from 'lucide-react';
export function DocumentsView() {
const { currentBank } = useBank();
const [documents, setDocuments] = useState<any[]>([]);
const [loading, setLoading] = useState(false);
const [searchQuery, setSearchQuery] = useState('');
const [total, setTotal] = useState(0);
// Document view panel state
const [selectedDocument, setSelectedDocument] = useState<any>(null);
const [loadingDocument, setLoadingDocument] = useState(false);
const loadDocuments = async () => {
if (!currentBank) return;
setLoading(true);
try {
const data: any = await client.listDocuments({
bank_id: currentBank,
q: searchQuery,
limit: 100,
});
setDocuments(data.items || []);
setTotal(data.total || 0);
} catch (error) {
console.error('Error loading documents:', error);
alert('Error loading documents: ' + (error as Error).message);
} finally {
setLoading(false);
}
};
const viewDocumentText = async (documentId: string) => {
if (!currentBank) return;
setLoadingDocument(true);
setSelectedDocument({ id: documentId }); // Set placeholder to show loading
try {
const doc: any = await client.getDocument(documentId, currentBank);
setSelectedDocument(doc);
} catch (error) {
console.error('Error loading document:', error);
alert('Error loading document: ' + (error as Error).message);
setSelectedDocument(null);
} finally {
setLoadingDocument(false);
}
};
// Auto-load documents when component mounts
useEffect(() => {
if (currentBank) {
loadDocuments();
}
}, [currentBank]);
return (
<div>
{/* Documents List Section */}
{loading ? (
<div className="flex items-center justify-center py-20">
<div className="text-center">
<div className="text-4xl mb-2"></div>
<div className="text-sm text-muted-foreground">Loading documents...</div>
</div>
</div>
) : documents.length > 0 ? (
<div className="mb-4 text-sm text-muted-foreground">
{total} total documents
</div>
) : (
<div className="flex items-center justify-center py-20">
<div className="text-center">
<div className="text-4xl mb-2">📄</div>
<div className="text-sm text-muted-foreground">No documents found</div>
</div>
</div>
)}
{/* Documents List and Detail Panel */}
{documents.length > 0 && (
<>
{/* Documents Table */}
<div className="w-full">
<div className="px-5 mb-4">
<Input
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Search documents (ID)..."
className="max-w-2xl"
/>
</div>
<div className="overflow-x-auto px-5 pb-5">
<Table>
<TableHeader>
<TableRow>
<TableHead>Document ID</TableHead>
<TableHead>Created</TableHead>
<TableHead>Context</TableHead>
<TableHead>Text Length</TableHead>
<TableHead>Memory Units</TableHead>
<TableHead>Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{documents.length > 0 ? (
documents.map((doc) => (
<TableRow
key={doc.id}
className={`cursor-pointer hover:bg-muted/50 ${selectedDocument?.id === doc.id ? 'bg-primary/10' : ''}`}
onClick={() => viewDocumentText(doc.id)}
>
<TableCell title={doc.id} className="text-card-foreground">
{doc.id.length > 30 ? doc.id.substring(0, 30) + '...' : doc.id}
</TableCell>
<TableCell className="text-card-foreground">
{doc.created_at ? new Date(doc.created_at).toLocaleString() : 'N/A'}
</TableCell>
<TableCell className="text-card-foreground">
{doc.retain_params?.context || '-'}
</TableCell>
<TableCell className="text-card-foreground">{doc.text_length?.toLocaleString()} chars</TableCell>
<TableCell className="text-card-foreground">{doc.memory_unit_count}</TableCell>
<TableCell>
<Button
onClick={(e) => {
e.stopPropagation();
viewDocumentText(doc.id);
}}
size="sm"
variant={selectedDocument?.id === doc.id ? 'default' : 'secondary'}
title="View original text"
>
View Text
</Button>
</TableCell>
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={6} className="text-center">
Click "Load Documents" to view data
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
</div>
{/* Document Detail Panel - Fixed on Right */}
{selectedDocument && (
<div className="fixed right-0 top-0 h-screen w-[420px] bg-card border-l-2 border-primary shadow-2xl z-50 overflow-y-auto animate-in slide-in-from-right duration-300 ease-out">
<div className="p-5">
{/* Header with close button */}
<div className="flex justify-between items-center mb-6 pb-4 border-b border-border">
<div>
<h3 className="text-xl font-bold text-foreground">Document Details</h3>
<p className="text-sm text-muted-foreground mt-1">Original document text and metadata</p>
</div>
<Button
variant="secondary"
size="sm"
onClick={() => setSelectedDocument(null)}
className="h-9 px-3 gap-2"
>
<X className="h-4 w-4" />
Close
</Button>
</div>
{loadingDocument ? (
<div className="flex items-center justify-center py-20">
<div className="text-center">
<div className="text-4xl mb-2"></div>
<div className="text-sm text-muted-foreground">Loading document...</div>
</div>
</div>
) : (
<div className="space-y-5">
{/* Document ID */}
<div className="p-4 bg-muted/50 rounded-lg">
<div className="text-xs font-bold text-muted-foreground uppercase mb-2">Document ID</div>
<div className="text-sm font-mono break-all text-card-foreground">{selectedDocument.id}</div>
</div>
{/* Created & Memory Units */}
{selectedDocument.created_at && (
<div className="grid grid-cols-2 gap-4">
<div className="p-4 bg-muted/50 rounded-lg">
<div className="text-xs font-bold text-muted-foreground uppercase mb-2">Created</div>
<div className="text-sm font-medium text-card-foreground">{new Date(selectedDocument.created_at).toLocaleString()}</div>
</div>
<div className="p-4 bg-muted/50 rounded-lg">
<div className="text-xs font-bold text-muted-foreground uppercase mb-2">Memory Units</div>
<div className="text-sm font-medium text-card-foreground">{selectedDocument.memory_unit_count}</div>
</div>
</div>
)}
{/* Text Length */}
{selectedDocument.original_text && (
<div className="p-4 bg-muted/50 rounded-lg">
<div className="text-xs font-bold text-muted-foreground uppercase mb-2">Text Length</div>
<div className="text-sm font-medium text-card-foreground">{selectedDocument.original_text.length.toLocaleString()} characters</div>
</div>
)}
{/* Retain Parameters */}
{selectedDocument.retain_params && (
<div className="p-4 bg-muted/50 rounded-lg">
<div className="text-xs font-bold text-muted-foreground uppercase mb-2">Retain Parameters</div>
<div className="text-sm space-y-2 text-card-foreground">
{selectedDocument.retain_params.context && (
<div><span className="font-semibold">Context:</span> {selectedDocument.retain_params.context}</div>
)}
{selectedDocument.retain_params.event_date && (
<div><span className="font-semibold">Event Date:</span> {new Date(selectedDocument.retain_params.event_date).toLocaleString()}</div>
)}
{selectedDocument.retain_params.metadata && (
<div className="mt-2">
<span className="font-semibold">Metadata:</span>
<pre className="mt-1 text-xs bg-background p-2 rounded text-card-foreground">{JSON.stringify(selectedDocument.retain_params.metadata, null, 2)}</pre>
</div>
)}
</div>
</div>
)}
{/* Original Text */}
{selectedDocument.original_text && (
<div>
<div className="text-xs font-bold text-muted-foreground uppercase mb-2">Original Text</div>
<div className="p-4 bg-muted/50 rounded-lg border border-border max-h-[400px] overflow-y-auto">
<pre className="text-sm whitespace-pre-wrap font-mono leading-relaxed text-card-foreground">{selectedDocument.original_text}</pre>
</div>
</div>
)}
</div>
)}
</div>
</div>
)}
</>
)}
</div>
);
}