Commit ·
fe844e6
1
Parent(s): 34d6b36
Rearrange article parameters
Browse files
src/app/page.tsx
CHANGED
|
@@ -258,7 +258,7 @@ export default function QuestionBuilder() {
|
|
| 258 |
<div className="flex-1 flex flex-col">
|
| 259 |
{/* Upper Right Panel: Source Article Editor */}
|
| 260 |
<Article
|
| 261 |
-
className="
|
| 262 |
sourceArticle={sourceArticle}
|
| 263 |
onSourceArticleChange={setSourceArticle}
|
| 264 |
isSourceLocked={isSourceLocked}
|
|
@@ -267,7 +267,7 @@ export default function QuestionBuilder() {
|
|
| 267 |
|
| 268 |
{/* Lower Right Panel: Question List */}
|
| 269 |
<QuestionList
|
| 270 |
-
className="
|
| 271 |
questions={questions}
|
| 272 |
questionTypes={QUESTION_TYPES}
|
| 273 |
selectedQuestions={selectedQuestions}
|
|
|
|
| 258 |
<div className="flex-1 flex flex-col">
|
| 259 |
{/* Upper Right Panel: Source Article Editor */}
|
| 260 |
<Article
|
| 261 |
+
className="flex-1"
|
| 262 |
sourceArticle={sourceArticle}
|
| 263 |
onSourceArticleChange={setSourceArticle}
|
| 264 |
isSourceLocked={isSourceLocked}
|
|
|
|
| 267 |
|
| 268 |
{/* Lower Right Panel: Question List */}
|
| 269 |
<QuestionList
|
| 270 |
+
className="flex-1"
|
| 271 |
questions={questions}
|
| 272 |
questionTypes={QUESTION_TYPES}
|
| 273 |
selectedQuestions={selectedQuestions}
|
src/components/article/Article.tsx
CHANGED
|
@@ -7,6 +7,7 @@ import { Checkbox } from '@/components/ui/checkbox';
|
|
| 7 |
import { Label } from '@/components/ui/label';
|
| 8 |
import { Textarea } from '@/components/ui/textarea';
|
| 9 |
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
|
|
| 10 |
|
| 11 |
interface ArticleProps {
|
| 12 |
className?: string;
|
|
@@ -27,6 +28,7 @@ export function Article({
|
|
| 27 |
const [wordCount, setWordCount] = useState(0);
|
| 28 |
const [isModalOpen, setIsModalOpen] = useState(false);
|
| 29 |
const [isGeneratingArticle, setIsGeneratingArticle] = useState(false);
|
|
|
|
| 30 |
|
| 31 |
const updateWordCount = (text: string) => {
|
| 32 |
const words = text.trim().split(/\s+/).length;
|
|
@@ -68,8 +70,8 @@ export function Article({
|
|
| 68 |
};
|
| 69 |
|
| 70 |
return (
|
| 71 |
-
<Card className={`
|
| 72 |
-
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
| 73 |
<CardTitle className="text-lg font-semibold">
|
| 74 |
📄 Source Article
|
| 75 |
</CardTitle>
|
|
@@ -94,16 +96,24 @@ export function Article({
|
|
| 94 |
🔒 Lock
|
| 95 |
</Label>
|
| 96 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
</div>
|
| 98 |
</CardHeader>
|
| 99 |
|
| 100 |
-
<CardContent className="
|
| 101 |
<Textarea
|
| 102 |
value={sourceArticle}
|
| 103 |
onChange={(e) => handleSourceArticleChange(e.target.value)}
|
| 104 |
placeholder="Paste or type your source article here (articles, passages, etc.)..."
|
| 105 |
disabled={isSourceLocked}
|
| 106 |
-
className="w-full h-full resize-none min-h-[
|
| 107 |
/>
|
| 108 |
</CardContent>
|
| 109 |
|
|
@@ -113,6 +123,24 @@ export function Article({
|
|
| 113 |
onGenerate={handleGenerateArticle}
|
| 114 |
isGenerating={isGeneratingArticle}
|
| 115 |
/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
</Card>
|
| 117 |
);
|
| 118 |
}
|
|
|
|
| 7 |
import { Label } from '@/components/ui/label';
|
| 8 |
import { Textarea } from '@/components/ui/textarea';
|
| 9 |
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
| 10 |
+
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
| 11 |
|
| 12 |
interface ArticleProps {
|
| 13 |
className?: string;
|
|
|
|
| 28 |
const [wordCount, setWordCount] = useState(0);
|
| 29 |
const [isModalOpen, setIsModalOpen] = useState(false);
|
| 30 |
const [isGeneratingArticle, setIsGeneratingArticle] = useState(false);
|
| 31 |
+
const [isExpandOpen, setIsExpandOpen] = useState(false);
|
| 32 |
|
| 33 |
const updateWordCount = (text: string) => {
|
| 34 |
const words = text.trim().split(/\s+/).length;
|
|
|
|
| 70 |
};
|
| 71 |
|
| 72 |
return (
|
| 73 |
+
<Card className={`flex flex-col ${className}`}>
|
| 74 |
+
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-4 px-6 pt-2">
|
| 75 |
<CardTitle className="text-lg font-semibold">
|
| 76 |
📄 Source Article
|
| 77 |
</CardTitle>
|
|
|
|
| 96 |
🔒 Lock
|
| 97 |
</Label>
|
| 98 |
</div>
|
| 99 |
+
<Button
|
| 100 |
+
onClick={() => setIsExpandOpen(true)}
|
| 101 |
+
disabled={isSourceLocked}
|
| 102 |
+
variant="outline"
|
| 103 |
+
size="sm"
|
| 104 |
+
>
|
| 105 |
+
⤢ Expand
|
| 106 |
+
</Button>
|
| 107 |
</div>
|
| 108 |
</CardHeader>
|
| 109 |
|
| 110 |
+
<CardContent className="flex-1 px-6 pb-6">
|
| 111 |
<Textarea
|
| 112 |
value={sourceArticle}
|
| 113 |
onChange={(e) => handleSourceArticleChange(e.target.value)}
|
| 114 |
placeholder="Paste or type your source article here (articles, passages, etc.)..."
|
| 115 |
disabled={isSourceLocked}
|
| 116 |
+
className="w-full h-full resize-none min-h-[200px]"
|
| 117 |
/>
|
| 118 |
</CardContent>
|
| 119 |
|
|
|
|
| 123 |
onGenerate={handleGenerateArticle}
|
| 124 |
isGenerating={isGeneratingArticle}
|
| 125 |
/>
|
| 126 |
+
|
| 127 |
+
{/* Full-screen editor dialog for comfortable editing */}
|
| 128 |
+
<Dialog open={isExpandOpen} onOpenChange={setIsExpandOpen}>
|
| 129 |
+
<DialogContent className="w-[98vw] sm:max-w-none md:max-w-[90vw] h-[70vh] max-h-[95vh] p-0 flex flex-col">
|
| 130 |
+
<DialogHeader className="px-6 pt-6 pb-2">
|
| 131 |
+
<DialogTitle>📄 Edit Source Article</DialogTitle>
|
| 132 |
+
</DialogHeader>
|
| 133 |
+
<div className="flex-1 px-6 pb-6">
|
| 134 |
+
<Textarea
|
| 135 |
+
value={sourceArticle}
|
| 136 |
+
onChange={(e) => handleSourceArticleChange(e.target.value)}
|
| 137 |
+
placeholder="Paste or type your source article here (articles, passages, etc.)..."
|
| 138 |
+
disabled={isSourceLocked}
|
| 139 |
+
className="w-full h-full resize-none"
|
| 140 |
+
/>
|
| 141 |
+
</div>
|
| 142 |
+
</DialogContent>
|
| 143 |
+
</Dialog>
|
| 144 |
</Card>
|
| 145 |
);
|
| 146 |
}
|
src/components/article/ArticleGenerationModal.tsx
CHANGED
|
@@ -132,15 +132,32 @@ export function ArticleGenerationModal({
|
|
| 132 |
|
| 133 |
<ScrollArea className="h-[calc(90vh-200px)] pr-4">
|
| 134 |
<form onSubmit={handleSubmit} className="space-y-6">
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
|
|
|
|
|
|
|
|
|
| 143 |
/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
<CheckboxField
|
| 145 |
label="學生年級 (Grade Level)"
|
| 146 |
fieldName="grade"
|
|
@@ -149,41 +166,17 @@ export function ArticleGenerationModal({
|
|
| 149 |
options={GRADE_OPTIONS}
|
| 150 |
columns={1}
|
| 151 |
/>
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
options={UNIT_OPTIONS}
|
| 160 |
-
columns={3}
|
| 161 |
-
/>
|
| 162 |
-
|
| 163 |
-
<div>
|
| 164 |
-
<Label htmlFor="textbook-vocab" className="mb-2">
|
| 165 |
-
課本單字列表 (Textbook Vocabulary)
|
| 166 |
-
</Label>
|
| 167 |
-
<Textarea
|
| 168 |
-
id="textbook-vocab"
|
| 169 |
-
value={formData.textbookVocab}
|
| 170 |
-
readOnly
|
| 171 |
-
placeholder="Select units to see vocabulary"
|
| 172 |
-
className="bg-muted"
|
| 173 |
-
/>
|
| 174 |
-
</div>
|
| 175 |
-
<div>
|
| 176 |
-
<Label htmlFor="additional-vocab" className="mb-2">
|
| 177 |
-
額外單字列表 (Additional Vocabulary)
|
| 178 |
-
</Label>
|
| 179 |
-
<Textarea
|
| 180 |
-
id="additional-vocab"
|
| 181 |
-
value={formData.additionalVocab}
|
| 182 |
-
onChange={(e) => handleTextChange('additionalVocab', e.target.value)}
|
| 183 |
-
placeholder="Enter additional vocabulary words, separated by commas"
|
| 184 |
/>
|
| 185 |
</div>
|
| 186 |
|
|
|
|
| 187 |
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
| 188 |
<CheckboxField
|
| 189 |
label="文法範圍 (Grammar Focus)"
|
|
@@ -203,16 +196,30 @@ export function ArticleGenerationModal({
|
|
| 203 |
/>
|
| 204 |
</div>
|
| 205 |
|
|
|
|
| 206 |
<div>
|
| 207 |
-
<Label htmlFor="
|
| 208 |
-
|
| 209 |
</Label>
|
| 210 |
<Textarea
|
| 211 |
-
id="
|
| 212 |
-
value={formData.
|
| 213 |
-
|
| 214 |
-
placeholder="
|
| 215 |
-
className="
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 216 |
/>
|
| 217 |
</div>
|
| 218 |
</form>
|
|
|
|
| 132 |
|
| 133 |
<ScrollArea className="h-[calc(90vh-200px)] pr-4">
|
| 134 |
<form onSubmit={handleSubmit} className="space-y-6">
|
| 135 |
+
{/* 初始文章 */}
|
| 136 |
+
<div>
|
| 137 |
+
<Label htmlFor="input-article" className="mb-2">
|
| 138 |
+
初始文章 (Initial Article)
|
| 139 |
+
</Label>
|
| 140 |
+
<Textarea
|
| 141 |
+
id="input-article"
|
| 142 |
+
value={formData.inputArticle}
|
| 143 |
+
onChange={(e) => handleTextChange('inputArticle', e.target.value)}
|
| 144 |
+
placeholder="Enter an initial article or topic to base the generation on..."
|
| 145 |
+
className="min-h-[120px]"
|
| 146 |
/>
|
| 147 |
+
</div>
|
| 148 |
+
|
| 149 |
+
{/* 出版社 */}
|
| 150 |
+
<CheckboxField
|
| 151 |
+
label="出版社 (Publisher)"
|
| 152 |
+
fieldName="publisher"
|
| 153 |
+
value={formData.publisher}
|
| 154 |
+
onChange={(value: string) => handleCheckboxChange('publisher', value)}
|
| 155 |
+
options={PUBLISHER_OPTIONS}
|
| 156 |
+
columns={1}
|
| 157 |
+
/>
|
| 158 |
+
|
| 159 |
+
{/* 學生年級 + 課程範圍 */}
|
| 160 |
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
| 161 |
<CheckboxField
|
| 162 |
label="學生年級 (Grade Level)"
|
| 163 |
fieldName="grade"
|
|
|
|
| 166 |
options={GRADE_OPTIONS}
|
| 167 |
columns={1}
|
| 168 |
/>
|
| 169 |
+
<CheckboxField
|
| 170 |
+
label="課程範圍 (Unit Range)"
|
| 171 |
+
fieldName="unit"
|
| 172 |
+
value={formData.unit}
|
| 173 |
+
onChange={(value: string) => handleCheckboxChange('unit', value)}
|
| 174 |
+
options={UNIT_OPTIONS}
|
| 175 |
+
columns={1}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 176 |
/>
|
| 177 |
</div>
|
| 178 |
|
| 179 |
+
{/* 文法範圍 + 主題範圍 */}
|
| 180 |
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
| 181 |
<CheckboxField
|
| 182 |
label="文法範圍 (Grammar Focus)"
|
|
|
|
| 196 |
/>
|
| 197 |
</div>
|
| 198 |
|
| 199 |
+
{/* 單字列表 */}
|
| 200 |
<div>
|
| 201 |
+
<Label htmlFor="textbook-vocab" className="mb-2">
|
| 202 |
+
課本單字列表 (Textbook Vocabulary)
|
| 203 |
</Label>
|
| 204 |
<Textarea
|
| 205 |
+
id="textbook-vocab"
|
| 206 |
+
value={formData.textbookVocab}
|
| 207 |
+
readOnly
|
| 208 |
+
placeholder="Select units to see vocabulary"
|
| 209 |
+
className="bg-muted"
|
| 210 |
+
/>
|
| 211 |
+
</div>
|
| 212 |
+
|
| 213 |
+
{/* 額外單字列表 */}
|
| 214 |
+
<div>
|
| 215 |
+
<Label htmlFor="additional-vocab" className="mb-2">
|
| 216 |
+
額外單字列表 (Additional Vocabulary)
|
| 217 |
+
</Label>
|
| 218 |
+
<Textarea
|
| 219 |
+
id="additional-vocab"
|
| 220 |
+
value={formData.additionalVocab}
|
| 221 |
+
onChange={(e) => handleTextChange('additionalVocab', e.target.value)}
|
| 222 |
+
placeholder="Enter additional vocabulary words, separated by commas"
|
| 223 |
/>
|
| 224 |
</div>
|
| 225 |
</form>
|