feat: implement QuestionBank CRUD with pagination and template query
- 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
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user