'use client';
import React from 'react';
import { FileText, GitBranch, Loader2 } from 'lucide-react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import { Badge } from '@/components/ui/badge';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import type { SkillFileContent, SkillFileInfo, SkillVersionRef } from '@/types';
type SkillDetailViewProps = {
title: string;
summary?: string | null;
badges?: React.ReactNode;
actions?: React.ReactNode;
currentVersion: string;
versions: SkillVersionRef[];
files: SkillFileInfo[];
content: string;
selectedFile?: SkillFileContent | null;
loadingFile?: boolean;
loadingVersion?: boolean;
onSelectVersion: (version: string) => void;
onOpenFile: (filePath: string) => void;
labels: {
overview: string;
files: string;
versions: string;
noReadme: string;
noFiles: string;
selectFile: string;
binaryFile: string;
current: string;
size: string;
};
};
export function SkillDetailView({
title,
summary,
badges,
actions,
currentVersion,
versions,
files,
content,
selectedFile,
loadingFile,
loadingVersion,
onSelectVersion,
onOpenFile,
labels,
}: SkillDetailViewProps) {
const readme = stripFrontmatter(content || '');
return (
{badges}
v{currentVersion || '-'}
{loadingVersion && }
{title}
{summary &&
{summary}
}
{actions}
{labels.overview}
{labels.files}
{labels.versions}
{readme.trim() ? (
) : (
} text={labels.noReadme} />
)}
{files.length === 0 ? (
} text={labels.noFiles} />
) : (
{files.map((file) => (
))}
)}
{loadingFile ? (
) : selectedFile ? (
) : (
} text={labels.selectFile} />
)}
{versions.map((version) => (
))}
);
}
function FilePreview({ file, labels }: { file: SkillFileContent; labels: SkillDetailViewProps['labels'] }) {
const content = file.content || '';
return (
{file.filePath}
{labels.size}: {formatBytes(file.fileSize)}
{file.isBinary ? (
} text={labels.binaryFile} />
) : isMarkdown(file.filePath, file.contentType) ? (
) : (
{content}
)}
);
}
function MarkdownPreview({ content }: { content: string }) {
return (
{content}
);
}
function EmptyPanel({ icon, text }: { icon: React.ReactNode; text: string }) {
return (
);
}
function stripFrontmatter(value: string): string {
if (!value.startsWith('---')) return value;
const marker = value.indexOf('\n---', 3);
if (marker < 0) return value;
const after = value.indexOf('\n', marker + 4);
return after >= 0 ? value.slice(after + 1) : '';
}
function isMarkdown(filePath: string, contentType?: string | null): boolean {
return filePath.toLowerCase().endsWith('.md') || (contentType || '').includes('markdown');
}
function formatBytes(value: number | undefined): string {
const size = Number(value || 0);
if (size < 1024) return `${size} B`;
if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)} KB`;
return `${(size / 1024 / 1024).toFixed(1)} MB`;
}