0a9588abb7
- Add pagination support to findAll (page, limit query params) - Add findByTemplateId method to service - Add GET /by-template/:templateId endpoint to controller - Service already includes CRUD for QuestionBank and QuestionBankItem
150 lines
6.2 KiB
TypeScript
150 lines
6.2 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
import { X } from 'lucide-react';
|
|
import { knowledgeBaseService } from '../services/knowledgeBaseService';
|
|
import { useLanguage } from '../contexts/LanguageContext';
|
|
|
|
interface ChunkInfo {
|
|
fileId: string;
|
|
fileName: string;
|
|
totalChunks: number;
|
|
chunkSize: number;
|
|
chunkOverlap: number;
|
|
chunks: Array<{
|
|
index: number;
|
|
content: string;
|
|
contentLength: number;
|
|
startPosition: number;
|
|
endPosition: number;
|
|
}>;
|
|
}
|
|
|
|
interface ChunkInfoDrawerProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
fileId: string;
|
|
fileName: string;
|
|
authToken: string;
|
|
}
|
|
|
|
export const ChunkInfoDrawer: React.FC<ChunkInfoDrawerProps> = ({
|
|
isOpen,
|
|
onClose,
|
|
fileId,
|
|
fileName,
|
|
authToken,
|
|
}) => {
|
|
const { t } = useLanguage();
|
|
const [chunkInfo, setChunkInfo] = useState<ChunkInfo | null>(null);
|
|
const [loading, setLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
if (isOpen && fileId) {
|
|
loadChunks();
|
|
}
|
|
}, [isOpen, fileId]);
|
|
|
|
const loadChunks = async () => {
|
|
setLoading(true);
|
|
setError(null);
|
|
try {
|
|
const data = await knowledgeBaseService.getFileChunks(fileId, authToken);
|
|
setChunkInfo(data);
|
|
} catch (err) {
|
|
console.error('Failed to load chunks:', err);
|
|
setError(t('errorLoadData'));
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
if (!isOpen) return null;
|
|
|
|
return (
|
|
<>
|
|
{/* Backdrop */}
|
|
<div
|
|
className="fixed inset-0 bg-black/50 z-[9998]"
|
|
onClick={onClose}
|
|
/>
|
|
|
|
{/* Drawer */}
|
|
<div className="fixed right-0 top-0 h-full w-full md:w-2/3 lg:w-1/2 bg-white shadow-2xl z-[9999] flex flex-col">
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between p-6 border-b border-slate-200 shrink-0">
|
|
<div>
|
|
<h2 className="text-xl font-semibold text-slate-800">
|
|
{t('chunkInfo')}
|
|
</h2>
|
|
<p className="text-sm text-slate-500 mt-1">{fileName}</p>
|
|
</div>
|
|
<button
|
|
onClick={onClose}
|
|
className="p-2 hover:bg-slate-100 rounded-lg transition-colors"
|
|
>
|
|
<X className="w-5 h-5" />
|
|
</button>
|
|
</div>
|
|
|
|
{/* Content */}
|
|
<div className="flex-1 overflow-y-auto p-6">
|
|
{loading ? (
|
|
<div className="flex items-center justify-center h-full">
|
|
<div className="text-slate-500">{t('loading')}</div>
|
|
</div>
|
|
) : error ? (
|
|
<div className="flex items-center justify-center h-full">
|
|
<div className="text-red-500">{error}</div>
|
|
</div>
|
|
) : chunkInfo ? (
|
|
<div>
|
|
{/* Summary */}
|
|
<div className="bg-slate-50 rounded-lg p-4 mb-6 space-y-2">
|
|
<div className="flex justify-between">
|
|
<span className="text-slate-600">{t('totalChunks')}:</span>
|
|
<span className="font-semibold">{chunkInfo.totalChunks}</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-slate-600">{t('chunkSize')}:</span>
|
|
<span className="font-semibold">{chunkInfo.chunkSize} tokens</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-slate-600">{t('chunkOverlap')}:</span>
|
|
<span className="font-semibold">{chunkInfo.chunkOverlap} tokens</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Chunks List */}
|
|
<div className="space-y-4">
|
|
{chunkInfo.chunks.map((chunk) => (
|
|
<div
|
|
key={chunk.index}
|
|
className="border border-slate-200 rounded-lg p-4 hover:border-blue-300 transition-colors"
|
|
>
|
|
<div className="flex justify-between items-center mb-3">
|
|
<span className="font-semibold text-slate-800">
|
|
{t('chunkIndex')} #{chunk.index}
|
|
</span>
|
|
<span className="text-sm text-slate-500">
|
|
{chunk.contentLength} {t('contentLength')}
|
|
</span>
|
|
</div>
|
|
<div className="bg-white border border-slate-200 rounded-lg p-4 max-h-96 overflow-y-auto">
|
|
<div className="text-slate-700 text-sm leading-7 whitespace-pre-wrap break-words" style={{ fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif' }}>
|
|
{chunk.content}
|
|
</div>
|
|
</div>
|
|
<div className="text-xs text-slate-400 mt-2">
|
|
{t('position')}: {chunk.startPosition} - {chunk.endPosition}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
};
|