Yu Chen commited on
Commit
4567d57
·
1 Parent(s): 70cd4ae

update the csv corpus to generate / rewrite article

Browse files
public/middle_school_english_vocabulary_overview.csv ADDED
The diff for this file is too large to render. See raw diff
 
src/app/api/article-rewrite/route.ts ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { openai } from '@ai-sdk/openai';
2
+ import { generateText } from 'ai';
3
+ import { langfuse } from '@/lib/langfuse';
4
+ import { AI_CONFIG } from '@/config/ai';
5
+
6
+ export async function POST(req: Request) {
7
+ try {
8
+ const {
9
+ grade,
10
+ unit,
11
+ textbookVocab,
12
+ additionalVocab,
13
+ grammar,
14
+ topic,
15
+ inputArticle,
16
+ } = await req.json();
17
+
18
+ const langfusePrompt = await langfuse.prompt.get('article/rewrite');
19
+
20
+ const prompt = langfusePrompt.prompt
21
+ .replace(/\{\{grade_values\}\}/g, Array.isArray(grade) ? grade.join(', ') : grade)
22
+ .replace(/\{\{unit_values\}\}/g, Array.isArray(unit) ? unit.join(', ') : unit)
23
+ .replace(/\{\{textbook_vocab_values\}\}/g, textbookVocab || '')
24
+ .replace(/\{\{additional_vocab_values\}\}/g, additionalVocab || '')
25
+ .replace(/\{\{grammar_values\}\}/g, Array.isArray(grammar) ? grammar.join(', ') : grammar)
26
+ .replace(/\{\{topic_values\}\}/g, Array.isArray(topic) ? topic.join(', ') : topic)
27
+ .replace(/\{\{input_article_value\}\}/g, inputArticle || '');
28
+
29
+ const result = await generateText({
30
+ model: openai(AI_CONFIG.model),
31
+ prompt,
32
+ });
33
+
34
+ return Response.json({
35
+ article: result.text,
36
+ createdAt: new Date().toISOString(),
37
+ });
38
+ } catch (error) {
39
+ console.error('Error rewriting article:', error);
40
+ return Response.json({ error: 'Failed to rewrite article' }, { status: 500 });
41
+ }
42
+ }
43
+
44
+
src/app/api/vocab/route.ts ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ type VocabRequest = {
5
+ publisher: string[];
6
+ grade: string[];
7
+ unit: string[];
8
+ };
9
+
10
+ // Map UI labels to CSV values
11
+ function mapGradeToCsv(uiGrade: string): string {
12
+ const map: Record<string, string> = {
13
+ '七年級上學期': '七上',
14
+ '七年級下學期': '七下',
15
+ '八年級上學期': '八上',
16
+ '八年級下學期': '八下',
17
+ '九年級上學期': '九上',
18
+ '九年級下學期': '九下',
19
+ };
20
+ return map[uiGrade] ?? uiGrade;
21
+ }
22
+
23
+ function mapUnitToCsv(uiUnit: string): string {
24
+ const normalized = uiUnit.replace(/\s+/g, '');
25
+ const map: Record<string, string> = {
26
+ '第一課': 'lesson 1',
27
+ '第二課': 'lesson 2',
28
+ '第三課': 'lesson 3',
29
+ '第四課': 'lesson 4',
30
+ '第五課': 'lesson 5',
31
+ '第六課': 'lesson 6',
32
+ };
33
+ return map[normalized] ?? uiUnit;
34
+ }
35
+
36
+ function parseCSVLine(line: string): string[] {
37
+ const result: string[] = [];
38
+ let current = '';
39
+ let inQuotes = false;
40
+ let i = 0;
41
+
42
+ while (i < line.length) {
43
+ const char = line[i];
44
+
45
+ if (char === '"') {
46
+ if (inQuotes && line[i + 1] === '"') {
47
+ current += '"';
48
+ i += 2;
49
+ continue;
50
+ } else {
51
+ inQuotes = !inQuotes;
52
+ }
53
+ } else if (char === ',' && !inQuotes) {
54
+ result.push(current.trim());
55
+ current = '';
56
+ } else {
57
+ current += char;
58
+ }
59
+
60
+ i++;
61
+ }
62
+
63
+ result.push(current.trim());
64
+ return result;
65
+ }
66
+
67
+ function parseCSVEntry(lines: string[], startIndex: number): { values: string[]; nextIndex: number } {
68
+ let currentLine = startIndex;
69
+ let fullLine = '';
70
+ let openQuotes = 0;
71
+
72
+ while (currentLine < lines.length) {
73
+ const line = lines[currentLine];
74
+ fullLine += (fullLine ? '\n' : '') + line;
75
+
76
+ for (const ch of line) {
77
+ if (ch === '"') openQuotes++;
78
+ }
79
+
80
+ if (openQuotes % 2 === 0) {
81
+ break;
82
+ }
83
+
84
+ currentLine++;
85
+ }
86
+
87
+ const values = parseCSVLine(fullLine);
88
+ return { values, nextIndex: currentLine + 1 };
89
+ }
90
+
91
+ export async function POST(req: Request) {
92
+ try {
93
+ const { publisher, grade, unit } = (await req.json()) as VocabRequest;
94
+
95
+ const filePath = path.join(process.cwd(), 'public', 'middle_school_english_vocabulary_overview.csv');
96
+ const csvContent = fs.readFileSync(filePath, 'utf-8');
97
+
98
+ const lines = csvContent.split('\n');
99
+ if (lines.length === 0) {
100
+ return Response.json({ vocab: [], text: '' });
101
+ }
102
+
103
+ // Header indices
104
+ const header = parseCSVLine(lines[0]);
105
+ const idxPublisher = header.indexOf('出版社');
106
+ const idxGrade = header.indexOf('年級');
107
+ const idxUnit = header.indexOf('單元');
108
+ const idxWord = header.indexOf('單字');
109
+
110
+ if (idxPublisher === -1 || idxGrade === -1 || idxUnit === -1 || idxWord === -1) {
111
+ return Response.json({ vocab: [], text: '' });
112
+ }
113
+
114
+ const gradeSet = new Set(grade.map(mapGradeToCsv));
115
+ const unitSet = new Set(unit.map(mapUnitToCsv));
116
+ const publisherSet = new Set(publisher);
117
+
118
+ const vocabSet = new Set<string>();
119
+
120
+ let i = 1;
121
+ while (i < lines.length) {
122
+ if (!lines[i] || !lines[i].trim()) {
123
+ i++;
124
+ continue;
125
+ }
126
+ const entry = parseCSVEntry(lines, i);
127
+ const values = entry.values;
128
+
129
+ const recPublisher = values[idxPublisher];
130
+ const recGrade = values[idxGrade];
131
+ const recUnit = values[idxUnit];
132
+ const recWord = values[idxWord];
133
+
134
+ const matchPublisher = publisherSet.size === 0 || publisherSet.has(recPublisher);
135
+ const matchGrade = gradeSet.size === 0 || gradeSet.has(recGrade);
136
+ const matchUnit = unitSet.size === 0 || unitSet.has(recUnit);
137
+
138
+ if (matchPublisher && matchGrade && matchUnit && recWord) {
139
+ vocabSet.add(recWord);
140
+ }
141
+
142
+ i = entry.nextIndex;
143
+ }
144
+
145
+ const vocab = Array.from(vocabSet);
146
+ const text = vocab.join(', ');
147
+ return Response.json({ vocab, text });
148
+ } catch (error) {
149
+ console.error('Error filtering vocab:', error);
150
+ return Response.json({ vocab: [], text: '' }, { status: 500 });
151
+ }
152
+ }
153
+
154
+
src/components/article/ArticleGenerationModal.tsx CHANGED
@@ -92,13 +92,37 @@ export function ArticleGenerationModal({
92
 
93
  // Update textbook vocabulary when selections change
94
  useEffect(() => {
95
- if (formData.grade.length > 0 && formData.unit.length > 0 && formData.publisher.length > 0) {
96
- // Mock vocabulary update - in real implementation this would call an API
97
- const mockVocab = "apple, book, computer, desk, elephant, friend, green, happy, important, jump";
98
- setFormData(prev => ({ ...prev, textbookVocab: mockVocab }));
99
- } else {
100
- setFormData(prev => ({ ...prev, textbookVocab: '' }));
101
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  }, [formData.grade, formData.unit, formData.publisher]);
103
 
104
  const handleCheckboxChange = (field: keyof ArticleGenerationParams, value: string) => {
 
92
 
93
  // Update textbook vocabulary when selections change
94
  useEffect(() => {
95
+ let cancelled = false;
96
+ const fetchVocab = async () => {
97
+ if (formData.grade.length > 0 && formData.unit.length > 0 && formData.publisher.length > 0) {
98
+ try {
99
+ const res = await fetch('/api/vocab', {
100
+ method: 'POST',
101
+ headers: { 'Content-Type': 'application/json' },
102
+ body: JSON.stringify({
103
+ publisher: formData.publisher,
104
+ grade: formData.grade,
105
+ unit: formData.unit,
106
+ }),
107
+ });
108
+ if (!res.ok) throw new Error('Failed to fetch vocab');
109
+ const data: { vocab: string[]; text: string } = await res.json();
110
+ if (!cancelled) {
111
+ setFormData(prev => ({ ...prev, textbookVocab: data.text }));
112
+ }
113
+ } catch (e) {
114
+ if (!cancelled) {
115
+ setFormData(prev => ({ ...prev, textbookVocab: '' }));
116
+ }
117
+ }
118
+ } else {
119
+ if (!cancelled) {
120
+ setFormData(prev => ({ ...prev, textbookVocab: '' }));
121
+ }
122
+ }
123
+ };
124
+ fetchVocab();
125
+ return () => { cancelled = true; };
126
  }, [formData.grade, formData.unit, formData.publisher]);
127
 
128
  const handleCheckboxChange = (field: keyof ArticleGenerationParams, value: string) => {