feat: port source-first ingestion to os repo

- make source the canonical field across os routes, tools, ui, and mcp
- align fresh-install schema, search, and embedding flows with source-first ingestion

Generated with Claude Code
This commit is contained in:
“BeeRad”
2026-03-20 12:36:26 +11:00
parent 41f8498d4a
commit 28e570696c
34 changed files with 588 additions and 521 deletions
+132 -139
View File
@@ -9,10 +9,10 @@ import { parseNodeMarkers } from '@/tools/infrastructure/nodeFormatter';
import { Node, NodeConnection, Chunk } from '@/types/database';
import DimensionTags from './dimensions/DimensionTags';
import { getNodeIcon } from '@/utils/nodeIcons';
import { openExternalUrl, shouldOpenExternally } from '@/utils/openExternalUrl';
import { useDimensionIcons } from '@/context/DimensionIconsContext';
import ConfirmDialog from '../common/ConfirmDialog';
import { SourceReader } from './source';
import { openExternalUrl, shouldOpenExternally } from '@/utils/openExternalUrl';
interface NodeSearchResult {
@@ -36,8 +36,8 @@ interface FocusPanelProps {
}
export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeClick, onTabClose, refreshTrigger, onOpenInOtherSlot, hideTabBar, onTextSelect, highlightedPassage }: FocusPanelProps) {
const { dimensionIcons } = useDimensionIcons();
const [nodesData, setNodesData] = useState<Record<number, Node>>({});
const { dimensionIcons } = useDimensionIcons();
// Context menu state
const [contextMenu, setContextMenu] = useState<{ x: number; y: number; tabId: number } | null>(null);
@@ -97,10 +97,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
const [chunkExpanded, setChunkExpanded] = useState<{ [key: number]: boolean }>({});
// Edges expand/collapse state
// edgesExpanded removed — all connections shown in Edges tab
// Connections section collapsed state (default closed)
const [edgeSearchOpen, setEdgeSearchOpen] = useState(false);
const [edgesExpanded, setEdgesExpanded] = useState<{ [key: number]: boolean }>({});
// Title expanded state for click-to-expand full title
const [titleExpanded, setTitleExpanded] = useState<{ [key: number]: boolean }>({});
@@ -108,7 +105,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
// Description regeneration state
const [regeneratingDescription, setRegeneratingDescription] = useState<number | null>(null);
// Content tab state: 'notes', 'desc', or 'source'
// Content tab state: 'desc', 'notes', 'edges', or 'source'
const [activeContentTab, setActiveContentTab] = useState<'notes' | 'desc' | 'edges' | 'source'>('desc');
// Desc (description) edit mode state
@@ -135,6 +132,9 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
const [loadingChunks, setLoadingChunks] = useState<Set<number>>(new Set());
const [chunksExpanded, setChunksExpanded] = useState<Record<number, boolean>>({});
// Edge creation search toggle
const [edgeSearchOpen, setEdgeSearchOpen] = useState(false);
// Helper: preview edge type based on heuristics (mirrors backend logic)
const previewEdgeType = (explanation: string): { type: string; label: string } | null => {
const norm = (explanation || '').trim().toLowerCase();
@@ -230,13 +230,16 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
setEditingField(null);
setEditingValue('');
setEditingNodeId(null);
// Also clear notes/desc/source edit modes
// Also clear notes/desc/source/edges edit modes
setNotesEditMode(false);
setNotesEditValue('');
setDescEditMode(false);
setDescEditValue('');
setSourceEditMode(false);
setSourceEditValue('');
setEdgeSearchOpen(false);
setNodeSearchQuery('');
setNodeSearchSuggestions([]);
}, [activeTab]);
const fetchNodeData = async (id: number) => {
@@ -356,7 +359,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
const updateData: Record<string, string> = {};
if (editingField === 'notes') {
updateData.content = editingValue;
updateData.notes = editingValue;
} else {
updateData[editingField] = editingValue;
}
@@ -378,7 +381,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
setNodesData(prev => ({ ...prev, [nodeId]: result.node }));
}
// Safety net: ensure edges exist for any tokens present in saved content
// Safety net: ensure edges exist for any tokens present in saved notes
if (editingField === 'notes' && typeof editingValue === 'string') {
try {
const tokens = parseNodeMarkers(editingValue);
@@ -421,7 +424,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
const response = await fetch(`/api/nodes/${nodeId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ notes: contentValue }),
body: JSON.stringify({ content: contentValue }),
});
if (!response.ok) throw new Error('Failed to save');
const result = await response.json();
@@ -497,16 +500,16 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
setDescEditMode(true);
};
// Sync description to source (chunk) and re-embed
// Sync description to source and re-embed
const syncDescToSource = async () => {
if (!activeTab) return;
setDescSaving(true);
try {
// Save description to chunk field
// Save description to source field
const response = await fetch(`/api/nodes/${activeTab}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ chunk: descEditValue }),
body: JSON.stringify({ source: descEditValue }),
});
if (!response.ok) throw new Error('Failed to sync');
const result = await response.json();
@@ -533,7 +536,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
}
};
// Save notes (content) with explicit Save button
// Save notes-tab content into canonical source
const saveNotes = async () => {
if (!activeTab) return;
setNotesSaving(true);
@@ -541,7 +544,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
const response = await fetch(`/api/nodes/${activeTab}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ notes: notesEditValue }),
body: JSON.stringify({ source: notesEditValue }),
});
if (!response.ok) throw new Error('Failed to save');
const result = await response.json();
@@ -571,8 +574,8 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
setNotesEditMode(false);
setNotesEditValue('');
} catch (e) {
console.error('Error saving notes:', e);
alert('Failed to save notes. Please try again.');
console.error('Error saving source content:', e);
alert('Failed to save content. Please try again.');
} finally {
setNotesSaving(false);
}
@@ -587,11 +590,11 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
// Start editing notes
const startNotesEdit = () => {
if (!activeTab || !nodesData[activeTab]) return;
setNotesEditValue(nodesData[activeTab].notes || '');
setNotesEditValue(nodesData[activeTab].source || nodesData[activeTab].notes || '');
setNotesEditMode(true);
};
// Save source (chunk) with explicit Save button
// Save source with explicit Save button
const saveSource = async () => {
if (!activeTab) return;
setSourceSaving(true);
@@ -599,7 +602,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
const response = await fetch(`/api/nodes/${activeTab}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ chunk: sourceEditValue }),
body: JSON.stringify({ source: sourceEditValue }),
});
if (!response.ok) throw new Error('Failed to save');
const result = await response.json();
@@ -625,7 +628,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
// Start editing source
const startSourceEdit = () => {
if (!activeTab || !nodesData[activeTab]) return;
setSourceEditValue(nodesData[activeTab].chunk || '');
setSourceEditValue(nodesData[activeTab].source || nodesData[activeTab].chunk || '');
setSourceEditMode(true);
};
@@ -641,11 +644,11 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
setSyncing(true);
setShowSyncConfirm(false);
try {
// First, save notes content to chunk field
// First, save notes content to source field
const response = await fetch(`/api/nodes/${activeTab}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ chunk: notesEditValue }),
body: JSON.stringify({ source: notesEditValue }),
});
if (!response.ok) throw new Error('Failed to sync');
const result = await response.json();
@@ -997,14 +1000,14 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
const embedContent = async (nodeId: number) => {
const node = nodesData[nodeId];
const hasNotes = node?.notes?.trim();
const hasChunk = node?.chunk?.trim();
// If chunk is empty but content exists, auto-populate chunk from content
if (!hasChunk && hasNotes) {
const hasSource = (node?.source || node?.chunk)?.trim();
// If source is empty but notes exist, auto-populate source from notes
if (!hasSource && hasNotes) {
try {
const response = await fetch(`/api/nodes/${nodeId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ chunk: hasNotes })
body: JSON.stringify({ source: hasNotes })
});
if (response.ok) {
@@ -1014,13 +1017,13 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
}
}
} catch (error) {
console.error('Failed to auto-populate chunk for embedding:', error);
console.error('Failed to auto-populate source for embedding:', error);
return;
}
}
// If neither content nor chunk exist, require content
if (!hasNotes && !hasChunk) {
startEdit('content', '');
// If neither notes nor source exist, require notes
if (!hasNotes && !hasSource) {
startEdit('notes', '');
return;
}
setEmbeddingNode(nodeId);
@@ -1119,7 +1122,6 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
setAddingEdge(null);
setEdgeExplanation('');
setPendingEdgeTarget(null);
setEdgeSearchOpen(false);
} catch (error) {
console.error('Error creating edge:', error);
@@ -1157,7 +1159,6 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
setPendingEdgeTarget(null);
setNodeSearchQuery('');
setNodeSearchSuggestions([]);
setEdgeSearchOpen(false);
} catch (error) {
console.error('Error creating edge:', error);
@@ -1277,63 +1278,55 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
borderRadius: '8px',
maxHeight: '200px',
overflowY: 'auto',
zIndex: 10,
boxShadow: '0 8px 24px rgba(0, 0, 0, 0.4)'
zIndex: 100,
boxShadow: '0 8px 24px rgba(0, 0, 0, 0.5)',
}}>
{nodeSearchSuggestions.map((suggestion, index) => (
<div
key={suggestion.id}
onClick={() => handleSelectNodeSuggestion(suggestion)}
onClick={() => {
handleSelectNodeSuggestion(suggestion);
setNodeSearchQuery('');
setNodeSearchSuggestions([]);
setEdgeSearchOpen(false);
}}
style={{
padding: '10px 14px',
padding: '8px 12px',
cursor: 'pointer',
borderBottom: index < nodeSearchSuggestions.length - 1 ? '1px solid var(--rah-bg-active)' : 'none',
borderBottom: index < nodeSearchSuggestions.length - 1 ? '1px solid var(--rah-border)' : 'none',
background: index === selectedSearchIndex ? 'var(--rah-bg-active)' : 'transparent',
transition: 'background 100ms ease',
display: 'flex',
alignItems: 'center',
gap: '10px'
}}
onMouseEnter={(e) => {
if (index !== selectedSearchIndex) {
e.currentTarget.style.background = 'var(--rah-bg-active)';
}
}}
onMouseLeave={(e) => {
if (index !== selectedSearchIndex) {
e.currentTarget.style.background = 'transparent';
}
gap: '8px',
}}
onMouseEnter={(e) => { e.currentTarget.style.background = 'var(--rah-bg-active)'; }}
onMouseLeave={(e) => { if (index !== selectedSearchIndex) e.currentTarget.style.background = 'transparent'; }}
>
<span style={{
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '9px',
fontWeight: 600,
color: 'var(--rah-bg-base)',
background: '#22c55e',
padding: '3px 6px',
borderRadius: '6px',
minWidth: '24px',
textAlign: 'center',
color: 'var(--rah-text-inverse)',
background: 'var(--rah-accent-green)',
padding: '2px 5px',
borderRadius: '4px',
flexShrink: 0,
fontFamily: "'SF Mono', 'Fira Code', monospace"
fontFamily: "'SF Mono', 'Fira Code', monospace",
}}>
{suggestion.id}
</span>
<span style={{
fontSize: '13px',
fontSize: '12px',
color: 'var(--rah-text-base)',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
flex: 1
flex: 1,
}}>
{suggestion.title}
</span>
{index === selectedSearchIndex && (
<span style={{ color: 'var(--rah-text-muted)', fontSize: '13px' }}></span>
<span style={{ color: 'var(--rah-text-muted)', fontSize: '12px' }}></span>
)}
</div>
))}
@@ -1342,10 +1335,10 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
</div>
)}
{/* Connections List */}
{/* Connections list */}
<div style={{ flex: 1, overflowY: 'auto' }}>
{loadingEdges.has(activeTab) ? (
<div style={{ color: 'var(--rah-text-muted)', fontSize: '12px', fontStyle: 'italic', padding: '16px 0', textAlign: 'center' }}>Loading connections</div>
<div style={{ color: 'var(--rah-text-muted)', fontSize: '12px', fontStyle: 'italic', padding: '8px 0' }}>Loading connections</div>
) : (() => {
const list = edgesData[activeTab] || [];
if (list.length === 0) {
@@ -1358,18 +1351,18 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
key={connection.id}
style={{
padding: '6px 0',
borderBottom: '1px solid var(--rah-bg-panel)',
borderBottom: '1px solid var(--rah-border)',
display: 'flex',
flexDirection: 'column',
gap: '2px',
minWidth: 0,
}}
>
{/* Row 1: arrow + ID + icon + title + delete */}
{/* Row 1: arrow + ID + title + delete */}
<div style={{ display: 'flex', alignItems: 'center', gap: '6px', minWidth: 0 }}>
<span style={{
fontSize: '11px',
color: connection.edge.from_node_id === activeTab ? '#22c55e' : '#f59e0b',
color: connection.edge.from_node_id === activeTab ? 'var(--rah-accent-green)' : '#f59e0b',
fontWeight: 600,
width: '14px',
textAlign: 'center',
@@ -1380,8 +1373,8 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
<span style={{
fontSize: '9px',
fontWeight: 700,
color: 'var(--rah-bg-base)',
background: '#22c55e',
color: 'var(--rah-text-inverse)',
background: 'var(--rah-accent-green)',
padding: '1px 5px',
borderRadius: '4px',
flexShrink: 0,
@@ -1405,7 +1398,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
minWidth: 0,
cursor: 'pointer',
}}
onMouseEnter={(e) => { e.currentTarget.style.color = '#22c55e'; }}
onMouseEnter={(e) => { e.currentTarget.style.color = 'var(--rah-accent-green)'; }}
onMouseLeave={(e) => { e.currentTarget.style.color = 'var(--rah-text-base)'; }}
>
{connection.connected_node.title}
@@ -1414,7 +1407,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
onClick={() => deleteEdge(connection.edge.id)}
disabled={deletingEdge === connection.edge.id}
style={{
color: 'var(--rah-border-stronger)',
color: 'var(--rah-text-muted)',
fontSize: '14px',
background: 'transparent',
border: 'none',
@@ -1424,7 +1417,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
lineHeight: 1,
}}
onMouseEnter={(e) => { if (deletingEdge !== connection.edge.id) e.currentTarget.style.color = '#dc2626'; }}
onMouseLeave={(e) => { if (deletingEdge !== connection.edge.id) e.currentTarget.style.color = 'var(--rah-border-stronger)'; }}
onMouseLeave={(e) => { if (deletingEdge !== connection.edge.id) e.currentTarget.style.color = 'var(--rah-text-muted)'; }}
>
{deletingEdge === connection.edge.id ? '...' : '×'}
</button>
@@ -1464,8 +1457,8 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
disabled={edgeSavingId === connection.edge.id}
style={{
fontSize: '10px',
color: '#000',
background: '#22c55e',
color: 'var(--rah-text-inverse)',
background: 'var(--rah-accent-green)',
border: 'none',
borderRadius: '4px',
padding: '3px 8px',
@@ -1508,9 +1501,9 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
{typeof connection.edge.context?.type === 'string' && (
<span style={{
fontSize: '9px',
color: 'var(--rah-text-muted)',
color: 'var(--rah-text-soft)',
background: 'var(--rah-bg-active)',
border: '1px solid var(--rah-bg-active)',
border: '1px solid var(--rah-border)',
padding: '1px 5px',
borderRadius: '999px',
flexShrink: 0,
@@ -1522,7 +1515,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
<span
style={{
fontSize: '11px',
color: connection.edge.context?.explanation ? '#777' : '#444',
color: connection.edge.context?.explanation ? 'var(--rah-text-muted)' : 'var(--rah-text-soft)',
fontStyle: connection.edge.context?.explanation ? 'normal' : 'italic',
overflow: 'hidden',
textOverflow: 'ellipsis',
@@ -1594,7 +1587,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
<div style={{
display: 'flex',
borderBottom: '1px solid var(--rah-border)',
background: 'var(--rah-bg-surface)',
background: 'var(--rah-bg-base)',
flexShrink: 0,
overflowX: 'auto',
overflowY: 'hidden'
@@ -1631,7 +1624,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
display: 'flex',
alignItems: 'center',
borderRight: '1px solid var(--rah-border)',
background: isActive ? 'var(--rah-bg-subtle)' : 'var(--rah-bg-surface)',
background: isActive ? 'var(--rah-bg-subtle)' : 'var(--rah-bg-base)',
borderBottom: isActive ? '2px solid var(--rah-text-muted)' : 'none',
paddingBottom: isActive ? '0' : '2px',
minWidth: '120px',
@@ -1646,7 +1639,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
padding: '8px 12px',
fontSize: '12px',
fontFamily: 'inherit',
color: isActive ? 'var(--rah-text-active)' : 'var(--rah-text-muted)',
color: isActive ? 'var(--rah-text-active)' : 'var(--rah-text-soft)',
background: 'transparent',
border: 'none',
cursor: 'pointer',
@@ -1708,7 +1701,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
Loading...
</div>
) : !currentNode ? (
<div style={{ color: 'var(var(--rah-text-muted))', fontSize: '13px' }}>Node not found.</div>
<div style={{ color: 'var(--rah-text-muted)', fontSize: '13px' }}>Node not found.</div>
) : nodesData[activeTab] ? (
<div style={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
{/* URL Row - Above Title */}
@@ -1791,8 +1784,8 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
return;
}
const link = nodesData[activeTab].link;
if (!link || !shouldOpenExternally(link)) {
const link = nodesData[activeTab].link || '';
if (!shouldOpenExternally(link)) {
return;
}
@@ -1819,7 +1812,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
<span
onClick={() => startEdit('link', '')}
style={{
color: 'var(--rah-text-muted)',
color: '#555',
fontSize: '11px',
cursor: 'pointer',
fontStyle: 'italic'
@@ -1850,8 +1843,8 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
}}
style={{
display: 'inline-block',
background: '#22c55e',
color: 'var(--rah-bg-base)',
background: 'var(--rah-accent-green)',
color: 'var(--rah-text-inverse)',
fontSize: '10px',
fontWeight: 600,
padding: '2px 6px',
@@ -1864,7 +1857,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
{activeTab}
</span>
{/* Node type icon */}
{/* Node icon */}
{nodesData[activeTab] && (
<span style={{ flexShrink: 0, display: 'flex', alignItems: 'center' }}>
{getNodeIcon(nodesData[activeTab], dimensionIcons, 18)}
@@ -1938,7 +1931,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
title={titleExpanded[activeTab] ? undefined : (nodesData[activeTab].title || 'Untitled')}
>
{nodesData[activeTab].title || 'Untitled'}
{savingField === 'title' && <span style={{ color: 'var(--rah-text-muted)', fontSize: '10px', marginLeft: '6px' }}>saving...</span>}
{savingField === 'title' && <span style={{ color: '#555', fontSize: '10px', marginLeft: '6px' }}>saving...</span>}
</h1>
)}
@@ -1948,14 +1941,14 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
style={{
display: 'flex',
alignItems: 'center',
gap: '4px',
gap: '6px',
padding: '4px 8px',
fontSize: '10px',
fontWeight: 500,
color: activeContentTab === 'edges' ? '#22c55e' : 'var(--rah-text-muted)',
color: activeContentTab === 'edges' ? 'var(--rah-accent-green)' : 'var(--rah-text-muted)',
background: activeContentTab === 'edges' ? '#0f2417' : 'transparent',
border: '1px solid',
borderColor: activeContentTab === 'edges' ? '#22c55e' : 'var(--rah-border-strong)',
borderColor: activeContentTab === 'edges' ? 'var(--rah-accent-green)' : 'var(--rah-border-strong)',
borderRadius: '4px',
cursor: 'pointer',
transition: 'all 0.2s',
@@ -1963,7 +1956,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
}}
onMouseEnter={(e) => {
if (activeContentTab !== 'edges') {
e.currentTarget.style.color = '#22c55e';
e.currentTarget.style.color = 'var(--rah-accent-green)';
e.currentTarget.style.borderColor = '#166534';
}
}}
@@ -1983,18 +1976,18 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
style={{
minWidth: '18px',
height: '18px',
padding: '0 6px',
borderRadius: '999px',
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
padding: '0 6px',
background: activeContentTab === 'edges' ? 'rgba(34, 197, 94, 0.18)' : 'var(--rah-bg-active)',
color: activeContentTab === 'edges' ? '#a7f3b8' : 'var(--rah-text-muted)',
background: activeContentTab === 'edges' ? 'var(--rah-accent-green-soft)' : 'var(--rah-bg-active)',
color: activeContentTab === 'edges' ? 'var(--rah-accent-green)' : 'var(--rah-text-soft)',
fontSize: '10px',
lineHeight: 1,
}}
>
{(edgesData[activeTab] || []).length}
{edgesData[activeTab]?.length ?? 0}
</span>
</span>
)}
@@ -2084,7 +2077,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
borderBottom: '1px solid var(--rah-border)'
}}>
<button
onClick={() => { setActiveContentTab('desc'); setNotesEditMode(false); setSourceEditMode(false); }}
onClick={() => { setActiveContentTab('desc'); setNotesEditMode(false); setSourceEditMode(false); setEdgeSearchOpen(false); }}
style={{
padding: '8px 16px',
fontSize: '11px',
@@ -2092,7 +2085,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
color: activeContentTab === 'desc' ? 'var(--rah-text-base)' : 'var(--rah-text-muted)',
background: 'transparent',
border: 'none',
borderBottom: activeContentTab === 'desc' ? '2px solid #22c55e' : '2px solid transparent',
borderBottom: activeContentTab === 'desc' ? '2px solid var(--rah-accent-green)' : '2px solid transparent',
cursor: 'pointer',
transition: 'all 0.15s',
marginBottom: '-1px'
@@ -2101,7 +2094,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
Desc
</button>
<button
onClick={() => { setActiveContentTab('notes'); setDescEditMode(false); setSourceEditMode(false); }}
onClick={() => { setActiveContentTab('notes'); setDescEditMode(false); setSourceEditMode(false); setEdgeSearchOpen(false); }}
style={{
padding: '8px 16px',
fontSize: '11px',
@@ -2109,7 +2102,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
color: activeContentTab === 'notes' ? 'var(--rah-text-base)' : 'var(--rah-text-muted)',
background: 'transparent',
border: 'none',
borderBottom: activeContentTab === 'notes' ? '2px solid #22c55e' : '2px solid transparent',
borderBottom: activeContentTab === 'notes' ? '2px solid var(--rah-accent-green)' : '2px solid transparent',
cursor: 'pointer',
transition: 'all 0.15s',
marginBottom: '-1px'
@@ -2126,13 +2119,13 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
color: activeContentTab === 'edges' ? 'var(--rah-text-base)' : 'var(--rah-text-muted)',
background: 'transparent',
border: 'none',
borderBottom: activeContentTab === 'edges' ? '2px solid #22c55e' : '2px solid transparent',
borderBottom: activeContentTab === 'edges' ? '2px solid var(--rah-accent-green)' : '2px solid transparent',
cursor: 'pointer',
transition: 'all 0.15s',
marginBottom: '-1px'
}}
>
Edges{activeTab && edgesData[activeTab]?.length ? ` (${edgesData[activeTab].length})` : ''}
Edges
</button>
<button
onClick={() => { setActiveContentTab('source'); setDescEditMode(false); setNotesEditMode(false); setEdgeSearchOpen(false); }}
@@ -2143,7 +2136,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
color: activeContentTab === 'source' ? 'var(--rah-text-base)' : 'var(--rah-text-muted)',
background: 'transparent',
border: 'none',
borderBottom: activeContentTab === 'source' ? '2px solid #22c55e' : '2px solid transparent',
borderBottom: activeContentTab === 'source' ? '2px solid var(--rah-accent-green)' : '2px solid transparent',
cursor: 'pointer',
transition: 'all 0.15s',
marginBottom: '-1px'
@@ -2164,7 +2157,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
gap: '4px',
padding: '4px 8px',
fontSize: '10px',
color: 'var(--rah-text-muted)',
color: 'var(--rah-text-soft)',
background: 'transparent',
border: '1px solid var(--rah-border-strong)',
borderRadius: '4px',
@@ -2183,7 +2176,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
gap: '4px',
padding: '4px 8px',
fontSize: '10px',
color: 'var(--rah-text-muted)',
color: 'var(--rah-text-soft)',
background: 'transparent',
border: '1px solid var(--rah-border-strong)',
borderRadius: '4px',
@@ -2208,7 +2201,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
gap: '4px',
padding: '4px 8px',
fontSize: '10px',
color: '#22c55e',
color: 'var(--rah-accent-green)',
background: 'transparent',
border: '1px solid #166534',
borderRadius: '4px',
@@ -2227,7 +2220,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
gap: '4px',
padding: '4px 8px',
fontSize: '10px',
color: 'var(--rah-text-muted)',
color: 'var(--rah-text-soft)',
background: 'transparent',
border: '1px solid var(--rah-border-strong)',
borderRadius: '4px',
@@ -2268,8 +2261,8 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
gap: '4px',
padding: '4px 8px',
fontSize: '10px',
color: edgeSearchOpen ? '#000' : '#22c55e',
background: edgeSearchOpen ? '#22c55e' : 'transparent',
color: edgeSearchOpen ? 'var(--rah-text-inverse)' : 'var(--rah-accent-green)',
background: edgeSearchOpen ? 'var(--rah-accent-green)' : 'transparent',
border: '1px solid #166534',
borderRadius: '4px',
cursor: 'pointer',
@@ -2383,7 +2376,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
gap: '4px',
padding: '6px 12px',
fontSize: '11px',
color: 'var(--rah-text-muted)',
color: 'var(--rah-text-soft)',
background: 'transparent',
border: '1px solid var(--rah-border-strong)',
borderRadius: '4px',
@@ -2402,8 +2395,8 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
gap: '4px',
padding: '6px 12px',
fontSize: '11px',
color: '#000',
background: '#22c55e',
color: 'var(--rah-text-inverse)',
background: 'var(--rah-accent-green)',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
@@ -2433,7 +2426,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
<div
onClick={startDescEdit}
style={{
color: 'var(--rah-text-muted)',
color: '#555',
fontSize: '12px',
fontStyle: 'italic',
cursor: 'pointer',
@@ -2536,7 +2529,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
top: '50px',
left: '12px',
background: 'var(--rah-bg-active)',
border: '1px solid var(--rah-border-stronger)',
border: '1px solid var(--rah-border-strong)',
borderRadius: '6px',
zIndex: 1000,
maxHeight: '200px',
@@ -2566,11 +2559,11 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
fontSize: '13px',
color: 'var(--rah-text-base)',
cursor: 'pointer',
background: idx === mentionIndex ? 'var(--rah-bg-hover)' : 'transparent',
background: idx === mentionIndex ? 'var(--rah-bg-elevated)' : 'transparent',
borderBottom: idx < mentionResults.length - 1 ? '1px solid var(--rah-border-strong)' : 'none'
}}
>
<span style={{ color: '#22c55e', marginRight: '8px', fontWeight: 600 }}>{n.id}</span>
<span style={{ color: 'var(--rah-accent-green)', marginRight: '8px', fontWeight: 600 }}>{n.id}</span>
<span>{n.title.length > 50 ? n.title.slice(0, 50) + '…' : n.title}</span>
</div>
))
@@ -2621,7 +2614,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
gap: '4px',
padding: '6px 12px',
fontSize: '11px',
color: 'var(--rah-text-muted)',
color: 'var(--rah-text-soft)',
background: 'transparent',
border: '1px solid var(--rah-border-strong)',
borderRadius: '4px',
@@ -2640,8 +2633,8 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
gap: '4px',
padding: '6px 12px',
fontSize: '11px',
color: '#000',
background: '#22c55e',
color: 'var(--rah-text-inverse)',
background: 'var(--rah-accent-green)',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
@@ -2671,7 +2664,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
style={{
padding: '4px 10px',
fontSize: '11px',
color: 'var(--rah-text-muted)',
color: 'var(--rah-text-soft)',
background: 'transparent',
border: '1px solid var(--rah-border-strong)',
borderRadius: '4px',
@@ -2685,7 +2678,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
style={{
padding: '4px 10px',
fontSize: '11px',
color: '#000',
color: 'var(--rah-text-inverse)',
background: '#f59e0b',
border: 'none',
borderRadius: '4px',
@@ -2711,7 +2704,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
}}
>
<MarkdownWithNodeTokens
content={nodesData[activeTab].notes || ''}
content={nodesData[activeTab].notes}
onNodeClick={onNodeClick || onTabSelect}
/>
</div>
@@ -2719,7 +2712,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
<div
onClick={startNotesEdit}
style={{
color: 'var(--rah-text-muted)',
color: '#555',
fontSize: '12px',
fontStyle: 'italic',
cursor: 'pointer',
@@ -2814,7 +2807,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
gap: '4px',
padding: '6px 12px',
fontSize: '11px',
color: 'var(--rah-text-muted)',
color: 'var(--rah-text-soft)',
background: 'transparent',
border: '1px solid var(--rah-border-strong)',
borderRadius: '4px',
@@ -2833,8 +2826,8 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
gap: '4px',
padding: '6px 12px',
fontSize: '11px',
color: '#000',
background: '#22c55e',
color: 'var(--rah-text-inverse)',
background: 'var(--rah-accent-green)',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
@@ -2861,7 +2854,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
display: 'flex',
background: 'var(--rah-bg-surface)',
borderRadius: '4px',
border: '1px solid var(--rah-bg-active)',
border: '1px solid var(--rah-border)',
overflow: 'hidden',
}}>
<button
@@ -2871,7 +2864,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
fontSize: '10px',
fontWeight: 500,
color: sourceReaderMode === 'raw' ? 'var(--rah-text-active)' : 'var(--rah-text-muted)',
background: sourceReaderMode === 'raw' ? 'var(--rah-border-strong)' : 'transparent',
background: sourceReaderMode === 'raw' ? 'var(--rah-bg-active)' : 'transparent',
border: 'none',
cursor: 'pointer',
transition: 'all 0.15s ease',
@@ -2886,7 +2879,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
fontSize: '10px',
fontWeight: 500,
color: sourceReaderMode === 'reader' ? 'var(--rah-text-active)' : 'var(--rah-text-muted)',
background: sourceReaderMode === 'reader' ? 'var(--rah-border-strong)' : 'transparent',
background: sourceReaderMode === 'reader' ? 'var(--rah-bg-active)' : 'transparent',
border: 'none',
cursor: 'pointer',
transition: 'all 0.15s ease',
@@ -2907,17 +2900,17 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
fontSize: '10px',
color: 'var(--rah-text-muted)',
background: 'transparent',
border: '1px solid var(--rah-bg-active)',
border: '1px solid var(--rah-border)',
borderRadius: '4px',
cursor: 'pointer',
}}
onMouseEnter={(e) => {
e.currentTarget.style.color = 'var(--rah-text-secondary)';
e.currentTarget.style.borderColor = 'var(--rah-border-stronger)';
e.currentTarget.style.color = 'var(--rah-text-soft)';
e.currentTarget.style.borderColor = 'var(--rah-border-strong)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.color = 'var(--rah-text-muted)';
e.currentTarget.style.borderColor = 'var(--rah-bg-active)';
e.currentTarget.style.borderColor = 'var(--rah-border)';
}}
>
<Pencil size={10} />
@@ -2927,10 +2920,10 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
{/* Content display */}
<div style={{ flex: 1, overflow: 'auto' }}>
{nodesData[activeTab]?.chunk ? (
{(nodesData[activeTab]?.source || nodesData[activeTab]?.chunk) ? (
sourceReaderMode === 'reader' ? (
<SourceReader
content={nodesData[activeTab].chunk}
content={nodesData[activeTab].source || nodesData[activeTab].chunk || ''}
onTextSelect={onTextSelect ? (text) => onTextSelect(activeTab, nodesData[activeTab]?.title || 'Untitled', text) : undefined}
highlightedText={highlightedPassage?.nodeId === activeTab ? highlightedPassage.selectedText : null}
/>
@@ -2949,7 +2942,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
minHeight: '100%',
}}
>
{nodesData[activeTab].chunk}
{nodesData[activeTab].source || nodesData[activeTab].chunk}
</div>
)
) : (
@@ -3036,7 +3029,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
textAlign: 'left',
}}
onMouseEnter={(e) => {
e.currentTarget.style.background = 'var(--rah-border-strong)';
e.currentTarget.style.background = 'var(--rah-bg-elevated)';
e.currentTarget.style.color = 'var(--rah-text-active)';
}}
onMouseLeave={(e) => {
@@ -3068,7 +3061,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
textAlign: 'left',
}}
onMouseEnter={(e) => {
e.currentTarget.style.background = 'var(--rah-border-strong)';
e.currentTarget.style.background = 'var(--rah-bg-elevated)';
e.currentTarget.style.color = 'var(--rah-text-active)';
}}
onMouseLeave={(e) => {
+1 -1
View File
@@ -172,12 +172,12 @@ export default function ThreePanelLayout() {
id: node.id,
title: node.title,
link: node.link,
source: node.source,
notes: node.notes,
dimensions: node.dimensions,
created_at: node.created_at,
updated_at: node.updated_at,
chunk_status: node.chunk_status,
chunk: node.chunk,
metadata: node.metadata,
}));
setOpenTabsData(validNodes);
+52 -52
View File
@@ -175,13 +175,13 @@ export default function DatabaseTableView({ onNodeClick, refreshToken = 0, toolb
<div style={{
display: 'flex',
alignItems: 'center',
background: 'var(var(--rah-bg-surface))',
border: '1px solid var(--rah-border-strong)',
background: 'var(--rah-bg-base)',
border: '1px solid var(--rah-border)',
borderRadius: '6px',
padding: '0 8px',
gap: '6px',
}}>
<Search size={12} style={{ color: 'var(var(--rah-text-muted))', flexShrink: 0 }} />
<Search size={12} style={{ color: 'var(--rah-text-muted)', flexShrink: 0 }} />
<input
type="text"
value={searchQuery}
@@ -190,7 +190,7 @@ export default function DatabaseTableView({ onNodeClick, refreshToken = 0, toolb
style={{
background: 'transparent',
border: 'none',
color: 'var(var(--rah-text-active))',
color: 'var(--rah-text-active)',
fontSize: '12px',
padding: '5px 0',
outline: 'none',
@@ -201,7 +201,7 @@ export default function DatabaseTableView({ onNodeClick, refreshToken = 0, toolb
<button
type="button"
onClick={() => { setSearchQuery(''); setActiveSearch(''); }}
style={{ background: 'transparent', border: 'none', color: 'var(var(--rah-text-muted))', cursor: 'pointer', padding: 0, display: 'flex' }}
style={{ background: 'transparent', border: 'none', color: 'var(--rah-text-muted)', cursor: 'pointer', padding: 0, display: 'flex' }}
>
<X size={11} />
</button>
@@ -236,8 +236,8 @@ export default function DatabaseTableView({ onNodeClick, refreshToken = 0, toolb
style={{
display: 'flex', alignItems: 'center', gap: '4px',
padding: '4px 7px', background: 'transparent',
border: '1px solid var(--rah-border-strong)', borderRadius: '5px',
color: 'var(var(--rah-text-muted))', fontSize: '11px', cursor: 'pointer',
border: '1px solid var(--rah-border)', borderRadius: '5px',
color: 'var(--rah-text-soft)', fontSize: '11px', cursor: 'pointer',
}}
onMouseEnter={(e) => { e.currentTarget.style.background = 'rgba(255,255,255,0.04)'; }}
onMouseLeave={(e) => { e.currentTarget.style.background = 'transparent'; }}
@@ -249,7 +249,7 @@ export default function DatabaseTableView({ onNodeClick, refreshToken = 0, toolb
{showFilterPicker && (
<div style={{
position: 'absolute', top: '100%', left: 0, marginTop: '4px',
background: 'var(var(--rah-bg-panel))', border: '1px solid var(--rah-border-strong)', borderRadius: '10px',
background: 'var(--rah-bg-panel)', border: '1px solid var(--rah-border)', borderRadius: '10px',
padding: '6px', minWidth: '220px', maxHeight: '320px', overflowY: 'auto',
zIndex: 1000, boxShadow: '0 8px 24px rgba(0,0,0,0.4)',
}}>
@@ -260,13 +260,13 @@ export default function DatabaseTableView({ onNodeClick, refreshToken = 0, toolb
placeholder="Search dimensions..."
autoFocus
style={{
width: '100%', padding: '7px 10px', background: 'var(var(--rah-bg-surface))',
width: '100%', padding: '7px 10px', background: 'var(--rah-bg-base)',
border: '1px solid transparent', borderRadius: '6px',
color: 'var(var(--rah-text-active))', fontSize: '12px', marginBottom: '4px', outline: 'none',
color: 'var(--rah-text-active)', fontSize: '12px', marginBottom: '4px', outline: 'none',
}}
/>
{filterPickerDimensions.length === 0 ? (
<div style={{ padding: '12px', color: 'var(var(--rah-text-muted))', fontSize: '12px', textAlign: 'center' }}>
<div style={{ padding: '12px', color: 'var(--rah-text-muted)', fontSize: '12px', textAlign: 'center' }}>
No matching dimensions
</div>
) : (
@@ -283,14 +283,14 @@ export default function DatabaseTableView({ onNodeClick, refreshToken = 0, toolb
style={{
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
width: '100%', padding: '7px 10px', background: 'transparent',
border: 'none', borderRadius: '5px', color: 'var(var(--rah-text-secondary))',
border: 'none', borderRadius: '5px', color: 'var(--rah-text-secondary)',
fontSize: '12px', cursor: 'pointer', textAlign: 'left',
}}
onMouseEnter={(e) => { e.currentTarget.style.background = 'rgba(255,255,255,0.04)'; }}
onMouseLeave={(e) => { e.currentTarget.style.background = 'transparent'; }}
>
<span>{d.dimension}</span>
<span style={{ color: 'var(var(--rah-text-muted))', fontSize: '10px', background: 'var(var(--rah-bg-active))', padding: '1px 6px', borderRadius: '10px' }}>
<span style={{ color: 'var(--rah-text-muted)', fontSize: '10px', background: 'var(--rah-bg-active)', padding: '1px 6px', borderRadius: '10px' }}>
{d.count}
</span>
</button>
@@ -303,9 +303,9 @@ export default function DatabaseTableView({ onNodeClick, refreshToken = 0, toolb
{selectedFilters.length > 0 && (
<button
onClick={() => setSelectedFilters([])}
style={{ padding: '4px 6px', background: 'transparent', border: 'none', color: 'var(var(--rah-text-muted))', fontSize: '11px', cursor: 'pointer' }}
style={{ padding: '4px 6px', background: 'transparent', border: 'none', color: 'var(--rah-text-muted)', fontSize: '11px', cursor: 'pointer' }}
onMouseEnter={(e) => { e.currentTarget.style.color = '#ef4444'; }}
onMouseLeave={(e) => { e.currentTarget.style.color = '#666'; }}
onMouseLeave={(e) => { e.currentTarget.style.color = 'var(--rah-text-muted)'; }}
>
Clear
</button>
@@ -319,8 +319,8 @@ export default function DatabaseTableView({ onNodeClick, refreshToken = 0, toolb
style={{
display: 'flex', alignItems: 'center', gap: '4px',
padding: '4px 7px', background: 'transparent',
border: '1px solid var(--rah-border-strong)', borderRadius: '5px',
color: 'var(var(--rah-text-muted))', fontSize: '11px', cursor: 'pointer', whiteSpace: 'nowrap',
border: '1px solid var(--rah-border)', borderRadius: '5px',
color: 'var(--rah-text-soft)', fontSize: '11px', cursor: 'pointer', whiteSpace: 'nowrap',
}}
onMouseEnter={(e) => { e.currentTarget.style.background = 'rgba(255,255,255,0.04)'; }}
onMouseLeave={(e) => { e.currentTarget.style.background = 'transparent'; }}
@@ -333,7 +333,7 @@ export default function DatabaseTableView({ onNodeClick, refreshToken = 0, toolb
{showSortDropdown && (
<div style={{
position: 'absolute', top: '100%', right: 0, marginTop: '4px',
background: 'var(var(--rah-bg-panel))', border: '1px solid var(--rah-border-strong)', borderRadius: '10px',
background: 'var(--rah-bg-panel)', border: '1px solid var(--rah-border)', borderRadius: '10px',
padding: '4px', minWidth: '160px', zIndex: 1000,
boxShadow: '0 8px 24px rgba(0,0,0,0.4)',
}}>
@@ -346,13 +346,13 @@ export default function DatabaseTableView({ onNodeClick, refreshToken = 0, toolb
width: '100%', padding: '7px 10px',
background: sortOrder === key ? 'rgba(255,255,255,0.04)' : 'transparent',
border: 'none', borderRadius: '5px',
color: sortOrder === key ? '#f0f0f0' : '#999',
color: sortOrder === key ? 'var(--rah-text-active)' : 'var(--rah-text-soft)',
fontSize: '12px', cursor: 'pointer', textAlign: 'left',
}}
onMouseEnter={(e) => { e.currentTarget.style.background = 'rgba(255,255,255,0.04)'; }}
onMouseLeave={(e) => { e.currentTarget.style.background = sortOrder === key ? 'rgba(255,255,255,0.04)' : 'transparent'; }}
>
{sortOrder === key && <span style={{ color: '#22c55e', fontSize: '12px' }}></span>}
{sortOrder === key && <span style={{ color: 'var(--rah-accent-green)', fontSize: '12px' }}></span>}
{SORT_LABELS[key]}
</button>
))}
@@ -363,15 +363,15 @@ export default function DatabaseTableView({ onNodeClick, refreshToken = 0, toolb
{/* Pagination */}
<div style={{
display: 'flex', alignItems: 'center', gap: '6px',
fontSize: '11px', color: 'var(var(--rah-text-muted))', whiteSpace: 'nowrap',
fontSize: '11px', color: 'var(--rah-text-muted)', whiteSpace: 'nowrap',
}}>
<span>{total > 0 ? `${startItem}-${endItem} of ${total}` : '0 nodes'}</span>
<button
onClick={() => setPage(p => Math.max(1, p - 1))}
disabled={page <= 1}
style={{
background: 'transparent', border: '1px solid var(--rah-border-strong)', borderRadius: '4px',
color: page <= 1 ? '#333' : '#888', cursor: page <= 1 ? 'default' : 'pointer',
background: 'transparent', border: '1px solid var(--rah-border)', borderRadius: '4px',
color: page <= 1 ? 'var(--rah-text-muted)' : 'var(--rah-text-soft)', cursor: page <= 1 ? 'default' : 'pointer',
padding: '2px 4px', display: 'flex',
}}
>
@@ -381,8 +381,8 @@ export default function DatabaseTableView({ onNodeClick, refreshToken = 0, toolb
onClick={() => setPage(p => Math.min(totalPages, p + 1))}
disabled={page >= totalPages}
style={{
background: 'transparent', border: '1px solid var(--rah-border-strong)', borderRadius: '4px',
color: page >= totalPages ? '#333' : '#888',
background: 'transparent', border: '1px solid var(--rah-border)', borderRadius: '4px',
color: page >= totalPages ? 'var(--rah-text-muted)' : 'var(--rah-text-soft)',
cursor: page >= totalPages ? 'default' : 'pointer',
padding: '2px 4px', display: 'flex',
}}
@@ -410,9 +410,9 @@ export default function DatabaseTableView({ onNodeClick, refreshToken = 0, toolb
{/* Table */}
<div style={{ flex: 1, overflow: 'auto' }}>
{loading ? (
<div style={{ padding: '40px', color: 'var(var(--rah-text-muted))', textAlign: 'center', fontSize: '13px' }}>Loading...</div>
<div style={{ padding: '40px', color: 'var(--rah-text-muted)', textAlign: 'center', fontSize: '13px' }}>Loading...</div>
) : nodes.length === 0 ? (
<div style={{ padding: '40px', color: 'var(var(--rah-text-muted))', textAlign: 'center', fontSize: '13px' }}>
<div style={{ padding: '40px', color: 'var(--rah-text-muted)', textAlign: 'center', fontSize: '13px' }}>
{activeSearch || selectedFilters.length > 0 ? 'No nodes match your filters.' : 'No nodes yet.'}
</div>
) : (
@@ -450,15 +450,15 @@ export default function DatabaseTableView({ onNodeClick, refreshToken = 0, toolb
height: '44px',
cursor: 'pointer',
background: hoveredRow === node.id
? '#141414'
: i % 2 === 0 ? '#080808' : '#0d0d0d',
? 'var(--rah-bg-panel)'
: i % 2 === 0 ? 'var(--rah-bg-base)' : 'var(--rah-bg-subtle)',
transition: 'background 0.1s ease',
}}
>
{/* Title */}
<td style={tdStyle()}>
<div style={truncCell}>
<span style={{ fontSize: '13px', color: '#e0e0e0', fontWeight: 400 }}>
<span style={{ fontSize: '13px', color: 'var(--rah-text-base)', fontWeight: 400 }}>
{node.title || 'Untitled'}
</span>
</div>
@@ -468,7 +468,7 @@ export default function DatabaseTableView({ onNodeClick, refreshToken = 0, toolb
<td style={tdStyle({ textAlign: 'right' })}>
<span style={{
fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace',
fontSize: '11px', color: 'var(var(--rah-text-muted))',
fontSize: '11px', color: 'var(--rah-text-muted)',
}}>
{node.id}
</span>
@@ -477,7 +477,7 @@ export default function DatabaseTableView({ onNodeClick, refreshToken = 0, toolb
{/* Description */}
<td style={tdStyle()}>
<div style={truncCell}>
<span style={{ fontSize: '11px', color: 'var(var(--rah-text-muted))' }}>
<span style={{ fontSize: '11px', color: 'var(--rah-text-soft)' }}>
{node.description || '\u2014'}
</span>
</div>
@@ -486,8 +486,8 @@ export default function DatabaseTableView({ onNodeClick, refreshToken = 0, toolb
{/* Notes */}
<td style={tdStyle()}>
<div style={truncCell}>
<span style={{ fontSize: '11px', color: 'var(var(--rah-text-muted))' }}>
{node.notes ? node.notes.slice(0, 120) : '\u2014'}
<span style={{ fontSize: '11px', color: 'var(--rah-text-soft)' }}>
{(node.source || node.notes) ? (node.source || node.notes || '').slice(0, 120) : '\u2014'}
</span>
</div>
</td>
@@ -503,7 +503,7 @@ export default function DatabaseTableView({ onNodeClick, refreshToken = 0, toolb
</span>
</span>
) : (
<span style={{ fontSize: '11px', color: '#333' }}>{'\u2014'}</span>
<span style={{ fontSize: '11px', color: 'var(--rah-text-muted)' }}>{'\u2014'}</span>
)}
</div>
</td>
@@ -516,49 +516,49 @@ export default function DatabaseTableView({ onNodeClick, refreshToken = 0, toolb
{node.dimensions.slice(0, 3).map(d => (
<span key={d} style={{
fontSize: '9px', padding: '1px 5px',
background: '#111914', border: '1px solid #1f2f24',
color: '#bbf7d0', borderRadius: '3px',
background: 'var(--rah-accent-green-soft)', border: '1px solid var(--rah-accent-green-soft-strong)',
color: 'var(--rah-accent-green)', borderRadius: '3px',
whiteSpace: 'nowrap',
}}>
{d}
</span>
))}
{node.dimensions.length > 3 && (
<span style={{ fontSize: '9px', color: 'var(var(--rah-text-muted))' }}>
<span style={{ fontSize: '9px', color: 'var(--rah-text-muted)' }}>
+{node.dimensions.length - 3}
</span>
)}
</>
) : (
<span style={{ fontSize: '10px', color: '#333' }}>{'\u2014'}</span>
<span style={{ fontSize: '10px', color: 'var(--rah-text-muted)' }}>{'\u2014'}</span>
)}
</div>
</td>
{/* Edges */}
<td style={tdStyle({ textAlign: 'right' })}>
<span style={{ fontSize: '12px', color: node.edge_count ? '#888' : '#333' }}>
<span style={{ fontSize: '12px', color: node.edge_count ? 'var(--rah-text-soft)' : 'var(--rah-text-muted)' }}>
{node.edge_count ?? 0}
</span>
</td>
{/* Event Date */}
<td style={tdStyle()}>
<span style={{ fontSize: '11px', color: node.event_date ? '#888' : '#333' }}>
<span style={{ fontSize: '11px', color: node.event_date ? 'var(--rah-text-soft)' : 'var(--rah-text-muted)' }}>
{formatDate(node.event_date)}
</span>
</td>
{/* Updated */}
<td style={tdStyle()}>
<span style={{ fontSize: '11px', color: 'var(var(--rah-text-muted))' }}>
<span style={{ fontSize: '11px', color: 'var(--rah-text-muted)' }}>
{formatRelativeTime(node.updated_at)}
</span>
</td>
{/* Created */}
<td style={tdStyle()}>
<span style={{ fontSize: '11px', color: 'var(var(--rah-text-muted))' }}>
<span style={{ fontSize: '11px', color: 'var(--rah-text-muted)' }}>
{formatRelativeTime(node.created_at)}
</span>
</td>
@@ -566,7 +566,7 @@ export default function DatabaseTableView({ onNodeClick, refreshToken = 0, toolb
{/* Metadata */}
<td style={tdStyle()}>
<div style={truncCell}>
<span style={{ fontSize: '10px', color: 'var(var(--rah-text-muted))', fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace' }}>
<span style={{ fontSize: '10px', color: 'var(--rah-text-muted)', fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace' }}>
{metaStr || '\u2014'}
</span>
</div>
@@ -575,8 +575,8 @@ export default function DatabaseTableView({ onNodeClick, refreshToken = 0, toolb
{/* Chunk */}
<td style={tdStyle()}>
<div style={truncCell}>
<span style={{ fontSize: '10px', color: 'var(var(--rah-text-muted))' }}>
{node.chunk ? node.chunk.slice(0, 100) : '\u2014'}
<span style={{ fontSize: '10px', color: 'var(--rah-text-muted)' }}>
{(node.source || node.chunk) ? (node.source || node.chunk || '').slice(0, 100) : '\u2014'}
</span>
</div>
</td>
@@ -585,7 +585,7 @@ export default function DatabaseTableView({ onNodeClick, refreshToken = 0, toolb
<td style={tdStyle()}>
<span style={{
fontSize: '10px',
color: node.chunk_status === 'chunked' ? '#4a9' : node.chunk_status === 'error' ? '#e55' : '#555',
color: node.chunk_status === 'chunked' ? 'var(--rah-accent-green)' : node.chunk_status === 'error' ? '#e55' : 'var(--rah-text-muted)',
}}>
{node.chunk_status || '\u2014'}
</span>
@@ -593,7 +593,7 @@ export default function DatabaseTableView({ onNodeClick, refreshToken = 0, toolb
{/* Embedding Updated */}
<td style={tdStyle()}>
<span style={{ fontSize: '11px', color: 'var(var(--rah-text-muted))' }}>
<span style={{ fontSize: '11px', color: 'var(--rah-text-muted)' }}>
{node.embedding_updated_at ? formatRelativeTime(node.embedding_updated_at) : '\u2014'}
</span>
</td>
@@ -612,15 +612,15 @@ function thStyle(extra: React.CSSProperties = {}): React.CSSProperties {
return {
position: 'sticky' as const,
top: 0,
background: 'var(var(--rah-bg-base))',
background: 'var(--rah-bg-base)',
padding: '8px 12px',
fontSize: '10px',
fontWeight: 500,
color: 'var(var(--rah-text-muted))',
color: 'var(--rah-text-muted)',
textAlign: 'left',
letterSpacing: '0.05em',
whiteSpace: 'nowrap',
borderBottom: '1px solid rgba(255,255,255,0.06)',
borderBottom: '1px solid var(--rah-border)',
zIndex: 1,
...extra,
};
@@ -630,7 +630,7 @@ function tdStyle(extra: React.CSSProperties = {}): React.CSSProperties {
return {
padding: '0 12px',
verticalAlign: 'middle',
borderBottom: '1px solid #111',
borderBottom: '1px solid var(--rah-border)',
overflow: 'hidden',
...extra,
};
+2 -2
View File
@@ -104,7 +104,7 @@ export default function GridView({ nodes, onNodeClick }: GridViewProps) {
</div>
{/* Description or Content Preview */}
{(node.description || node.notes) && (
{(node.description || node.source || node.notes) && (
<div style={{
flex: 1,
fontSize: '11px',
@@ -116,7 +116,7 @@ export default function GridView({ nodes, onNodeClick }: GridViewProps) {
WebkitBoxOrient: 'vertical',
marginBottom: '10px'
}}>
{node.description || truncateContent(node.notes)}
{node.description || truncateContent(node.source || node.notes)}
</div>
)}
+2 -2
View File
@@ -105,7 +105,7 @@ export default function ListView({ nodes, onNodeClick }: ListViewProps) {
</div>
{/* Description or Content Preview */}
{(node.description || node.notes) && (
{(node.description || node.source || node.notes) && (
<div style={{
fontSize: '12px',
color: '#666',
@@ -116,7 +116,7 @@ export default function ListView({ nodes, onNodeClick }: ListViewProps) {
WebkitBoxOrient: 'vertical',
overflow: 'hidden'
}}>
{node.description || truncateContent(node.notes)}
{node.description || truncateContent(node.source || node.notes)}
</div>
)}