Commit ·
afdd8ce
1
Parent(s): 5a88b6d
Update readme
Browse files- README.md +177 -7
- src/components/ParameterInput.tsx +96 -0
- src/components/QuestionParameterForm.tsx +16 -189
- src/types/questionConfig.ts +204 -0
README.md
CHANGED
|
@@ -1,4 +1,15 @@
|
|
| 1 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
## Getting Started
|
| 4 |
|
|
@@ -18,16 +29,175 @@ Open [http://localhost:3000](http://localhost:3000) with your browser to see the
|
|
| 18 |
|
| 19 |
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
| 20 |
|
| 21 |
-
|
| 22 |
|
| 23 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
|
| 25 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
|
| 27 |
-
|
| 28 |
-
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
| 29 |
|
| 30 |
-
|
|
|
|
|
|
|
|
|
|
| 31 |
|
| 32 |
## Deploy on Vercel
|
| 33 |
|
|
|
|
| 1 |
+
# QuizFlash 🚀
|
| 2 |
+
|
| 3 |
+
An intelligent quiz generation platform powered by AI that creates customizable educational questions from any source material.
|
| 4 |
+
|
| 5 |
+
## Features
|
| 6 |
+
|
| 7 |
+
- **Multiple Question Types**: Support for multiple choice, cloze (fill-in-the-blank), grammar check, reading comprehension, and essay questions
|
| 8 |
+
- **AI-Powered Generation**: Uses OpenAI GPT-4 to generate high-quality, educational questions
|
| 9 |
+
- **Configurable Parameters**: Each question type has customizable difficulty levels and specific parameters
|
| 10 |
+
- **Source Material Support**: Generate questions based on your own text content
|
| 11 |
+
- **Real-time Question Bank**: Build and manage a collection of generated questions
|
| 12 |
+
- **Modern UI**: Clean, responsive interface built with Next.js and Tailwind CSS
|
| 13 |
|
| 14 |
## Getting Started
|
| 15 |
|
|
|
|
| 29 |
|
| 30 |
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
| 31 |
|
| 32 |
+
## System Architecture
|
| 33 |
|
| 34 |
+
```mermaid
|
| 35 |
+
graph TD
|
| 36 |
+
A["🔧 Question Config<br/>(questionConfig.ts)"] --> B["📋 Parameter Form<br/>(QuestionParameterForm)"]
|
| 37 |
+
B --> C["🎛️ Parameter Input<br/>(ParameterInput)"]
|
| 38 |
+
C --> D["👤 User Input<br/>(UI Interaction)"]
|
| 39 |
+
D --> E["📊 Parameters Object<br/>(QuestionParameters)"]
|
| 40 |
+
|
| 41 |
+
E --> F["🎯 Question Type<br/>Selection"]
|
| 42 |
+
F --> G["📝 Source Material<br/>(Optional)"]
|
| 43 |
+
|
| 44 |
+
E --> H["🚀 Generate Question<br/>(API Call)"]
|
| 45 |
+
G --> H
|
| 46 |
+
|
| 47 |
+
H --> I["🤖 AI Generation<br/>(OpenAI API)"]
|
| 48 |
+
|
| 49 |
+
I --> J["📄 Prompt Templates<br/>(quiz-generation.ts)"]
|
| 50 |
+
J --> K["🔄 Template Rendering<br/>(renderTemplate)"]
|
| 51 |
+
K --> L["📤 Final Prompt<br/>(to AI Model)"]
|
| 52 |
+
|
| 53 |
+
L --> M["🧠 AI Processing<br/>(GPT-4 Mini)"]
|
| 54 |
+
M --> N["📋 Zod Schema<br/>Validation"]
|
| 55 |
+
N --> O["✅ Structured Response<br/>(JSON Object)"]
|
| 56 |
+
|
| 57 |
+
O --> P["💾 Question Bank<br/>(State Management)"]
|
| 58 |
+
P --> Q["🎴 Question Card<br/>(UI Component)"]
|
| 59 |
+
|
| 60 |
+
Q --> R["👁️ User Display<br/>(Question Bank UI)"]
|
| 61 |
+
|
| 62 |
+
subgraph "Configuration Layer"
|
| 63 |
+
A
|
| 64 |
+
B
|
| 65 |
+
C
|
| 66 |
+
end
|
| 67 |
+
|
| 68 |
+
subgraph "User Interaction Layer"
|
| 69 |
+
D
|
| 70 |
+
E
|
| 71 |
+
F
|
| 72 |
+
G
|
| 73 |
+
end
|
| 74 |
+
|
| 75 |
+
subgraph "API Processing Layer"
|
| 76 |
+
H
|
| 77 |
+
I
|
| 78 |
+
J
|
| 79 |
+
K
|
| 80 |
+
L
|
| 81 |
+
end
|
| 82 |
+
|
| 83 |
+
subgraph "AI Generation Layer"
|
| 84 |
+
M
|
| 85 |
+
N
|
| 86 |
+
O
|
| 87 |
+
end
|
| 88 |
+
|
| 89 |
+
subgraph "Display Layer"
|
| 90 |
+
P
|
| 91 |
+
Q
|
| 92 |
+
R
|
| 93 |
+
end
|
| 94 |
+
|
| 95 |
+
style A fill:#e1f5fe
|
| 96 |
+
style J fill:#fff3e0
|
| 97 |
+
style Q fill:#f3e5f5
|
| 98 |
+
style M fill:#e8f5e8
|
| 99 |
+
```
|
| 100 |
+
|
| 101 |
+
### Flow Description
|
| 102 |
+
|
| 103 |
+
#### 1. Configuration Layer
|
| 104 |
+
- **Question Config**: Abstract configuration system that defines parameter schemas for each question type
|
| 105 |
+
- **Parameter Form**: Dynamically renders form fields based on the configuration
|
| 106 |
+
- **Parameter Input**: Reusable input components (select, range, input, checkbox)
|
| 107 |
+
|
| 108 |
+
#### 2. User Interaction Layer
|
| 109 |
+
- **User Input**: Users interact with dynamically generated parameter forms
|
| 110 |
+
- **Parameters Object**: Collects user preferences (difficulty, number of options, etc.)
|
| 111 |
+
- **Question Type Selection**: Users choose from available question types
|
| 112 |
+
- **Source Material**: Optional text input to provide context for questions
|
| 113 |
+
|
| 114 |
+
#### 3. API Processing Layer
|
| 115 |
+
- **Generate Question**: API endpoint processes the generation request
|
| 116 |
+
- **Prompt Templates**: Pre-defined, configurable templates for each question type
|
| 117 |
+
- **Template Rendering**: Dynamic substitution of user parameters into prompt templates
|
| 118 |
+
- **Final Prompt**: Complete, contextualized prompt ready for AI processing
|
| 119 |
+
|
| 120 |
+
#### 4. AI Generation Layer
|
| 121 |
+
- **AI Processing**: OpenAI GPT-4 Mini generates questions based on the prompt
|
| 122 |
+
- **Zod Schema Validation**: Ensures AI responses match expected structure
|
| 123 |
+
- **Structured Response**: Clean, validated JSON object containing question data
|
| 124 |
+
|
| 125 |
+
#### 5. Display Layer
|
| 126 |
+
- **Question Bank**: State management for storing and organizing generated questions
|
| 127 |
+
- **Question Card**: UI components for rendering individual questions
|
| 128 |
+
- **User Display**: Final question bank interface with selection and management features
|
| 129 |
+
|
| 130 |
+
### Key Architecture Benefits
|
| 131 |
+
|
| 132 |
+
- **🔧 Configurable**: Adding new question types requires only configuration changes
|
| 133 |
+
- **♻️ Reusable**: Components work across all question types
|
| 134 |
+
- **🛡️ Type-Safe**: Full TypeScript support throughout the entire flow
|
| 135 |
+
- **📦 Modular**: Clean separation of concerns between layers
|
| 136 |
+
- **🚀 Scalable**: Easy to extend with new features and question types
|
| 137 |
|
| 138 |
+
## Tech Stack
|
| 139 |
+
|
| 140 |
+
- **Frontend**: Next.js 14, React, TypeScript, Tailwind CSS
|
| 141 |
+
- **AI Integration**: OpenAI GPT-4 Mini, Vercel AI SDK
|
| 142 |
+
- **Validation**: Zod schema validation
|
| 143 |
+
- **Styling**: Tailwind CSS with dark mode support
|
| 144 |
+
- **State Management**: React hooks and context
|
| 145 |
+
|
| 146 |
+
## Project Structure
|
| 147 |
+
|
| 148 |
+
```
|
| 149 |
+
src/
|
| 150 |
+
├── app/ # Next.js App Router
|
| 151 |
+
│ ├── api/ # API routes
|
| 152 |
+
│ │ ├── chat/ # Chat functionality
|
| 153 |
+
│ │ ├── generate-question/ # Single question generation
|
| 154 |
+
│ │ └── generate-quiz/ # Multiple question generation
|
| 155 |
+
│ └── page.tsx # Main application page
|
| 156 |
+
├── components/ # React components
|
| 157 |
+
│ ├── QuestionParameterForm.tsx # Dynamic parameter form
|
| 158 |
+
│ └── ParameterInput.tsx # Reusable input components
|
| 159 |
+
├── types/ # TypeScript type definitions
|
| 160 |
+
│ ├── quiz.ts # Core quiz types
|
| 161 |
+
│ └── questionConfig.ts # Configuration types and schemas
|
| 162 |
+
└── prompts/ # AI prompt templates
|
| 163 |
+
└── quiz-generation.ts # Question generation prompts
|
| 164 |
+
```
|
| 165 |
+
|
| 166 |
+
## Adding New Question Types
|
| 167 |
+
|
| 168 |
+
Thanks to the abstract configuration system, adding new question types is incredibly simple:
|
| 169 |
+
|
| 170 |
+
1. Add configuration to `src/types/questionConfig.ts`:
|
| 171 |
+
```typescript
|
| 172 |
+
'your-new-type': {
|
| 173 |
+
id: 'your-new-type',
|
| 174 |
+
parameters: [
|
| 175 |
+
{
|
| 176 |
+
key: 'difficulty',
|
| 177 |
+
label: 'Difficulty Level',
|
| 178 |
+
type: 'select',
|
| 179 |
+
defaultValue: 'intermediate',
|
| 180 |
+
options: [
|
| 181 |
+
{ value: 'beginner', label: 'Beginner' },
|
| 182 |
+
{ value: 'intermediate', label: 'Intermediate' },
|
| 183 |
+
{ value: 'advanced', label: 'Advanced' },
|
| 184 |
+
],
|
| 185 |
+
},
|
| 186 |
+
// Add more parameters as needed
|
| 187 |
+
],
|
| 188 |
+
}
|
| 189 |
+
```
|
| 190 |
+
|
| 191 |
+
2. That's it! No component modifications required. The UI will automatically render the appropriate form fields.
|
| 192 |
+
|
| 193 |
+
## Learn More
|
| 194 |
|
| 195 |
+
To learn more about the technologies used:
|
|
|
|
| 196 |
|
| 197 |
+
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API
|
| 198 |
+
- [Vercel AI SDK](https://sdk.vercel.ai/docs) - AI integration toolkit
|
| 199 |
+
- [OpenAI API](https://platform.openai.com/docs) - AI model documentation
|
| 200 |
+
- [Tailwind CSS](https://tailwindcss.com/docs) - utility-first CSS framework
|
| 201 |
|
| 202 |
## Deploy on Vercel
|
| 203 |
|
src/components/ParameterInput.tsx
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use client';
|
| 2 |
+
|
| 3 |
+
import { ParameterConfig } from '@/types/questionConfig';
|
| 4 |
+
|
| 5 |
+
interface ParameterInputProps {
|
| 6 |
+
config: ParameterConfig;
|
| 7 |
+
value: any;
|
| 8 |
+
onChange: (value: any) => void;
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
export default function ParameterInput({ config, value, onChange }: ParameterInputProps) {
|
| 12 |
+
const { key, label, type, options, min, max, step, placeholder, helpText } = config;
|
| 13 |
+
|
| 14 |
+
const currentValue = value ?? config.defaultValue;
|
| 15 |
+
|
| 16 |
+
const baseInputClasses = "w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500";
|
| 17 |
+
|
| 18 |
+
switch (type) {
|
| 19 |
+
case 'select':
|
| 20 |
+
return (
|
| 21 |
+
<div>
|
| 22 |
+
<label className="block text-sm font-medium mb-2">{label}</label>
|
| 23 |
+
<select
|
| 24 |
+
value={currentValue}
|
| 25 |
+
onChange={(e) => {
|
| 26 |
+
const val = options?.find(opt => opt.value.toString() === e.target.value)?.value;
|
| 27 |
+
onChange(val);
|
| 28 |
+
}}
|
| 29 |
+
className={baseInputClasses}
|
| 30 |
+
>
|
| 31 |
+
{options?.map((option) => (
|
| 32 |
+
<option key={option.value.toString()} value={option.value.toString()}>
|
| 33 |
+
{option.label}
|
| 34 |
+
</option>
|
| 35 |
+
))}
|
| 36 |
+
</select>
|
| 37 |
+
</div>
|
| 38 |
+
);
|
| 39 |
+
|
| 40 |
+
case 'range':
|
| 41 |
+
return (
|
| 42 |
+
<div>
|
| 43 |
+
<label className="block text-sm font-medium mb-2">{label}</label>
|
| 44 |
+
<input
|
| 45 |
+
type="range"
|
| 46 |
+
min={min}
|
| 47 |
+
max={max}
|
| 48 |
+
step={step}
|
| 49 |
+
value={currentValue}
|
| 50 |
+
onChange={(e) => onChange(parseInt(e.target.value))}
|
| 51 |
+
className="w-full"
|
| 52 |
+
/>
|
| 53 |
+
<div className="text-center text-sm text-gray-600">
|
| 54 |
+
{currentValue} {helpText}
|
| 55 |
+
</div>
|
| 56 |
+
</div>
|
| 57 |
+
);
|
| 58 |
+
|
| 59 |
+
case 'input':
|
| 60 |
+
return (
|
| 61 |
+
<div>
|
| 62 |
+
<label className="block text-sm font-medium mb-2">{label}</label>
|
| 63 |
+
<input
|
| 64 |
+
type="text"
|
| 65 |
+
value={currentValue || ''}
|
| 66 |
+
placeholder={placeholder}
|
| 67 |
+
onChange={(e) => onChange(e.target.value)}
|
| 68 |
+
className={baseInputClasses}
|
| 69 |
+
/>
|
| 70 |
+
{helpText && (
|
| 71 |
+
<div className="text-xs text-gray-500 mt-1">{helpText}</div>
|
| 72 |
+
)}
|
| 73 |
+
</div>
|
| 74 |
+
);
|
| 75 |
+
|
| 76 |
+
case 'checkbox':
|
| 77 |
+
return (
|
| 78 |
+
<div className="flex items-center">
|
| 79 |
+
<input
|
| 80 |
+
type="checkbox"
|
| 81 |
+
id={key}
|
| 82 |
+
checked={currentValue || false}
|
| 83 |
+
onChange={(e) => onChange(e.target.checked)}
|
| 84 |
+
className="mr-2"
|
| 85 |
+
/>
|
| 86 |
+
<label htmlFor={key} className="text-sm font-medium">{label}</label>
|
| 87 |
+
{helpText && (
|
| 88 |
+
<div className="text-xs text-gray-500 ml-2">{helpText}</div>
|
| 89 |
+
)}
|
| 90 |
+
</div>
|
| 91 |
+
);
|
| 92 |
+
|
| 93 |
+
default:
|
| 94 |
+
return null;
|
| 95 |
+
}
|
| 96 |
+
}
|
src/components/QuestionParameterForm.tsx
CHANGED
|
@@ -1,6 +1,8 @@
|
|
| 1 |
'use client';
|
| 2 |
|
| 3 |
import { QuestionType, QuestionParameters } from '@/types/quiz';
|
|
|
|
|
|
|
| 4 |
|
| 5 |
interface QuestionParameterFormProps {
|
| 6 |
questionType: QuestionType;
|
|
@@ -17,194 +19,19 @@ export default function QuestionParameterForm({
|
|
| 17 |
onParametersChange({ ...parameters, [key]: value });
|
| 18 |
};
|
| 19 |
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
return (
|
| 23 |
-
<div className="space-y-4">
|
| 24 |
-
<div>
|
| 25 |
-
<label className="block text-sm font-medium mb-2">Difficulty Level</label>
|
| 26 |
-
<select
|
| 27 |
-
value={parameters.difficulty || 'intermediate'}
|
| 28 |
-
onChange={(e) => updateParameter('difficulty', e.target.value)}
|
| 29 |
-
className="w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
|
| 30 |
-
>
|
| 31 |
-
<option value="beginner">Beginner</option>
|
| 32 |
-
<option value="intermediate">Intermediate</option>
|
| 33 |
-
<option value="advanced">Advanced</option>
|
| 34 |
-
</select>
|
| 35 |
-
</div>
|
| 36 |
-
<div>
|
| 37 |
-
<label className="block text-sm font-medium mb-2">Number of Options</label>
|
| 38 |
-
<select
|
| 39 |
-
value={parameters.numOptions || 4}
|
| 40 |
-
onChange={(e) => updateParameter('numOptions', parseInt(e.target.value))}
|
| 41 |
-
className="w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
|
| 42 |
-
>
|
| 43 |
-
<option value={3}>3 Options</option>
|
| 44 |
-
<option value={4}>4 Options</option>
|
| 45 |
-
<option value={5}>5 Options</option>
|
| 46 |
-
</select>
|
| 47 |
-
</div>
|
| 48 |
-
</div>
|
| 49 |
-
);
|
| 50 |
-
|
| 51 |
-
case 'cloze':
|
| 52 |
-
return (
|
| 53 |
-
<div className="space-y-4">
|
| 54 |
-
<div>
|
| 55 |
-
<label className="block text-sm font-medium mb-2">Target Part of Speech</label>
|
| 56 |
-
<select
|
| 57 |
-
value={parameters.partOfSpeech || 'any'}
|
| 58 |
-
onChange={(e) => updateParameter('partOfSpeech', e.target.value)}
|
| 59 |
-
className="w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
|
| 60 |
-
>
|
| 61 |
-
<option value="any">Any</option>
|
| 62 |
-
<option value="noun">Nouns</option>
|
| 63 |
-
<option value="verb">Verbs</option>
|
| 64 |
-
<option value="adjective">Adjectives</option>
|
| 65 |
-
<option value="adverb">Adverbs</option>
|
| 66 |
-
</select>
|
| 67 |
-
</div>
|
| 68 |
-
<div>
|
| 69 |
-
<label className="block text-sm font-medium mb-2">Number of Blanks</label>
|
| 70 |
-
<input
|
| 71 |
-
type="range"
|
| 72 |
-
min="1"
|
| 73 |
-
max="5"
|
| 74 |
-
value={parameters.numBlanks || 1}
|
| 75 |
-
onChange={(e) => updateParameter('numBlanks', parseInt(e.target.value))}
|
| 76 |
-
className="w-full"
|
| 77 |
-
/>
|
| 78 |
-
<div className="text-center text-sm text-gray-600">{parameters.numBlanks || 1} blank(s)</div>
|
| 79 |
-
</div>
|
| 80 |
-
</div>
|
| 81 |
-
);
|
| 82 |
-
|
| 83 |
-
case 'grammar':
|
| 84 |
-
return (
|
| 85 |
-
<div className="space-y-4">
|
| 86 |
-
<div>
|
| 87 |
-
<label className="block text-sm font-medium mb-2">Difficulty Level</label>
|
| 88 |
-
<select
|
| 89 |
-
value={parameters.difficulty || 'intermediate'}
|
| 90 |
-
onChange={(e) => updateParameter('difficulty', e.target.value)}
|
| 91 |
-
className="w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
|
| 92 |
-
>
|
| 93 |
-
<option value="beginner">Beginner</option>
|
| 94 |
-
<option value="intermediate">Intermediate</option>
|
| 95 |
-
<option value="advanced">Advanced</option>
|
| 96 |
-
</select>
|
| 97 |
-
</div>
|
| 98 |
-
<div>
|
| 99 |
-
<label className="block text-sm font-medium mb-2">Grammar Focus</label>
|
| 100 |
-
<select
|
| 101 |
-
value={parameters.grammarFocus || 'any'}
|
| 102 |
-
onChange={(e) => updateParameter('grammarFocus', e.target.value)}
|
| 103 |
-
className="w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
|
| 104 |
-
>
|
| 105 |
-
<option value="any">Any Grammar Rule</option>
|
| 106 |
-
<option value="verb-tenses">Verb Tenses</option>
|
| 107 |
-
<option value="subject-verb">Subject-Verb Agreement</option>
|
| 108 |
-
<option value="punctuation">Punctuation</option>
|
| 109 |
-
<option value="sentence-structure">Sentence Structure</option>
|
| 110 |
-
</select>
|
| 111 |
-
</div>
|
| 112 |
-
</div>
|
| 113 |
-
);
|
| 114 |
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
<option value="advanced">Advanced</option>
|
| 128 |
-
</select>
|
| 129 |
-
</div>
|
| 130 |
-
<div>
|
| 131 |
-
<label className="block text-sm font-medium mb-2">Question Focus</label>
|
| 132 |
-
<select
|
| 133 |
-
value={parameters.focus || 'comprehension'}
|
| 134 |
-
onChange={(e) => updateParameter('focus', e.target.value)}
|
| 135 |
-
className="w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
|
| 136 |
-
>
|
| 137 |
-
<option value="comprehension">General Comprehension</option>
|
| 138 |
-
<option value="inference">Making Inferences</option>
|
| 139 |
-
<option value="main-idea">Main Ideas</option>
|
| 140 |
-
<option value="details">Supporting Details</option>
|
| 141 |
-
<option value="vocabulary">Vocabulary in Context</option>
|
| 142 |
-
</select>
|
| 143 |
-
</div>
|
| 144 |
-
</div>
|
| 145 |
-
);
|
| 146 |
-
|
| 147 |
-
case 'essay':
|
| 148 |
-
return (
|
| 149 |
-
<div className="space-y-4">
|
| 150 |
-
<div>
|
| 151 |
-
<label className="block text-sm font-medium mb-2">Difficulty Level</label>
|
| 152 |
-
<select
|
| 153 |
-
value={parameters.difficulty || 'intermediate'}
|
| 154 |
-
onChange={(e) => updateParameter('difficulty', e.target.value)}
|
| 155 |
-
className="w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
|
| 156 |
-
>
|
| 157 |
-
<option value="beginner">Beginner</option>
|
| 158 |
-
<option value="intermediate">Intermediate</option>
|
| 159 |
-
<option value="advanced">Advanced</option>
|
| 160 |
-
</select>
|
| 161 |
-
</div>
|
| 162 |
-
<div>
|
| 163 |
-
<label className="block text-sm font-medium mb-2">Essay Type</label>
|
| 164 |
-
<select
|
| 165 |
-
value={parameters.essayType || 'argumentative'}
|
| 166 |
-
onChange={(e) => updateParameter('essayType', e.target.value)}
|
| 167 |
-
className="w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
|
| 168 |
-
>
|
| 169 |
-
<option value="argumentative">Argumentative</option>
|
| 170 |
-
<option value="descriptive">Descriptive</option>
|
| 171 |
-
<option value="narrative">Narrative</option>
|
| 172 |
-
<option value="expository">Expository</option>
|
| 173 |
-
<option value="compare-contrast">Compare & Contrast</option>
|
| 174 |
-
</select>
|
| 175 |
-
</div>
|
| 176 |
-
<div>
|
| 177 |
-
<label className="block text-sm font-medium mb-2">Word Count Range</label>
|
| 178 |
-
<select
|
| 179 |
-
value={parameters.wordCount || '300-500'}
|
| 180 |
-
onChange={(e) => updateParameter('wordCount', e.target.value)}
|
| 181 |
-
className="w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
|
| 182 |
-
>
|
| 183 |
-
<option value="150-250">150-250 words</option>
|
| 184 |
-
<option value="300-500">300-500 words</option>
|
| 185 |
-
<option value="500-750">500-750 words</option>
|
| 186 |
-
<option value="750-1000">750-1000 words</option>
|
| 187 |
-
</select>
|
| 188 |
-
</div>
|
| 189 |
-
</div>
|
| 190 |
-
);
|
| 191 |
-
|
| 192 |
-
default:
|
| 193 |
-
return (
|
| 194 |
-
<div className="space-y-4">
|
| 195 |
-
<div>
|
| 196 |
-
<label className="block text-sm font-medium mb-2">Difficulty Level</label>
|
| 197 |
-
<select
|
| 198 |
-
value={parameters.difficulty || 'intermediate'}
|
| 199 |
-
onChange={(e) => updateParameter('difficulty', e.target.value)}
|
| 200 |
-
className="w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
|
| 201 |
-
>
|
| 202 |
-
<option value="beginner">Beginner</option>
|
| 203 |
-
<option value="intermediate">Intermediate</option>
|
| 204 |
-
<option value="advanced">Advanced</option>
|
| 205 |
-
</select>
|
| 206 |
-
</div>
|
| 207 |
-
</div>
|
| 208 |
-
);
|
| 209 |
-
}
|
| 210 |
}
|
|
|
|
| 1 |
'use client';
|
| 2 |
|
| 3 |
import { QuestionType, QuestionParameters } from '@/types/quiz';
|
| 4 |
+
import { questionTypeConfigs, defaultQuestionTypeConfig } from '@/types/questionConfig';
|
| 5 |
+
import ParameterInput from './ParameterInput';
|
| 6 |
|
| 7 |
interface QuestionParameterFormProps {
|
| 8 |
questionType: QuestionType;
|
|
|
|
| 19 |
onParametersChange({ ...parameters, [key]: value });
|
| 20 |
};
|
| 21 |
|
| 22 |
+
// Get configuration for this question type, fallback to default
|
| 23 |
+
const config = questionTypeConfigs[questionType.id] || defaultQuestionTypeConfig;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
|
| 25 |
+
return (
|
| 26 |
+
<div className="space-y-4">
|
| 27 |
+
{config.parameters.map((paramConfig) => (
|
| 28 |
+
<ParameterInput
|
| 29 |
+
key={paramConfig.key}
|
| 30 |
+
config={paramConfig}
|
| 31 |
+
value={parameters[paramConfig.key]}
|
| 32 |
+
onChange={(value) => updateParameter(paramConfig.key, value)}
|
| 33 |
+
/>
|
| 34 |
+
))}
|
| 35 |
+
</div>
|
| 36 |
+
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
}
|
src/types/questionConfig.ts
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export type ParameterType = 'select' | 'range' | 'input' | 'checkbox';
|
| 2 |
+
|
| 3 |
+
export interface ParameterOption {
|
| 4 |
+
value: string | number;
|
| 5 |
+
label: string;
|
| 6 |
+
}
|
| 7 |
+
|
| 8 |
+
export interface ParameterConfig {
|
| 9 |
+
key: string;
|
| 10 |
+
label: string;
|
| 11 |
+
type: ParameterType;
|
| 12 |
+
defaultValue?: any;
|
| 13 |
+
options?: ParameterOption[];
|
| 14 |
+
min?: number;
|
| 15 |
+
max?: number;
|
| 16 |
+
step?: number;
|
| 17 |
+
placeholder?: string;
|
| 18 |
+
helpText?: string;
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
export interface QuestionTypeConfig {
|
| 22 |
+
id: string;
|
| 23 |
+
parameters: ParameterConfig[];
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
// Configuration for each question type
|
| 27 |
+
export const questionTypeConfigs: Record<string, QuestionTypeConfig> = {
|
| 28 |
+
'multiple-choice': {
|
| 29 |
+
id: 'multiple-choice',
|
| 30 |
+
parameters: [
|
| 31 |
+
{
|
| 32 |
+
key: 'difficulty',
|
| 33 |
+
label: 'Difficulty Level',
|
| 34 |
+
type: 'select',
|
| 35 |
+
defaultValue: 'intermediate',
|
| 36 |
+
options: [
|
| 37 |
+
{ value: 'beginner', label: 'Beginner' },
|
| 38 |
+
{ value: 'intermediate', label: 'Intermediate' },
|
| 39 |
+
{ value: 'advanced', label: 'Advanced' },
|
| 40 |
+
],
|
| 41 |
+
},
|
| 42 |
+
{
|
| 43 |
+
key: 'numOptions',
|
| 44 |
+
label: 'Number of Options',
|
| 45 |
+
type: 'select',
|
| 46 |
+
defaultValue: 4,
|
| 47 |
+
options: [
|
| 48 |
+
{ value: 3, label: '3 Options' },
|
| 49 |
+
{ value: 4, label: '4 Options' },
|
| 50 |
+
{ value: 5, label: '5 Options' },
|
| 51 |
+
],
|
| 52 |
+
},
|
| 53 |
+
],
|
| 54 |
+
},
|
| 55 |
+
|
| 56 |
+
'cloze': {
|
| 57 |
+
id: 'cloze',
|
| 58 |
+
parameters: [
|
| 59 |
+
{
|
| 60 |
+
key: 'partOfSpeech',
|
| 61 |
+
label: 'Target Part of Speech',
|
| 62 |
+
type: 'select',
|
| 63 |
+
defaultValue: 'any',
|
| 64 |
+
options: [
|
| 65 |
+
{ value: 'any', label: 'Any' },
|
| 66 |
+
{ value: 'noun', label: 'Nouns' },
|
| 67 |
+
{ value: 'verb', label: 'Verbs' },
|
| 68 |
+
{ value: 'adjective', label: 'Adjectives' },
|
| 69 |
+
{ value: 'adverb', label: 'Adverbs' },
|
| 70 |
+
],
|
| 71 |
+
},
|
| 72 |
+
{
|
| 73 |
+
key: 'numBlanks',
|
| 74 |
+
label: 'Number of Blanks',
|
| 75 |
+
type: 'range',
|
| 76 |
+
defaultValue: 1,
|
| 77 |
+
min: 1,
|
| 78 |
+
max: 5,
|
| 79 |
+
step: 1,
|
| 80 |
+
helpText: 'blank(s)',
|
| 81 |
+
},
|
| 82 |
+
],
|
| 83 |
+
},
|
| 84 |
+
|
| 85 |
+
'grammar': {
|
| 86 |
+
id: 'grammar',
|
| 87 |
+
parameters: [
|
| 88 |
+
{
|
| 89 |
+
key: 'difficulty',
|
| 90 |
+
label: 'Difficulty Level',
|
| 91 |
+
type: 'select',
|
| 92 |
+
defaultValue: 'intermediate',
|
| 93 |
+
options: [
|
| 94 |
+
{ value: 'beginner', label: 'Beginner' },
|
| 95 |
+
{ value: 'intermediate', label: 'Intermediate' },
|
| 96 |
+
{ value: 'advanced', label: 'Advanced' },
|
| 97 |
+
],
|
| 98 |
+
},
|
| 99 |
+
{
|
| 100 |
+
key: 'grammarFocus',
|
| 101 |
+
label: 'Grammar Focus',
|
| 102 |
+
type: 'select',
|
| 103 |
+
defaultValue: 'any',
|
| 104 |
+
options: [
|
| 105 |
+
{ value: 'any', label: 'Any Grammar Rule' },
|
| 106 |
+
{ value: 'verb-tenses', label: 'Verb Tenses' },
|
| 107 |
+
{ value: 'subject-verb', label: 'Subject-Verb Agreement' },
|
| 108 |
+
{ value: 'punctuation', label: 'Punctuation' },
|
| 109 |
+
{ value: 'sentence-structure', label: 'Sentence Structure' },
|
| 110 |
+
],
|
| 111 |
+
},
|
| 112 |
+
],
|
| 113 |
+
},
|
| 114 |
+
|
| 115 |
+
'reading-comp': {
|
| 116 |
+
id: 'reading-comp',
|
| 117 |
+
parameters: [
|
| 118 |
+
{
|
| 119 |
+
key: 'difficulty',
|
| 120 |
+
label: 'Difficulty Level',
|
| 121 |
+
type: 'select',
|
| 122 |
+
defaultValue: 'intermediate',
|
| 123 |
+
options: [
|
| 124 |
+
{ value: 'beginner', label: 'Beginner' },
|
| 125 |
+
{ value: 'intermediate', label: 'Intermediate' },
|
| 126 |
+
{ value: 'advanced', label: 'Advanced' },
|
| 127 |
+
],
|
| 128 |
+
},
|
| 129 |
+
{
|
| 130 |
+
key: 'focus',
|
| 131 |
+
label: 'Question Focus',
|
| 132 |
+
type: 'select',
|
| 133 |
+
defaultValue: 'comprehension',
|
| 134 |
+
options: [
|
| 135 |
+
{ value: 'comprehension', label: 'General Comprehension' },
|
| 136 |
+
{ value: 'inference', label: 'Making Inferences' },
|
| 137 |
+
{ value: 'main-idea', label: 'Main Ideas' },
|
| 138 |
+
{ value: 'details', label: 'Supporting Details' },
|
| 139 |
+
{ value: 'vocabulary', label: 'Vocabulary in Context' },
|
| 140 |
+
],
|
| 141 |
+
},
|
| 142 |
+
],
|
| 143 |
+
},
|
| 144 |
+
|
| 145 |
+
'essay': {
|
| 146 |
+
id: 'essay',
|
| 147 |
+
parameters: [
|
| 148 |
+
{
|
| 149 |
+
key: 'difficulty',
|
| 150 |
+
label: 'Difficulty Level',
|
| 151 |
+
type: 'select',
|
| 152 |
+
defaultValue: 'intermediate',
|
| 153 |
+
options: [
|
| 154 |
+
{ value: 'beginner', label: 'Beginner' },
|
| 155 |
+
{ value: 'intermediate', label: 'Intermediate' },
|
| 156 |
+
{ value: 'advanced', label: 'Advanced' },
|
| 157 |
+
],
|
| 158 |
+
},
|
| 159 |
+
{
|
| 160 |
+
key: 'essayType',
|
| 161 |
+
label: 'Essay Type',
|
| 162 |
+
type: 'select',
|
| 163 |
+
defaultValue: 'argumentative',
|
| 164 |
+
options: [
|
| 165 |
+
{ value: 'argumentative', label: 'Argumentative' },
|
| 166 |
+
{ value: 'descriptive', label: 'Descriptive' },
|
| 167 |
+
{ value: 'narrative', label: 'Narrative' },
|
| 168 |
+
{ value: 'expository', label: 'Expository' },
|
| 169 |
+
{ value: 'compare-contrast', label: 'Compare & Contrast' },
|
| 170 |
+
],
|
| 171 |
+
},
|
| 172 |
+
{
|
| 173 |
+
key: 'wordCount',
|
| 174 |
+
label: 'Word Count Range',
|
| 175 |
+
type: 'select',
|
| 176 |
+
defaultValue: '300-500',
|
| 177 |
+
options: [
|
| 178 |
+
{ value: '150-250', label: '150-250 words' },
|
| 179 |
+
{ value: '300-500', label: '300-500 words' },
|
| 180 |
+
{ value: '500-750', label: '500-750 words' },
|
| 181 |
+
{ value: '750-1000', label: '750-1000 words' },
|
| 182 |
+
],
|
| 183 |
+
},
|
| 184 |
+
],
|
| 185 |
+
},
|
| 186 |
+
};
|
| 187 |
+
|
| 188 |
+
// Default configuration for unknown question types
|
| 189 |
+
export const defaultQuestionTypeConfig: QuestionTypeConfig = {
|
| 190 |
+
id: 'default',
|
| 191 |
+
parameters: [
|
| 192 |
+
{
|
| 193 |
+
key: 'difficulty',
|
| 194 |
+
label: 'Difficulty Level',
|
| 195 |
+
type: 'select',
|
| 196 |
+
defaultValue: 'intermediate',
|
| 197 |
+
options: [
|
| 198 |
+
{ value: 'beginner', label: 'Beginner' },
|
| 199 |
+
{ value: 'intermediate', label: 'Intermediate' },
|
| 200 |
+
{ value: 'advanced', label: 'Advanced' },
|
| 201 |
+
],
|
| 202 |
+
},
|
| 203 |
+
],
|
| 204 |
+
};
|