lorehub
Version:
Capture and surface the collective wisdom of your codebase
214 lines • 10.8 kB
JavaScript
import React, { useState, useEffect } from 'react';
import { Box, Text, useInput } from 'ink';
import { Table } from './Table.js';
import { TruncatedText } from './TruncatedText.js';
import { useTerminalDimensions } from '../hooks/useTerminalDimensions.js';
export function SimilarLoresView({ db, lore: lore, onBack }) {
const { columns, rows } = useTerminalDimensions();
const [currentLore, setCurrentLore] = useState(lore);
const [similarLores, setSimilarLores] = useState([]);
const [loading, setLoading] = useState(true);
const [selectedIndex, setSelectedIndex] = useState(0);
const [navigationHistory, setNavigationHistory] = useState([lore]);
const [similarLoresCounts, setSimilarLoresCounts] = useState(new Map());
useInput((input, key) => {
if (input === 'q' || key.escape) {
onBack();
}
else if (key.upArrow || input === 'k') {
if (selectedIndex > 0) {
setSelectedIndex(selectedIndex - 1);
}
}
else if (key.downArrow || input === 'j') {
if (selectedIndex < similarLores.length - 1) {
setSelectedIndex(selectedIndex + 1);
}
}
else if (input === 's' && similarLores.length > 0 && !loading) {
// Navigate to the selected similar lore
const selectedSimilarLore = similarLores[selectedIndex];
if (selectedSimilarLore) {
setNavigationHistory([...navigationHistory, currentLore]);
setCurrentLore(selectedSimilarLore);
setSelectedIndex(0);
setLoading(true);
}
}
else if (input === 'b' && navigationHistory.length > 1) {
// Go back in navigation history
const newHistory = [...navigationHistory];
newHistory.pop(); // Remove current
const previousLore = newHistory[newHistory.length - 1];
if (previousLore) {
setNavigationHistory(newHistory);
setCurrentLore(previousLore);
setSelectedIndex(0);
setLoading(true);
}
}
});
useEffect(() => {
const loadSimilarLores = async () => {
try {
const similar = await db.findSimilarLores(currentLore.id, {
limit: 20,
threshold: 0.3
});
// Load realm info for each similar lore
const loresWithRealms = similar.map(f => {
const realm = db.findRealm(f.realmId || f.realmId);
const currentRealm = db.findRealmByPath(process.cwd());
return {
...f,
realmName: realm?.name || 'Unknown',
realmPath: realm?.path || '',
isCurrentRealm: currentRealm?.id === (f.realmId || f.realmId)
};
});
setSimilarLores(loresWithRealms);
// Load similar counts for each lore
const counts = new Map();
await Promise.all(loresWithRealms.map(async (f) => {
try {
const similar = await db.findSimilarLores(f.id, {
limit: 10,
threshold: 0.5
});
counts.set(f.id, similar.length);
}
catch (error) {
counts.set(f.id, 0);
}
}));
setSimilarLoresCounts(counts);
}
catch (error) {
console.error('Failed to load similar lores:', error);
setSimilarLores([]);
}
finally {
setLoading(false);
}
};
setLoading(true);
loadSimilarLores();
}, [db, currentLore]);
if (loading) {
return React.createElement(Text, null, "Loading similar lores...");
}
if (similarLores.length === 0) {
return (React.createElement(Box, { flexDirection: "column" },
React.createElement(Text, { color: "yellow" }, "No similar lores 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 loresWidth = columns > 120 ? Math.floor(columns * 0.6) : Math.floor(columns * 0.5);
const detailsWidth = columns - loresWidth - 3;
// Helper function to get status letter
const getStatusLetter = (status) => {
switch (status) {
case 'living': return 'L';
case 'archived': return 'A';
case 'whispered': return 'W';
case 'proclaimed': return 'P';
default: return '?';
}
};
// Prepare table data
const tableData = similarLores.map((lore, index) => {
const similarCount = similarLoresCounts.get(lore.id) || 0;
const isSelected = index === selectedIndex;
const similarityPercent = Math.round(lore.similarity * 100);
// Calculate max content length
const contentMaxLength = Math.max(30, loresWidth - 40);
const content = lore.content.length > contentMaxLength
? lore.content.substring(0, contentMaxLength - 3) + '...'
: lore.content;
return {
'': isSelected ? '→' : ' ',
'Match': `${similarityPercent}%`,
'Sim': similarCount > 0 ? `${similarCount}≈` : '-',
'Type': lore.type.substring(0, 3).toUpperCase(),
'S': getStatusLetter(lore.status || 'living'),
'Content': content,
};
});
const selectedLore = similarLores[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 lores to"),
navigationHistory.length > 1 && (React.createElement(Text, { dimColor: true },
" (depth: ",
navigationHistory.length - 1,
")")),
React.createElement(Text, { bold: true }, ":")),
React.createElement(Text, { color: "cyan" },
"[",
currentLore.type,
"] ",
currentLore.content.substring(0, 60),
currentLore.content.length > 60 ? '...' : ''),
React.createElement(Box, { marginTop: 1 },
React.createElement(Text, { dimColor: true },
"Found ",
similarLores.length,
" similar lore",
similarLores.length !== 1 ? 's' : ''))),
React.createElement(Box, { flexDirection: "row", height: contentHeight, overflow: "hidden" },
React.createElement(Box, { flexDirection: "column", width: loresWidth, marginRight: 2 },
React.createElement(Box, { marginBottom: 1 },
React.createElement(Text, { bold: true, dimColor: true }, "Similar Lores")),
React.createElement(Box, { flexDirection: "column" }, similarLores.length > 0 ? (React.createElement(Table, { data: tableData })) : (React.createElement(Text, { dimColor: true }, "No similar lores found")))),
React.createElement(Box, { flexDirection: "column", width: detailsWidth, overflow: "hidden" },
React.createElement(Text, { bold: true, dimColor: true }, "Details"),
selectedLore && (React.createElement(Box, { flexDirection: "column", marginTop: 1 },
React.createElement(Box, { flexDirection: "column", marginBottom: 1 },
React.createElement(Text, { dimColor: true },
"Similarity: ",
(selectedLore.similarity * 100).toFixed(1),
"%"),
React.createElement(Text, { dimColor: true },
"Realm: ",
selectedLore.realmName,
selectedLore.isCurrentRealm ? ' •' : ''),
React.createElement(Text, { dimColor: true },
"Type: ",
selectedLore.type,
" | Status: ",
selectedLore.status || 'living',
" | Confidence: ",
selectedLore.confidence,
"%"),
React.createElement(Text, { dimColor: true },
"Created: ",
selectedLore.createdAt.toLocaleDateString(),
" ",
selectedLore.createdAt.toLocaleTimeString()),
React.createElement(Text, { dimColor: true },
"Sigils: ",
selectedLore.sigils.length > 0 ? selectedLore.sigils.join(', ') : 'none'),
React.createElement(Text, { dimColor: true },
"Provinces: ",
selectedLore.provinces.length > 0 ? selectedLore.provinces.join(', ') : 'none')),
React.createElement(Text, { dimColor: true }, '─'.repeat(Math.min(detailsWidth - 2, 50))),
React.createElement(Box, { flexDirection: "column", marginTop: 1 },
React.createElement(Box, { marginBottom: 1 },
React.createElement(Text, { wrap: "wrap" }, selectedLore.content)),
selectedLore.why && (React.createElement(Box, { marginTop: 1, flexDirection: "column" },
React.createElement(Box, { marginBottom: 0 },
React.createElement(Text, { bold: true, dimColor: true }, "Why:")),
React.createElement(Box, null,
React.createElement(Text, { wrap: "wrap", dimColor: true }, selectedLore.why))))))))),
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=SimilarLoresView.js.map