Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,29 +27,36 @@ export const ContentViewer: React.FC<ContentViewerProps> = ({ selectedItem, onCo

return (
<div className="flex flex-col h-full">
{/* Content Header */}
<div className="p-4 border-b border-white/10 flex items-center justify-between flex-shrink-0">
<div className="flex items-center gap-3">
{selectedItem.type === "document" ? (
<>
{/* Content Header - Fixed with proper overflow handling */}
<div className="p-4 border-b border-white/10 flex items-center gap-3 flex-shrink-0">
{/* Icon and Metadata - Allow to grow and shrink with min-w-0 for proper truncation */}
<div className="flex items-center gap-3 min-w-0 flex-1">
{/* Icon - Fixed size, no shrink */}
<div className="flex-shrink-0">
{selectedItem.type === "document" ? (
<FileText className="w-5 h-5 text-cyan-400" />
<div>
<h4 className="text-sm font-medium text-white/90">
) : (
<Code className="w-5 h-5 text-green-400" />
)}
</div>

{/* Metadata Content - Can shrink with proper overflow */}
<div className="min-w-0 flex-1">
{selectedItem.type === "document" ? (
<>
<h4 className="text-sm font-medium text-white/90 truncate">
{selectedItem.metadata && "title" in selectedItem.metadata
? selectedItem.metadata.title || "Document"
: "Document"}
</h4>
{selectedItem.metadata && "section" in selectedItem.metadata && selectedItem.metadata.section && (
<p className="text-xs text-gray-500">{selectedItem.metadata.section}</p>
<p className="text-xs text-gray-500 truncate">{selectedItem.metadata.section}</p>
)}
</div>
</>
) : (
<>
<Code className="w-5 h-5 text-green-400" />
<div>
<div className="flex items-center gap-2">
<span className="px-2 py-0.5 bg-green-500/10 text-green-400 text-xs font-mono rounded">
</>
) : (
<>
<div className="flex items-center gap-2 min-w-0">
<span className="px-2 py-0.5 bg-green-500/10 text-green-400 text-xs font-mono rounded flex-shrink-0">
{selectedItem.type === "code" && selectedItem.metadata && "language" in selectedItem.metadata
? selectedItem.metadata.language || "unknown"
: "unknown"}
Expand All @@ -58,24 +65,28 @@ export const ContentViewer: React.FC<ContentViewerProps> = ({ selectedItem, onCo
selectedItem.metadata &&
"file_path" in selectedItem.metadata &&
selectedItem.metadata.file_path && (
<span className="text-xs text-gray-500 font-mono">{selectedItem.metadata.file_path}</span>
<span className="text-xs text-gray-500 font-mono truncate min-w-0">
{selectedItem.metadata.file_path}
</span>
)}
</div>
{selectedItem.type === "code" &&
selectedItem.metadata &&
"summary" in selectedItem.metadata &&
selectedItem.metadata.summary && (
<p className="text-xs text-gray-400 mt-1">{selectedItem.metadata.summary}</p>
<p className="text-xs text-gray-400 mt-1 line-clamp-2">{selectedItem.metadata.summary}</p>
)}
</div>
</>
)}
</>
)}
</div>
</div>

{/* Copy Button - Never shrinks, always visible */}
<Button
size="sm"
variant="ghost"
onClick={() => onCopy(selectedItem.content, selectedItem.id)}
className="text-gray-400 hover:text-white"
className="text-gray-400 hover:text-white flex-shrink-0"
>
{copiedId === selectedItem.id ? (
<>
Expand All @@ -95,21 +106,45 @@ export const ContentViewer: React.FC<ContentViewerProps> = ({ selectedItem, onCo
<div className="flex-1 overflow-y-auto min-h-0 p-6 scrollbar-thin">
{selectedItem.type === "document" ? (
<div className="prose prose-invert max-w-none">
<pre className="whitespace-pre-wrap text-sm text-gray-300 font-sans">{selectedItem.content}</pre>
<pre className="whitespace-pre-wrap text-sm text-gray-300 font-sans leading-relaxed">
{selectedItem.content || "No content available"}
</pre>
</div>
) : (
<pre className="bg-black/30 border border-white/10 rounded-lg p-4 overflow-x-auto">
<code className="text-sm text-gray-300 font-mono">{selectedItem.content}</code>
</pre>
<div className="relative">
<pre className="bg-black/30 border border-white/10 rounded-lg p-4 overflow-x-auto scrollbar-thin scrollbar-thumb-white/10 scrollbar-track-transparent">
<code className="text-sm text-gray-300 font-mono">
{selectedItem.content || "// No code content available"}
</code>
</pre>
</div>
)}
</div>

{/* Content Footer */}
{selectedItem.metadata?.relevance_score != null && (
<div className="p-3 border-t border-white/10 text-xs text-gray-500 flex-shrink-0">
Relevance Score: {(selectedItem.metadata.relevance_score * 100).toFixed(0)}%
{/* Content Footer - Show metadata */}
<div className="border-t border-white/10 flex-shrink-0">
<div className="px-4 py-3 flex items-center justify-between text-xs text-gray-500">
<div className="flex items-center gap-4">
{selectedItem.metadata?.relevance_score != null && (
<span>
Relevance:{" "}
<span className="text-cyan-400">{(selectedItem.metadata.relevance_score * 100).toFixed(0)}%</span>
</span>
)}
{selectedItem.type === "document" && "url" in selectedItem.metadata && selectedItem.metadata.url && (
<a
href={selectedItem.metadata.url}
target="_blank"
rel="noopener noreferrer"
className="text-cyan-400 hover:text-cyan-300 transition-colors underline"
>
View Source
</a>
)}
</div>
<span className="text-gray-600">{selectedItem.type === "document" ? "Document Chunk" : "Code Example"}</span>
</div>
)}
</div>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -61,61 +61,84 @@ export const InspectorSidebar: React.FC<InspectorSidebarProps> = ({
};

return (
<div className="w-80 border-r border-white/10 flex flex-col bg-black/40">
<aside className="w-80 border-r border-white/10 flex flex-col bg-black/40" aria-label="Document and code browser">
{/* Search */}
<div className="p-4 border-b border-white/10">
<div className="p-4 border-b border-white/10 flex-shrink-0">
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-500" />
<Search
className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-500 pointer-events-none"
aria-hidden="true"
/>
<Input
placeholder={`Search ${viewMode}...`}
value={searchQuery}
onChange={(e) => onSearchChange(e.target.value)}
className="pl-9 bg-black/30"
aria-label={`Search ${viewMode}`}
/>
</div>
</div>

{/* Item List */}
<div className="flex-1 overflow-y-auto min-h-0 scrollbar-thin">
{isLoading ? (
<div className="p-4 text-center text-gray-500">Loading...</div>
<div className="p-4 text-center text-gray-500" aria-live="polite">
<Loader2 className="w-5 h-5 animate-spin mx-auto mb-2" aria-hidden="true" />
<span>Loading {viewMode}...</span>
</div>
) : items.length === 0 ? (
<div className="p-4 text-center text-gray-500">No {viewMode} found</div>
<div className="p-4 text-center text-gray-500">
No {viewMode} found
{searchQuery && <p className="text-xs mt-1">Try adjusting your search</p>}
</div>
) : (
<div className="p-2">
{items.map((item) => (
<motion.button
type="button"
key={item.id}
whileHover={{ x: 2 }}
whileTap={{ scale: 0.98 }}
onClick={() => onItemSelect(item)}
className={cn(
"w-full text-left p-3 rounded-lg mb-1 transition-colors",
"hover:bg-white/5",
selectedItemId === item.id ? "bg-cyan-500/10 border border-cyan-500/30" : "border border-transparent",
"w-full text-left p-3 rounded-lg mb-1 transition-all",
"hover:bg-white/5 focus:outline-none focus:ring-2 focus:ring-cyan-500/50",
selectedItemId === item.id
? "bg-cyan-500/10 border border-cyan-500/30 ring-1 ring-cyan-500/20"
: "border border-transparent",
)}
role="option"
aria-selected={selectedItemId === item.id}
aria-label={`${getItemTitle(item)}. ${getItemDescription(item)}`}
>
<div className="flex items-start gap-3">
<div className="mt-0.5">
{/* Icon - Fixed size */}
<div className="mt-0.5 flex-shrink-0" aria-hidden="true">
{viewMode === "documents" ? (
<FileText className="w-4 h-4 text-cyan-400" />
) : (
<Code className="w-4 h-4 text-green-400" />
)}
</div>

{/* Content - Can shrink with proper overflow */}
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
<span className="text-sm font-medium text-white/90 truncate">{getItemTitle(item)}</span>
<div className="flex items-center gap-2 mb-1 min-w-0">
<span className="text-sm font-medium text-white/90 truncate flex-1" title={getItemTitle(item)}>
{getItemTitle(item)}
</span>
{viewMode === "code" && (item as CodeExample).language && (
<span className="px-1.5 py-0.5 bg-green-500/10 text-green-400 text-xs rounded">
<span className="px-1.5 py-0.5 bg-green-500/10 text-green-400 text-xs rounded flex-shrink-0">
{(item as CodeExample).language}
</span>
)}
</div>
<p className="text-xs text-gray-500 line-clamp-2">{getItemDescription(item)}</p>
<p className="text-xs text-gray-500 line-clamp-2" title={getItemDescription(item)}>
{getItemDescription(item)}
</p>
{item.metadata?.relevance_score != null && (
<div className="flex items-center gap-1 mt-1">
<Hash className="w-3 h-3 text-gray-600" />
<Hash className="w-3 h-3 text-gray-600" aria-hidden="true" />
<span className="text-xs text-gray-600">
{(item.metadata.relevance_score * 100).toFixed(0)}%
</span>
Expand All @@ -128,28 +151,32 @@ export const InspectorSidebar: React.FC<InspectorSidebarProps> = ({

{/* Load More Button */}
{hasNextPage && !isLoading && (
<div className="p-3 border-t border-white/10">
<div className="p-3 mt-2">
<Button
variant="ghost"
size="sm"
onClick={onLoadMore}
disabled={isFetchingNextPage}
className="w-full text-cyan-400 hover:text-white hover:bg-cyan-500/10"
className="w-full text-cyan-400 hover:text-white hover:bg-cyan-500/10 transition-all"
aria-label={`Load more ${viewMode}`}
>
{isFetchingNextPage ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
Loading...
<Loader2 className="w-4 h-4 mr-2 animate-spin" aria-hidden="true" />
<span>Loading...</span>
</>
) : (
`Load More ${viewMode}`
<>
<span>Load More {viewMode}</span>
<span className="sr-only">. Press to load additional items.</span>
</>
)}
</Button>
</div>
)}
</div>
)}
</div>
</div>
</aside>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Orchestrates split-view design with sidebar navigation and content viewer
*/

import { useEffect, useState } from "react";
import { useCallback, useEffect, useState } from "react";
import { InspectorDialog, InspectorDialogContent, InspectorDialogTitle } from "../../../ui/primitives";
import type { CodeExample, DocumentChunk, InspectorSelectedItem, KnowledgeItem } from "../../types";
import { useInspectorPagination } from "../hooks/useInspectorPagination";
Expand Down Expand Up @@ -79,49 +79,56 @@ export const KnowledgeInspector: React.FC<KnowledgeInspectorProps> = ({ item, op
}
}, [viewMode, currentItems, selectedItem]);

const handleCopy = async (text: string, id: string) => {
await navigator.clipboard.writeText(text);
setCopiedId(id);
setTimeout(() => setCopiedId(null), 2000);
};

const handleItemSelect = (item: DocumentChunk | CodeExample) => {
if (viewMode === "documents") {
const doc = item as DocumentChunk;
setSelectedItem({
type: "document",
id: doc.id || "",
content: doc.content || "",
metadata: {
title: doc.metadata?.title,
section: doc.metadata?.section,
relevance_score: doc.metadata?.relevance_score,
url: doc.metadata?.url,
tags: doc.metadata?.tags,
},
});
} else {
const code = item as CodeExample;
setSelectedItem({
type: "code",
id: String(code.id),
content: code.content || code.code || "",
metadata: {
language: code.language,
file_path: code.file_path,
summary: code.summary,
relevance_score: code.metadata?.relevance_score,
title: code.title || code.example_name,
},
});
const handleCopy = useCallback(async (text: string, id: string) => {
try {
await navigator.clipboard.writeText(text);
setCopiedId(id);
setTimeout(() => setCopiedId((v) => (v === id ? null : v)), 2000);
} catch (error) {
console.error("Failed to copy to clipboard:", error);
}
};
}, []);

const handleItemSelect = useCallback(
(item: DocumentChunk | CodeExample) => {
if (viewMode === "documents") {
const doc = item as DocumentChunk;
setSelectedItem({
type: "document",
id: doc.id || "",
content: doc.content || "",
metadata: {
title: doc.title || doc.metadata?.title,
section: doc.section || doc.metadata?.section,
relevance_score: doc.metadata?.relevance_score,
url: doc.url || doc.metadata?.url,
tags: doc.metadata?.tags,
},
});
} else {
const code = item as CodeExample;
setSelectedItem({
type: "code",
id: String(code.id),
content: code.content || code.code || "",
metadata: {
language: code.language,
file_path: code.file_path,
summary: code.summary,
relevance_score: code.metadata?.relevance_score,
title: code.title || code.example_name,
},
});
}
},
[viewMode],
);

const handleViewModeChange = (mode: ViewMode) => {
const handleViewModeChange = useCallback((mode: ViewMode) => {
setViewMode(mode);
setSelectedItem(null);
setSearchQuery("");
};
}, []);

return (
<InspectorDialog open={open} onOpenChange={onOpenChange}>
Expand Down