@umituz/react-native-ai-generation-content 1.17.32 → 1.17.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/domains/creations/presentation/components/FilterChips.tsx +2 -1
- package/src/domains/flashcard-generation/FlashcardGenerationService.ts +326 -0
- package/src/features/text-to-image/domain/constants/options.constants.ts +13 -3
- package/src/presentation/components/selectors/factories/aspect-ratio.factory.ts +56 -0
- package/src/presentation/components/selectors/factories/duration.factory.ts +33 -0
- package/src/presentation/components/selectors/factories/index.ts +8 -0
- package/src/presentation/components/selectors/factories/style.factory.ts +47 -0
- package/src/presentation/components/selectors/index.ts +2 -0
- package/src/presentation/hooks/useFlashcardGeneration.ts +154 -0
package/package.json
CHANGED
|
@@ -58,7 +58,8 @@ export function FilterChips({
|
|
|
58
58
|
[tokens],
|
|
59
59
|
);
|
|
60
60
|
|
|
61
|
-
const
|
|
61
|
+
const safeAvailableTypes = availableTypes ?? [];
|
|
62
|
+
const visibleTypes = types.filter((t) => safeAvailableTypes.includes(t.id));
|
|
62
63
|
|
|
63
64
|
return (
|
|
64
65
|
<View style={[styles.container, style]}>
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flashcard Generation Service
|
|
3
|
+
* AI-powered flashcard generation for educational content
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
GenerationRequest,
|
|
8
|
+
GenerationResult,
|
|
9
|
+
GenerationStatus,
|
|
10
|
+
PhotoGenerationInput,
|
|
11
|
+
DirectExecutionResult,
|
|
12
|
+
} from "../domain/entities";
|
|
13
|
+
|
|
14
|
+
export interface FlashcardGenerationRequest {
|
|
15
|
+
topic: string;
|
|
16
|
+
difficulty: "beginner" | "intermediate" | "advanced";
|
|
17
|
+
count: number;
|
|
18
|
+
language?: string;
|
|
19
|
+
format?: "qa" | "definition" | "fill_blank" | "multiple_choice";
|
|
20
|
+
context?: string;
|
|
21
|
+
tags?: string[];
|
|
22
|
+
includeImages?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface GeneratedFlashcard {
|
|
26
|
+
id: string;
|
|
27
|
+
front: string;
|
|
28
|
+
back: string;
|
|
29
|
+
difficulty: "easy" | "medium" | "hard";
|
|
30
|
+
tags: string[];
|
|
31
|
+
source: "ai_generated";
|
|
32
|
+
generationRequest: FlashcardGenerationRequest;
|
|
33
|
+
confidence: number; // 0-1 score
|
|
34
|
+
createdAt?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface FlashcardGenerationResult {
|
|
38
|
+
success: boolean;
|
|
39
|
+
flashcards: GeneratedFlashcard[];
|
|
40
|
+
creditsUsed: number;
|
|
41
|
+
tokensUsed: number;
|
|
42
|
+
processingTime: number; // milliseconds
|
|
43
|
+
error?: string;
|
|
44
|
+
requestId: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class FlashcardGenerationService {
|
|
48
|
+
private static instance: FlashcardGenerationService;
|
|
49
|
+
|
|
50
|
+
static getInstance(): FlashcardGenerationService {
|
|
51
|
+
if (!FlashcardGenerationService.instance) {
|
|
52
|
+
FlashcardGenerationService.instance = new FlashcardGenerationService();
|
|
53
|
+
}
|
|
54
|
+
return FlashcardGenerationService.instance;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Generate flashcards using AI
|
|
59
|
+
*/
|
|
60
|
+
async generateFlashcards(
|
|
61
|
+
request: FlashcardGenerationRequest,
|
|
62
|
+
): Promise<FlashcardGenerationResult> {
|
|
63
|
+
try {
|
|
64
|
+
const startTime = Date.now();
|
|
65
|
+
|
|
66
|
+
// Create AI generation prompt
|
|
67
|
+
const prompt = this.buildFlashcardPrompt(request);
|
|
68
|
+
|
|
69
|
+
// Use the AI generation orchestrator
|
|
70
|
+
const generationRequest: GenerationRequest = {
|
|
71
|
+
prompt,
|
|
72
|
+
type: "text_to_text" as any,
|
|
73
|
+
options: {
|
|
74
|
+
maxTokens: this.calculateMaxTokens(request.count),
|
|
75
|
+
temperature: 0.7,
|
|
76
|
+
language: request.language || "en",
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const result = await this.executeGeneration(generationRequest);
|
|
81
|
+
|
|
82
|
+
// Parse AI response into flashcards
|
|
83
|
+
const flashcards = this.parseFlashcardsFromResult(result, request);
|
|
84
|
+
const processingTime = Date.now() - startTime;
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
success: true,
|
|
88
|
+
flashcards,
|
|
89
|
+
creditsUsed: request.count * 2, // 2 credits per flashcard
|
|
90
|
+
tokensUsed: result.metadata?.tokensUsed || 0,
|
|
91
|
+
processingTime,
|
|
92
|
+
requestId: result.jobId || `req_${Date.now()}`,
|
|
93
|
+
};
|
|
94
|
+
} catch (error) {
|
|
95
|
+
return {
|
|
96
|
+
success: false,
|
|
97
|
+
flashcards: [],
|
|
98
|
+
creditsUsed: 0,
|
|
99
|
+
tokensUsed: 0,
|
|
100
|
+
processingTime: 0,
|
|
101
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
102
|
+
requestId: "",
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Validate generated flashcard content
|
|
109
|
+
*/
|
|
110
|
+
async validateFlashcard(
|
|
111
|
+
front: string,
|
|
112
|
+
back: string,
|
|
113
|
+
): Promise<{
|
|
114
|
+
accuracy: number;
|
|
115
|
+
relevance: number;
|
|
116
|
+
clarity: number;
|
|
117
|
+
completeness: number;
|
|
118
|
+
overall: number;
|
|
119
|
+
}> {
|
|
120
|
+
// Simple validation heuristic
|
|
121
|
+
const frontLength = front.length;
|
|
122
|
+
const backLength = back.length;
|
|
123
|
+
|
|
124
|
+
const accuracy = this.calculateAccuracy(front, back);
|
|
125
|
+
const relevance = this.calculateRelevance(front, back);
|
|
126
|
+
const clarity = this.calculateClarity(front, back);
|
|
127
|
+
const completeness = this.calculateCompleteness(front, back);
|
|
128
|
+
const overall = (accuracy + relevance + clarity + completeness) / 4;
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
accuracy,
|
|
132
|
+
relevance,
|
|
133
|
+
clarity,
|
|
134
|
+
completeness,
|
|
135
|
+
overall,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private buildFlashcardPrompt(request: FlashcardGenerationRequest): string {
|
|
140
|
+
const qualityMap = {
|
|
141
|
+
beginner:
|
|
142
|
+
"simple, clear language appropriate for learners just starting out",
|
|
143
|
+
intermediate:
|
|
144
|
+
"moderate complexity with some technical terms expected to be known",
|
|
145
|
+
advanced:
|
|
146
|
+
"complex content with specialized terminology and nuanced concepts",
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const formatInstructions = {
|
|
150
|
+
qa: "Format as Question-Answer pairs",
|
|
151
|
+
definition: "Format as Term-Definition pairs",
|
|
152
|
+
fill_blank: "Format as Fill-in-the-blank exercises",
|
|
153
|
+
multiple_choice:
|
|
154
|
+
"Format as Multiple Choice questions with one correct answer",
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
return `Generate ${request.count} educational flashcards about "${request.topic}".
|
|
158
|
+
|
|
159
|
+
Topic Context: ${request.context || "General learning"}
|
|
160
|
+
Difficulty Level: ${request.difficulty} - ${qualityMap[request.difficulty]}
|
|
161
|
+
Format: ${request.format || "qa"} - ${formatInstructions[request.format || "qa"]}
|
|
162
|
+
Language: ${request.language || "English"}
|
|
163
|
+
Tags to include: ${request.tags?.join(", ") || "auto-generated"}
|
|
164
|
+
|
|
165
|
+
Requirements:
|
|
166
|
+
- Questions should be clear and concise
|
|
167
|
+
- Answers should be accurate and comprehensive
|
|
168
|
+
- Content should be age and difficulty appropriate
|
|
169
|
+
- Include relevant educational context
|
|
170
|
+
- Make it engaging and memorable
|
|
171
|
+
|
|
172
|
+
Output format: JSON array with structure:
|
|
173
|
+
[
|
|
174
|
+
{
|
|
175
|
+
"front": "Question text here",
|
|
176
|
+
"back": "Answer text here",
|
|
177
|
+
"difficulty": "easy|medium|hard",
|
|
178
|
+
"tags": ["tag1", "tag2"]
|
|
179
|
+
}
|
|
180
|
+
]`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private calculateMaxTokens(count: number): number {
|
|
184
|
+
// Estimate ~50 tokens per flashcard + overhead
|
|
185
|
+
return Math.max(count * 50, 200);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private async executeGeneration(request: GenerationRequest): Promise<any> {
|
|
189
|
+
// This would integrate with the actual AI generation orchestrator
|
|
190
|
+
// For now, return mock result
|
|
191
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
success: true,
|
|
195
|
+
result: this.generateMockContent(request.options?.maxTokens || 200),
|
|
196
|
+
metadata: {
|
|
197
|
+
tokensUsed: request.options?.maxTokens || 200,
|
|
198
|
+
processingTime: 2000,
|
|
199
|
+
},
|
|
200
|
+
jobId: `job_${Date.now()}`,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private generateMockContent(maxTokens: number): string {
|
|
205
|
+
const mockFlashcards = [
|
|
206
|
+
{
|
|
207
|
+
front: "What is photosynthesis?",
|
|
208
|
+
back: "The process by which plants convert sunlight, water, and carbon dioxide into glucose and oxygen.",
|
|
209
|
+
difficulty: "medium",
|
|
210
|
+
tags: ["biology", "science", "plants"],
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
front: "Define gravity",
|
|
214
|
+
back: "A fundamental force that attracts objects with mass toward each other.",
|
|
215
|
+
difficulty: "easy",
|
|
216
|
+
tags: ["physics", "science", "forces"],
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
front: "What is the formula for water?",
|
|
220
|
+
back: "H₂O",
|
|
221
|
+
difficulty: "easy",
|
|
222
|
+
tags: ["chemistry", "science", "molecules"],
|
|
223
|
+
},
|
|
224
|
+
];
|
|
225
|
+
|
|
226
|
+
return JSON.stringify(mockFlashcards.slice(0, Math.floor(maxTokens / 100)));
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
private parseFlashcardsFromResult(
|
|
230
|
+
result: any,
|
|
231
|
+
request: FlashcardGenerationRequest,
|
|
232
|
+
): GeneratedFlashcard[] {
|
|
233
|
+
try {
|
|
234
|
+
let flashcards: any[];
|
|
235
|
+
|
|
236
|
+
if (typeof result.result === "string") {
|
|
237
|
+
flashcards = JSON.parse(result.result);
|
|
238
|
+
} else if (Array.isArray(result.result)) {
|
|
239
|
+
flashcards = result.result;
|
|
240
|
+
} else {
|
|
241
|
+
throw new Error("Invalid AI response format");
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return flashcards.map((item, index) => ({
|
|
245
|
+
id: `generated_${Date.now()}_${index}`,
|
|
246
|
+
front: item.front || "",
|
|
247
|
+
back: item.back || "",
|
|
248
|
+
difficulty: item.difficulty || "medium",
|
|
249
|
+
tags: Array.isArray(item.tags)
|
|
250
|
+
? item.tags
|
|
251
|
+
: item.tags
|
|
252
|
+
? [item.tags]
|
|
253
|
+
: [],
|
|
254
|
+
source: "ai_generated" as const,
|
|
255
|
+
generationRequest: request,
|
|
256
|
+
confidence: 0.8 + Math.random() * 0.2, // 0.8-1.0
|
|
257
|
+
createdAt: new Date().toISOString(),
|
|
258
|
+
}));
|
|
259
|
+
} catch (error) {
|
|
260
|
+
console.error("Failed to parse AI response:", error);
|
|
261
|
+
return [];
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
private calculateAccuracy(front: string, back: string): number {
|
|
266
|
+
// Simple heuristics for accuracy assessment
|
|
267
|
+
let score = 0.5; // Base score
|
|
268
|
+
|
|
269
|
+
// Check for reasonable length
|
|
270
|
+
if (front.length >= 5 && front.length <= 200) score += 0.2;
|
|
271
|
+
if (back.length >= 10 && back.length <= 500) score += 0.2;
|
|
272
|
+
|
|
273
|
+
// Check for balanced content
|
|
274
|
+
const frontWords = front.split(/\s+/).length;
|
|
275
|
+
const backWords = back.split(/\s+/).length;
|
|
276
|
+
if (backWords >= frontWords * 0.5 && backWords <= frontWords * 3)
|
|
277
|
+
score += 0.1;
|
|
278
|
+
|
|
279
|
+
return Math.min(score, 1.0);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
private calculateRelevance(front: string, back: string): number {
|
|
283
|
+
// Simple relevance check
|
|
284
|
+
let score = 0.6; // Base score
|
|
285
|
+
|
|
286
|
+
// Check for educational content indicators
|
|
287
|
+
const educationalTerms = [
|
|
288
|
+
"define",
|
|
289
|
+
"explain",
|
|
290
|
+
"describe",
|
|
291
|
+
"what is",
|
|
292
|
+
"how does",
|
|
293
|
+
"formula",
|
|
294
|
+
"process",
|
|
295
|
+
"function",
|
|
296
|
+
];
|
|
297
|
+
const hasEducationalTerms = educationalTerms.some(
|
|
298
|
+
(term) =>
|
|
299
|
+
front.toLowerCase().includes(term) || back.toLowerCase().includes(term),
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
if (hasEducationalTerms) score += 0.3;
|
|
303
|
+
if (front.includes("?") || front.toLowerCase().includes("what is"))
|
|
304
|
+
score += 0.1;
|
|
305
|
+
|
|
306
|
+
return Math.min(score, 1.0);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
private calculateClarity(front: string, back: string): number {
|
|
310
|
+
let score = 0.5; // Base score
|
|
311
|
+
|
|
312
|
+
// Check for clear structure
|
|
313
|
+
if (front.trim().endsWith("?")) score += 0.2;
|
|
314
|
+
if (!front.includes("...") && !back.includes("...")) score += 0.2;
|
|
315
|
+
if (!/[A-Z]{2,}/.test(front)) score += 0.1; // Not too many caps
|
|
316
|
+
|
|
317
|
+
return Math.min(score, 1.0);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
private calculateCompleteness(front: string, back: string): number {
|
|
321
|
+
const frontScore = Math.min(front.length / 20, 1.0); // Ideal 20 chars
|
|
322
|
+
const backScore = Math.min(back.length / 50, 1.0); // Ideal 50 chars
|
|
323
|
+
|
|
324
|
+
return (frontScore + backScore) / 2;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
@@ -13,12 +13,19 @@ import type {
|
|
|
13
13
|
|
|
14
14
|
export const DEFAULT_NUM_IMAGES_OPTIONS: NumImages[] = [1, 2, 3, 4];
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* @deprecated Use createAspectRatioOptions factory with translations instead
|
|
18
|
+
* This will be removed in next major version
|
|
19
|
+
*/
|
|
16
20
|
export const DEFAULT_ASPECT_RATIO_OPTIONS: { value: AspectRatio; label: string }[] = [
|
|
17
|
-
{ value: "9:16", label: "
|
|
18
|
-
{ value: "16:9", label: "
|
|
19
|
-
{ value: "1:1", label: "
|
|
21
|
+
{ value: "9:16", label: "9:16" },
|
|
22
|
+
{ value: "16:9", label: "16:9" },
|
|
23
|
+
{ value: "1:1", label: "1:1" },
|
|
20
24
|
];
|
|
21
25
|
|
|
26
|
+
/**
|
|
27
|
+
* @deprecated Apps should provide their own translated labels
|
|
28
|
+
*/
|
|
22
29
|
export const DEFAULT_SIZE_OPTIONS: { value: ImageSize; label: string }[] = [
|
|
23
30
|
{ value: "512x512", label: "512×512" },
|
|
24
31
|
{ value: "768x768", label: "768×768" },
|
|
@@ -27,6 +34,9 @@ export const DEFAULT_SIZE_OPTIONS: { value: ImageSize; label: string }[] = [
|
|
|
27
34
|
{ value: "1792x1024", label: "1792×1024" },
|
|
28
35
|
];
|
|
29
36
|
|
|
37
|
+
/**
|
|
38
|
+
* @deprecated Apps should provide their own translated labels
|
|
39
|
+
*/
|
|
30
40
|
export const DEFAULT_OUTPUT_FORMAT_OPTIONS: { value: OutputFormat; label: string }[] = [
|
|
31
41
|
{ value: "png", label: "PNG" },
|
|
32
42
|
{ value: "jpeg", label: "JPEG" },
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aspect Ratio Factory
|
|
3
|
+
* Creates i18n-ready aspect ratio options
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { AspectRatioOption } from "../types";
|
|
7
|
+
|
|
8
|
+
export interface AspectRatioTranslations {
|
|
9
|
+
landscape: {
|
|
10
|
+
name: string;
|
|
11
|
+
description: string;
|
|
12
|
+
};
|
|
13
|
+
portrait: {
|
|
14
|
+
name: string;
|
|
15
|
+
description: string;
|
|
16
|
+
};
|
|
17
|
+
square: {
|
|
18
|
+
name: string;
|
|
19
|
+
description: string;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Creates aspect ratio options with translations
|
|
25
|
+
* @param translations - Translated labels from app
|
|
26
|
+
* @returns Array of aspect ratio options
|
|
27
|
+
*/
|
|
28
|
+
export const createAspectRatioOptions = (
|
|
29
|
+
translations: AspectRatioTranslations
|
|
30
|
+
): AspectRatioOption[] => [
|
|
31
|
+
{
|
|
32
|
+
id: "16:9",
|
|
33
|
+
name: translations.landscape.name,
|
|
34
|
+
icon: "Monitor",
|
|
35
|
+
description: translations.landscape.description,
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: "9:16",
|
|
39
|
+
name: translations.portrait.name,
|
|
40
|
+
icon: "Smartphone",
|
|
41
|
+
description: translations.portrait.description,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: "1:1",
|
|
45
|
+
name: translations.square.name,
|
|
46
|
+
icon: "Square",
|
|
47
|
+
description: translations.square.description,
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Default aspect ratio IDs (no translation needed)
|
|
53
|
+
*/
|
|
54
|
+
export const ASPECT_RATIO_IDS = ["16:9", "9:16", "1:1"] as const;
|
|
55
|
+
|
|
56
|
+
export type AspectRatioId = (typeof ASPECT_RATIO_IDS)[number];
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Duration Factory
|
|
3
|
+
* Creates i18n-ready duration options
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { DurationValue } from "../types";
|
|
7
|
+
|
|
8
|
+
export interface DurationOption {
|
|
9
|
+
value: DurationValue;
|
|
10
|
+
label: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Creates duration options with translations
|
|
15
|
+
* @param durations - Array of duration values in seconds
|
|
16
|
+
* @param formatLabel - Function to format duration label (e.g., "4s", "8 seconds")
|
|
17
|
+
* @returns Array of duration options
|
|
18
|
+
*/
|
|
19
|
+
export const createDurationOptions = (
|
|
20
|
+
durations: readonly DurationValue[],
|
|
21
|
+
formatLabel: (seconds: DurationValue) => string
|
|
22
|
+
): DurationOption[] =>
|
|
23
|
+
durations.map((duration) => ({
|
|
24
|
+
value: duration,
|
|
25
|
+
label: formatLabel(duration),
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Common duration values (seconds)
|
|
30
|
+
*/
|
|
31
|
+
export const COMMON_DURATIONS = [4, 8, 12, 16] as const;
|
|
32
|
+
|
|
33
|
+
export type CommonDuration = (typeof COMMON_DURATIONS)[number];
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Style Factory
|
|
3
|
+
* Creates i18n-ready style options
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { StyleOption } from "../types";
|
|
7
|
+
|
|
8
|
+
export interface StyleTranslation {
|
|
9
|
+
name: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type StyleTranslations = Record<string, StyleTranslation>;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Creates style options with translations
|
|
17
|
+
* @param styleIds - Array of style IDs
|
|
18
|
+
* @param translations - Map of style ID to translations
|
|
19
|
+
* @param getThumbnail - Optional function to get thumbnail URL for style
|
|
20
|
+
* @returns Array of style options
|
|
21
|
+
*/
|
|
22
|
+
export const createStyleOptions = (
|
|
23
|
+
styleIds: readonly string[],
|
|
24
|
+
translations: StyleTranslations,
|
|
25
|
+
getThumbnail?: (styleId: string) => string | undefined
|
|
26
|
+
): StyleOption[] =>
|
|
27
|
+
styleIds.map((id) => ({
|
|
28
|
+
id,
|
|
29
|
+
name: translations[id]?.name || id,
|
|
30
|
+
description: translations[id]?.description,
|
|
31
|
+
thumbnail: getThumbnail?.(id),
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Helper to create style options from array of style configs
|
|
36
|
+
*/
|
|
37
|
+
export const createStyleOptionsFromConfig = <T extends { id: string }>(
|
|
38
|
+
styles: readonly T[],
|
|
39
|
+
translations: StyleTranslations,
|
|
40
|
+
getThumbnail?: (style: T) => string | undefined
|
|
41
|
+
): StyleOption[] =>
|
|
42
|
+
styles.map((style) => ({
|
|
43
|
+
id: style.id,
|
|
44
|
+
name: translations[style.id]?.name || style.id,
|
|
45
|
+
description: translations[style.id]?.description,
|
|
46
|
+
thumbnail: getThumbnail?.(style),
|
|
47
|
+
}));
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flashcard Generation Hooks
|
|
3
|
+
* React hooks for AI-powered flashcard generation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
import type {
|
|
8
|
+
FlashcardGenerationRequest,
|
|
9
|
+
GeneratedFlashcard,
|
|
10
|
+
FlashcardGenerationResult,
|
|
11
|
+
} from "../domains/flashcard-generation/FlashcardGenerationService";
|
|
12
|
+
|
|
13
|
+
export interface UseFlashcardGenerationResult {
|
|
14
|
+
generateFlashcards: (
|
|
15
|
+
request: FlashcardGenerationRequest,
|
|
16
|
+
) => Promise<FlashcardGenerationResult>;
|
|
17
|
+
isGenerating: boolean;
|
|
18
|
+
result: FlashcardGenerationResult | null;
|
|
19
|
+
error: string | null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const useFlashcardGeneration = (): UseFlashcardGenerationResult => {
|
|
23
|
+
const [isGenerating, setIsGenerating] = React.useState(false);
|
|
24
|
+
const [result, setResult] = React.useState<FlashcardGenerationResult | null>(
|
|
25
|
+
null,
|
|
26
|
+
);
|
|
27
|
+
const [error, setError] = React.useState<string | null>(null);
|
|
28
|
+
|
|
29
|
+
const generateFlashcards = React.useCallback(
|
|
30
|
+
async (
|
|
31
|
+
request: FlashcardGenerationRequest,
|
|
32
|
+
): Promise<FlashcardGenerationResult> => {
|
|
33
|
+
try {
|
|
34
|
+
setIsGenerating(true);
|
|
35
|
+
setError(null);
|
|
36
|
+
|
|
37
|
+
// This would use the actual FlashcardGenerationService
|
|
38
|
+
// For now, mock the implementation
|
|
39
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
40
|
+
|
|
41
|
+
const flashcards: GeneratedFlashcard[] = [];
|
|
42
|
+
for (let i = 0; i < request.count; i++) {
|
|
43
|
+
flashcards.push({
|
|
44
|
+
id: `generated_${Date.now()}_${i}`,
|
|
45
|
+
front: `Mock question ${i + 1} about ${request.topic}`,
|
|
46
|
+
back: `Mock answer ${i + 1} for ${request.topic}`,
|
|
47
|
+
difficulty: i % 3 === 0 ? "easy" : i % 3 === 1 ? "medium" : "hard",
|
|
48
|
+
tags: [
|
|
49
|
+
...(request.tags || []),
|
|
50
|
+
request.topic.toLowerCase().replace(/\s+/g, "_"),
|
|
51
|
+
],
|
|
52
|
+
source: "ai_generated",
|
|
53
|
+
generationRequest: request,
|
|
54
|
+
confidence: 0.8 + Math.random() * 0.2,
|
|
55
|
+
createdAt: new Date().toISOString(),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const generationResult: FlashcardGenerationResult = {
|
|
60
|
+
success: true,
|
|
61
|
+
flashcards,
|
|
62
|
+
creditsUsed: request.count * 2,
|
|
63
|
+
tokensUsed: request.count * 100,
|
|
64
|
+
processingTime: 2000,
|
|
65
|
+
requestId: `req_${Date.now()}`,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
setResult(generationResult);
|
|
69
|
+
return generationResult;
|
|
70
|
+
} catch (err) {
|
|
71
|
+
const errorMessage =
|
|
72
|
+
err instanceof Error ? err.message : "Generation failed";
|
|
73
|
+
setError(errorMessage);
|
|
74
|
+
setIsGenerating(false);
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
success: false,
|
|
78
|
+
flashcards: [],
|
|
79
|
+
creditsUsed: 0,
|
|
80
|
+
tokensUsed: 0,
|
|
81
|
+
processingTime: 0,
|
|
82
|
+
error: errorMessage,
|
|
83
|
+
requestId: `req_${Date.now()}`,
|
|
84
|
+
};
|
|
85
|
+
} finally {
|
|
86
|
+
setIsGenerating(false);
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
[],
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const reset = React.useCallback(() => {
|
|
93
|
+
setResult(null);
|
|
94
|
+
setError(null);
|
|
95
|
+
setIsGenerating(false);
|
|
96
|
+
}, []);
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
generateFlashcards,
|
|
100
|
+
isGenerating,
|
|
101
|
+
result,
|
|
102
|
+
error,
|
|
103
|
+
reset,
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export interface UseFlashcardValidationResult {
|
|
108
|
+
validateFlashcard: (
|
|
109
|
+
front: string,
|
|
110
|
+
back: string,
|
|
111
|
+
) => Promise<{
|
|
112
|
+
accuracy: number;
|
|
113
|
+
relevance: number;
|
|
114
|
+
clarity: number;
|
|
115
|
+
completeness: number;
|
|
116
|
+
overall: number;
|
|
117
|
+
}>;
|
|
118
|
+
isValidating: boolean;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export const useFlashcardValidation = (): UseFlashcardValidationResult => {
|
|
122
|
+
const [isValidating, setIsValidating] = React.useState(false);
|
|
123
|
+
|
|
124
|
+
const validateFlashcard = React.useCallback(
|
|
125
|
+
async (front: string, back: string) => {
|
|
126
|
+
setIsValidating(true);
|
|
127
|
+
|
|
128
|
+
// Mock validation - in production would use actual service
|
|
129
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
130
|
+
|
|
131
|
+
const accuracy = Math.min(1, front.length / 10);
|
|
132
|
+
const relevance = Math.min(1, back.length / 25);
|
|
133
|
+
const clarity = Math.min(1, 1 - front.split(/\s+/).length / 20);
|
|
134
|
+
const completeness = Math.min(1, back.length / 50);
|
|
135
|
+
const overall = (accuracy + relevance + clarity + completeness) / 4;
|
|
136
|
+
|
|
137
|
+
setIsValidating(false);
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
accuracy,
|
|
141
|
+
relevance,
|
|
142
|
+
clarity,
|
|
143
|
+
completeness,
|
|
144
|
+
overall,
|
|
145
|
+
};
|
|
146
|
+
},
|
|
147
|
+
[],
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
validateFlashcard,
|
|
152
|
+
isValidating,
|
|
153
|
+
};
|
|
154
|
+
};
|