lorehub
Version:
Capture and surface the collective wisdom of your codebase
170 lines • 9.64 kB
JavaScript
import React, { useState, useEffect } from 'react';
import { Box, Text, useInput } from 'ink';
import SelectInput from 'ink-select-input';
import { TruncatedText } from './TruncatedText.js';
import { useTerminalDimensions } from '../hooks/useTerminalDimensions.js';
export function SimilarFactsView({ db, fact, onBack }) {
const { columns, rows } = useTerminalDimensions();
const [currentFact, setCurrentFact] = useState(fact);
const [similarFacts, setSimilarFacts] = useState([]);
const [loading, setLoading] = useState(true);
const [selectedIndex, setSelectedIndex] = useState(0);
const [navigationHistory, setNavigationHistory] = useState([fact]);
const [similarFactsCounts, setSimilarFactsCounts] = useState(new Map());
useInput((input, key) => {
if (input === 'q' || key.escape) {
onBack();
}
else if (input === 's' && similarFacts.length > 0 && !loading) {
// Navigate to the selected similar fact
const selectedSimilarFact = similarFacts[selectedIndex];
if (selectedSimilarFact) {
setNavigationHistory([...navigationHistory, currentFact]);
setCurrentFact(selectedSimilarFact);
setSelectedIndex(0);
setLoading(true);
}
}
else if (input === 'b' && navigationHistory.length > 1) {
// Go back in navigation history
const newHistory = [...navigationHistory];
newHistory.pop(); // Remove current
const previousFact = newHistory[newHistory.length - 1];
if (previousFact) {
setNavigationHistory(newHistory);
setCurrentFact(previousFact);
setSelectedIndex(0);
setLoading(true);
}
}
});
useEffect(() => {
const loadSimilarFacts = async () => {
try {
const similar = await db.findSimilarFacts(currentFact.id, {
limit: 20,
threshold: 0.3
});
// Load project info for each similar fact
const factsWithProjects = similar.map(f => {
const project = db.findProject(f.projectId || f.realmId);
const currentProject = db.findProjectByPath(process.cwd());
return {
...f,
projectName: project?.name || 'Unknown',
projectPath: project?.path || '',
isCurrentProject: currentProject?.id === (f.projectId || f.realmId)
};
});
setSimilarFacts(factsWithProjects);
// Load similar counts for each fact
const counts = new Map();
await Promise.all(factsWithProjects.map(async (f) => {
try {
const similar = await db.findSimilarFacts(f.id, {
limit: 10,
threshold: 0.5
});
counts.set(f.id, similar.length);
}
catch (error) {
counts.set(f.id, 0);
}
}));
setSimilarFactsCounts(counts);
}
catch (error) {
console.error('Failed to load similar facts:', error);
setSimilarFacts([]);
}
finally {
setLoading(false);
}
};
setLoading(true);
loadSimilarFacts();
}, [db, currentFact]);
if (loading) {
return React.createElement(Text, null, "Loading similar facts...");
}
if (similarFacts.length === 0) {
return (React.createElement(Box, { flexDirection: "column" },
React.createElement(Text, { color: "yellow" }, "No similar facts found"),
React.createElement(Box, { marginTop: 1 },
React.createElement(Text, { dimColor: true }, "Press q or ESC to go back"))));
}
// Calculate dimensions early before using them
const headerHeight = 4;
const footerHeight = 2;
const contentHeight = rows - headerHeight - footerHeight - 1;
// Use same split as main view
const factsWidth = columns > 120 ? Math.floor(columns * 0.6) : Math.floor(columns * 0.5);
const detailsWidth = columns - factsWidth - 3;
const items = similarFacts.map((f, index) => {
const typeStr = `[${f.type.substring(0, 3).toUpperCase()}]`;
const similarityStr = `${(f.similarity * 100).toFixed(0)}%`;
const similarCount = similarFactsCounts.get(f.id) || 0;
// Use fixed-width formatting similar to main view
// Fixed width similarity percentage (4 chars)
// Fixed width similar count (3 chars + ≈)
const similarityFixed = similarityStr.padStart(4);
const similarCountStr = similarCount > 0 ? `${similarCount.toString().padStart(3)}≈` : ' ';
// Calculate available width
// Account for: similarity% (4), space (1), similar count (4), space (1), type (5), space (1)
const prefixLength = 4 + 1 + 4 + 1 + 5 + 1;
const availableWidth = factsWidth - prefixLength - 4;
const contentMaxLength = Math.max(20, availableWidth);
const content = f.content.length > contentMaxLength
? f.content.substring(0, contentMaxLength - 3) + '...'
: f.content;
return {
label: `${similarityFixed} ${similarCountStr} ${typeStr} ${content}`,
value: index,
};
});
const selectedFact = similarFacts[selectedIndex];
return (React.createElement(Box, { flexDirection: "column", height: rows - 1 },
React.createElement(Box, { height: 4, flexDirection: "column" },
React.createElement(Box, { flexDirection: "row" },
React.createElement(Text, { bold: true }, "Similar facts to"),
navigationHistory.length > 1 && (React.createElement(Text, { dimColor: true },
" (depth: ",
navigationHistory.length - 1,
")")),
React.createElement(Text, { bold: true }, ":")),
React.createElement(Text, { color: "cyan" },
"[",
currentFact.type,
"] ",
currentFact.content.substring(0, 60),
currentFact.content.length > 60 ? '...' : ''),
React.createElement(Box, { marginTop: 1 },
React.createElement(Text, { dimColor: true },
"Found ",
similarFacts.length,
" similar fact",
similarFacts.length !== 1 ? 's' : ''))),
React.createElement(Box, { flexDirection: "row", height: contentHeight, overflow: "hidden" },
React.createElement(Box, { flexDirection: "column", width: factsWidth, marginRight: 2 },
React.createElement(Text, { bold: true, dimColor: true }, "Similar Facts"),
React.createElement(Box, { marginTop: 1 },
React.createElement(SelectInput, { items: items, onHighlight: (item) => setSelectedIndex(item.value), initialIndex: 0, limit: contentHeight - 2 }))),
React.createElement(Box, { flexDirection: "column", width: detailsWidth, overflow: "hidden" },
React.createElement(Text, { bold: true, dimColor: true }, "Details"),
selectedFact && (React.createElement(Box, { flexDirection: "column", marginTop: 1, height: contentHeight - 2, overflow: "hidden" },
React.createElement(Box, { flexDirection: "column", flexGrow: 1, overflow: "hidden" },
React.createElement(TruncatedText, { text: selectedFact.content, maxLines: selectedFact.why ? Math.floor((contentHeight - 10) * 0.6) : contentHeight - 10, width: detailsWidth }),
selectedFact.why && (React.createElement(Box, { marginTop: 1, flexDirection: "column" },
React.createElement(TruncatedText, { text: `Why: ${selectedFact.why}`, maxLines: Math.floor((contentHeight - 10) * 0.4), width: detailsWidth, dimColor: true })))),
React.createElement(Box, { flexDirection: "column", flexShrink: 0, marginTop: 1 },
React.createElement(TruncatedText, { text: `Similarity: ${(selectedFact.similarity * 100).toFixed(1)}%`, maxLines: 1, width: detailsWidth, dimColor: true }),
React.createElement(TruncatedText, { text: `Project: ${selectedFact.projectName}`, maxLines: 1, width: detailsWidth, dimColor: true }),
React.createElement(TruncatedText, { text: `Type: ${selectedFact.type} | Confidence: ${selectedFact.confidence}%`, maxLines: 1, width: detailsWidth, dimColor: true }),
React.createElement(TruncatedText, { text: `Created: ${selectedFact.createdAt.toLocaleDateString()}`, maxLines: 1, width: detailsWidth, dimColor: true }),
selectedFact.sigils.length > 0 && (React.createElement(TruncatedText, { text: `Sigils: ${selectedFact.sigils.join(', ')}`, maxLines: 1, width: detailsWidth, dimColor: true }))))))),
React.createElement(Box, { height: 2, marginTop: 1 },
React.createElement(Text, { dimColor: true },
"q/ESC: exit | \u2191\u2193: navigate | s: dive deeper",
navigationHistory.length > 1 && ' | b: back'))));
}
//# sourceMappingURL=SimilarFactsView.js.map