'use client'; import React, { useState } from 'react'; import { questionBankApi, QuestionBank, CreateQuestionBankPayload, QuestionBankType } from '@/lib/api'; import { X, Save, Sparkles, Volume2, Trash2, Upload } from 'lucide-react'; interface QuestionBankEditorProps { question?: QuestionBank | null; onSuccess?: () => void; onCancel?: () => void; } export default function QuestionBankEditor({ question, onSuccess, onCancel }: QuestionBankEditorProps) { const [formData, setFormData] = useState({ question_text: question?.question_text || '', question_type: question?.question_type || 'multiple-choice', options: question?.options || (question?.question_type === 'true-false' ? ['Verdadero', 'Falso'] : undefined), correct_answer: question?.correct_answer, explanation: question?.explanation || '', points: question?.points || 1, difficulty: question?.difficulty || 'medium', tags: question?.tags || [], media_url: question?.media_url, media_type: question?.media_type, skill_assessed: question?.skill_assessed, }); const [audioFile, setAudioFile] = useState(null); const [audioPreview, setAudioPreview] = useState(question?.audio_url || null); const [uploadingAudio, setUploadingAudio] = useState(false); const [newTag, setNewTag] = useState(''); const [saving, setSaving] = useState(false); const [generatingAI, setGeneratingAI] = useState(false); const normalizedOptions = Array.isArray(formData.options) ? (formData.options as string[]) : []; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!formData.question_text.trim()) { alert('El texto de la pregunta es obligatorio'); return; } try { setSaving(true); let audioUrl = audioPreview; // Upload audio file if provided if (audioFile) { setUploadingAudio(true); const audioFormData = new FormData(); audioFormData.append('file', audioFile); audioFormData.append('type', 'question_audio'); const uploadResponse = await fetch(`${process.env.NEXT_PUBLIC_CMS_API_URL || 'http://localhost:3001'}/assets/upload`, { method: 'POST', headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}`, }, body: audioFormData, }); if (uploadResponse.ok) { const uploadResult = await uploadResponse.json(); audioUrl = uploadResult.url || uploadResult.file_url; } else { console.warn('Audio upload failed, continuing without audio'); } setUploadingAudio(false); } const payload = { ...formData, audio_url: audioUrl || undefined, audio_text: formData.question_text, // Suggestion for what the audio should say }; if (question) { await questionBankApi.update(question.id, payload); } else { await questionBankApi.create(payload); } onSuccess?.(); } catch (error) { console.error('Failed to save question:', error); alert('Error al guardar la pregunta'); } finally { setSaving(false); } }; const handleAddTag = () => { if (newTag.trim() && !formData.tags?.includes(newTag.trim())) { setFormData({ ...formData, tags: [...(formData.tags || []), newTag.trim()], }); setNewTag(''); } }; const handleRemoveTag = (tagToRemove: string) => { setFormData({ ...formData, tags: formData.tags?.filter(tag => tag !== tagToRemove) || [], }); }; const handleAddOption = () => { const currentOptions = normalizedOptions; setFormData({ ...formData, options: [...currentOptions, `Opci贸n ${currentOptions.length + 1}`], }); }; const handleRemoveOption = (index: number) => { const newOptions = normalizedOptions.filter((_, i) => i !== index); setFormData({ ...formData, options: newOptions }); // Adjust correct answer if needed if (typeof formData.correct_answer === 'number' && formData.correct_answer === index) { setFormData({ ...formData, correct_answer: undefined }); } else if (typeof formData.correct_answer === 'number' && formData.correct_answer > index) { setFormData({ ...formData, correct_answer: formData.correct_answer - 1 }); } }; const handleGenerateWithAI = async () => { if (!formData.question_text.trim()) { alert('Ingresa una pregunta base o contexto para que la IA genere las opciones y explicaci贸n'); return; } // Define las 4 habilidades del ingl茅s que deben cubrirse const skills = ['reading', 'listening', 'speaking', 'writing']; const randomSkill = skills[Math.floor(Math.random() * skills.length)]; const skillPrompts = { reading: 'Focus on reading comprehension, vocabulary in context, or text analysis.', listening: 'Focus on listening comprehension, audio-based understanding, or spoken dialogue interpretation.', speaking: 'Focus on oral production, pronunciation, or conversational response.', writing: 'Focus on written production, grammar in writing, or composition skills.' }; try { setGeneratingAI(true); // AI generation con el nuevo endpoint de question bank const token = localStorage.getItem('studio_token'); const response = await fetch(`${process.env.NEXT_PUBLIC_CMS_API_URL || 'http://localhost:3001'}/question-bank/ai-generate`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}`, }, body: JSON.stringify({ question_text: formData.question_text, difficulty: formData.difficulty, skill: randomSkill, }), }); if (!response.ok) { const errorText = await response.text(); throw new Error(`Error ${response.status}: ${errorText}`); } const data = await response.json(); if (data.options && data.correct_answer !== undefined) { setFormData({ ...formData, options: data.options, correct_answer: data.correct_answer, explanation: data.explanation ? `${data.explanation}\n\n馃搳 Skill assessed: ${randomSkill.toUpperCase()}` : `This question assesses ${randomSkill.toUpperCase()} skills.`, tags: [...(formData.tags || []), randomSkill, 'ai-generated'], }); alert(`IA gener贸 las opciones y explicaci贸n enfocadas en la habilidad: ${randomSkill.toUpperCase()}`); } } catch (error) { console.error('AI generation error:', error); alert(`Error al generar con IA: ${error instanceof Error ? error.message : 'Verifica que Ollama est茅 configurado'}`); } finally { setGeneratingAI(false); } }; const isMultipleChoice = formData.question_type === 'multiple-choice' || formData.question_type === 'true-false'; return (
{/* Header */}

{question ? 'Editar Pregunta' : 'Nueva Pregunta'}

{question ? 'Modifica los detalles de la pregunta' : 'Crea una nueva pregunta para el banco'}

{/* Form */}
{/* Question Type & Difficulty */}
{/* Question Text */}