feat: Implement ZIP RAG import functionality with background processing
- Added support for ZIP RAG import in the asset management system. - Introduced a new background task type for ZIP RAG imports. - Enhanced the asset import process to allow for optional development processing. - Updated the UI to reflect the new RAG processing status and options. - Created database migration for background tasks related to ZIP RAG imports. - Refactored asset handling to support FLV normalization and improved error handling. - Added new API endpoints and updated existing ones to accommodate changes.
This commit is contained in:
@@ -6,13 +6,14 @@ import { Upload, Database, FileArchive, CheckCircle2, AlertTriangle, Scissors }
|
||||
|
||||
export default function AdminSharedMaterialsPage() {
|
||||
const [zipFile, setZipFile] = useState<File | null>(null);
|
||||
const [ingestRag, setIngestRag] = useState(true);
|
||||
const [ingestRag, setIngestRag] = useState(false);
|
||||
const [englishLevel, setEnglishLevel] = useState('');
|
||||
const [plans, setPlans] = useState<MySqlPlan[]>([]);
|
||||
const [courses, setCourses] = useState<MySqlCourse[]>([]);
|
||||
const [selectedPlanId, setSelectedPlanId] = useState<number | ''>('');
|
||||
const [selectedCourseId, setSelectedCourseId] = useState<number | ''>('');
|
||||
const [splitToRegular, setSplitToRegular] = useState(false);
|
||||
const [useDevProcessing, setUseDevProcessing] = useState(false);
|
||||
const [regularPlanId, setRegularPlanId] = useState<number | ''>('');
|
||||
const [regularCourses, setRegularCourses] = useState<MySqlCourse[]>([]);
|
||||
const [selectedCourseIdR1, setSelectedCourseIdR1] = useState<number | ''>('');
|
||||
@@ -27,6 +28,8 @@ export default function AdminSharedMaterialsPage() {
|
||||
rag_ingested_assets: number;
|
||||
rag_chunks_ingested: number;
|
||||
failed_entries: string[];
|
||||
rag_background_started?: boolean;
|
||||
rag_background_items?: number;
|
||||
} | null>(null);
|
||||
|
||||
const canUpload = useMemo(() => Boolean(zipFile) && !loading, [zipFile, loading]);
|
||||
@@ -159,6 +162,7 @@ export default function AdminSharedMaterialsPage() {
|
||||
splitToRegular,
|
||||
selectedCourseIdR1 || undefined,
|
||||
selectedCourseIdR2 || undefined,
|
||||
useDevProcessing,
|
||||
);
|
||||
setResult(response);
|
||||
setPhase('done');
|
||||
@@ -218,7 +222,16 @@ export default function AdminSharedMaterialsPage() {
|
||||
checked={ingestRag}
|
||||
onChange={(e) => setIngestRag(e.target.checked)}
|
||||
/>
|
||||
<span className="font-medium">Ingerir automaticamente en RAG al importar</span>
|
||||
<span className="font-medium">Ingerir automaticamente en RAG al importar (recomendado activar solo para ZIPs pequeños)</span>
|
||||
</label>
|
||||
|
||||
<label className="flex items-center gap-3 rounded-lg border border-sky-200 bg-sky-50 px-4 py-3 text-sm text-sky-900">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={useDevProcessing}
|
||||
onChange={(e) => setUseDevProcessing(e.target.checked)}
|
||||
/>
|
||||
<span className="font-medium">Procesar este ZIP con infraestructura DEV (más potente) para transcripción/IA</span>
|
||||
</label>
|
||||
|
||||
<div className="space-y-2">
|
||||
@@ -396,7 +409,7 @@ export default function AdminSharedMaterialsPage() {
|
||||
|
||||
<p className="text-sm text-slate-700">{statusText}</p>
|
||||
<p className="text-xs text-slate-500">
|
||||
Nota: esta importacion ZIP corre en la misma solicitud (no crea fila en Tasks), por eso aqui ves el estado en vivo.
|
||||
Nota: la subida e importación base terminan en esta solicitud. Si activas RAG, su procesamiento puede continuar en segundo plano.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
@@ -405,6 +418,12 @@ export default function AdminSharedMaterialsPage() {
|
||||
{result && (
|
||||
<div className="rounded-2xl border border-slate-200 dark:border-white/10 bg-white dark:bg-white/[0.02] p-6 space-y-4">
|
||||
<h3 className="text-lg font-bold text-slate-900 dark:text-white">Resultado de la Importacion</h3>
|
||||
{result.rag_background_started && (
|
||||
<div className="inline-flex items-center gap-2 rounded-full border border-indigo-200 bg-indigo-50 px-3 py-1 text-xs font-semibold text-indigo-800">
|
||||
<span className="h-2 w-2 animate-pulse rounded-full bg-indigo-500" />
|
||||
RAG en segundo plano: {result.rag_background_items ?? 0} archivos en procesamiento
|
||||
</div>
|
||||
)}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div className="rounded-lg border border-slate-200 dark:border-white/10 p-4">
|
||||
<div className="flex items-center gap-2 text-slate-700 dark:text-gray-300">
|
||||
|
||||
@@ -98,6 +98,10 @@ export default function BackgroundTasksPage() {
|
||||
label = 'Transcription';
|
||||
color = 'bg-purple-100 text-purple-800';
|
||||
break;
|
||||
case 'zip_rag_import':
|
||||
label = 'ZIP RAG';
|
||||
color = 'bg-indigo-100 text-indigo-800';
|
||||
break;
|
||||
case 'lesson_image':
|
||||
label = 'Lesson Image';
|
||||
color = 'bg-blue-100 text-blue-800';
|
||||
@@ -165,7 +169,7 @@ export default function BackgroundTasksPage() {
|
||||
<div className="text-xs text-gray-400">({format(new Date(task.updated_at), 'yyyy')})</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 text-right space-x-2">
|
||||
{task.status === 'failed' && (
|
||||
{task.task_type === 'lesson_transcription' && task.status === 'failed' && (
|
||||
<button
|
||||
onClick={() => handleRetry(task.id)}
|
||||
disabled={actionLoading === task.id}
|
||||
@@ -175,14 +179,16 @@ export default function BackgroundTasksPage() {
|
||||
Retry
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={() => handleCancel(task.id)}
|
||||
disabled={actionLoading === task.id}
|
||||
className="inline-flex items-center px-3 py-1.5 border border-red-200 text-xs font-medium rounded-md text-red-700 bg-red-50 hover:bg-red-100 disabled:opacity-50"
|
||||
>
|
||||
{actionLoading === task.id ? <Loader2 className="w-3 h-3 animate-spin mr-1" /> : <XCircle className="w-3 h-3 mr-1" />}
|
||||
Cancel
|
||||
</button>
|
||||
{task.task_type === 'lesson_transcription' && (
|
||||
<button
|
||||
onClick={() => handleCancel(task.id)}
|
||||
disabled={actionLoading === task.id}
|
||||
className="inline-flex items-center px-3 py-1.5 border border-red-200 text-xs font-medium rounded-md text-red-700 bg-red-50 hover:bg-red-100 disabled:opacity-50"
|
||||
>
|
||||
{actionLoading === task.id ? <Loader2 className="w-3 h-3 animate-spin mr-1" /> : <XCircle className="w-3 h-3 mr-1" />}
|
||||
Cancel
|
||||
</button>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user