@umituz/react-native-ai-generation-content 1.17.86 → 1.17.88
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 +2 -2
- package/src/domains/creations/index.ts +6 -3
- package/src/domains/creations/presentation/components/CreationBadges.tsx +3 -3
- package/src/domains/creations/presentation/components/CreationDetail/DetailStory.tsx +5 -8
- package/src/domains/creations/presentation/components/CreationThumbnail.tsx +1 -1
- package/src/domains/creations/presentation/components/index.ts +0 -3
- package/src/domains/creations/presentation/screens/CreationDetailScreen.tsx +5 -4
- package/src/domains/creations/presentation/screens/CreationsGalleryScreen.tsx +2 -11
- package/src/domains/creations/presentation/utils/filterUtils.ts +18 -1
- package/src/domains/flashcard-generation/FlashcardGenerationService.ts +85 -311
- package/src/domains/flashcard-generation/builders/flashcard-prompt.builder.ts +55 -0
- package/src/domains/flashcard-generation/parsers/flashcard-response.parser.ts +68 -0
- package/src/domains/flashcard-generation/types/flashcard.types.ts +56 -0
- package/src/domains/flashcard-generation/validators/flashcard.validator.ts +67 -0
- package/src/features/image-to-video/presentation/components/MusicMoodSelector.tsx +3 -3
- package/src/features/replace-background/presentation/components/ResultDisplay.tsx +1 -2
- package/src/features/script-generator/presentation/components/ScriptDisplay.tsx +3 -4
- package/src/features/text-to-image/domain/constants/index.ts +3 -3
- package/src/features/text-to-image/domain/constants/options.constants.ts +12 -25
- package/src/features/text-to-image/index.ts +12 -4
- package/src/features/text-to-image/presentation/hooks/index.ts +8 -0
- package/src/features/text-to-image/presentation/hooks/useTextToImageCallbacksBuilder.ts +200 -0
- package/src/features/text-to-video/presentation/components/HeroSection.tsx +2 -8
- package/src/features/text-to-video/presentation/components/OptionsPanel.tsx +3 -5
- package/src/presentation/components/AIGenerationForm.tsx +0 -1
- package/src/presentation/components/AIGenerationHero.tsx +27 -24
- package/src/presentation/components/GenerationProgressContent.tsx +4 -2
- package/src/presentation/components/PhotoUploadCard/PhotoUploadCard.tsx +12 -26
- package/src/presentation/components/StylePresetsGrid.tsx +5 -3
- package/src/presentation/components/buttons/GenerateButton.tsx +39 -102
- package/src/presentation/components/headers/FeatureHeader.tsx +15 -9
- package/src/presentation/components/image-picker/DualImagePicker.tsx +0 -6
- package/src/presentation/components/image-picker/ImagePickerBox.tsx +27 -16
- package/src/presentation/components/modals/SettingsSheet.tsx +4 -2
- package/src/presentation/components/result/ResultImageCard.tsx +12 -35
- package/src/presentation/components/result/ResultStoryCard.tsx +10 -16
- package/src/domains/creations/presentation/components/CreationsProvider.tsx +0 -56
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-generation-content",
|
|
3
|
-
"version": "1.17.
|
|
3
|
+
"version": "1.17.88",
|
|
4
4
|
"description": "Provider-agnostic AI generation orchestration for React Native",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -84,9 +84,9 @@
|
|
|
84
84
|
"expo-haptics": "^15.0.8",
|
|
85
85
|
"expo-image": "^3.0.11",
|
|
86
86
|
"expo-linear-gradient": "~15.0.7",
|
|
87
|
-
"expo-video": "^2.0.0",
|
|
88
87
|
"expo-localization": "^17.0.8",
|
|
89
88
|
"expo-sharing": "^14.0.8",
|
|
89
|
+
"expo-video": "^2.0.0",
|
|
90
90
|
"firebase": "^12.6.0",
|
|
91
91
|
"i18next": "^25.7.3",
|
|
92
92
|
"react": "19.1.0",
|
|
@@ -149,10 +149,13 @@ export {
|
|
|
149
149
|
// Gallery Components
|
|
150
150
|
export { CreationsHomeCard } from "./presentation/components/CreationsHomeCard";
|
|
151
151
|
export { EmptyState } from "./presentation/components/EmptyState";
|
|
152
|
+
|
|
153
|
+
// Utilities
|
|
152
154
|
export {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
155
|
+
getLocalizedTitle,
|
|
156
|
+
getFilterCategoriesFromConfig,
|
|
157
|
+
getTranslatedTypes,
|
|
158
|
+
} from "./presentation/utils/filterUtils";
|
|
156
159
|
|
|
157
160
|
// =============================================================================
|
|
158
161
|
// PRESENTATION LAYER - Screens
|
|
@@ -78,17 +78,17 @@ export function CreationBadges({
|
|
|
78
78
|
paddingHorizontal: 8,
|
|
79
79
|
paddingVertical: 4,
|
|
80
80
|
borderRadius: 12,
|
|
81
|
-
backgroundColor:
|
|
81
|
+
backgroundColor: tokens.colors.modalOverlay,
|
|
82
82
|
gap: 4,
|
|
83
83
|
},
|
|
84
84
|
typeText: {
|
|
85
85
|
fontSize: 10,
|
|
86
86
|
fontWeight: "600",
|
|
87
|
-
color:
|
|
87
|
+
color: tokens.colors.textInverse,
|
|
88
88
|
textTransform: "capitalize",
|
|
89
89
|
},
|
|
90
90
|
}),
|
|
91
|
-
[statusColor]
|
|
91
|
+
[statusColor, tokens]
|
|
92
92
|
);
|
|
93
93
|
|
|
94
94
|
return (
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { View, StyleSheet } from 'react-native';
|
|
4
4
|
import { AtomicText, useAppDesignTokens, type DesignTokens } from "@umituz/react-native-design-system";
|
|
5
|
-
import { LinearGradient } from 'expo-linear-gradient';
|
|
6
5
|
|
|
7
6
|
interface DetailStoryProps {
|
|
8
7
|
readonly story: string;
|
|
@@ -16,16 +15,13 @@ export const DetailStory: React.FC<DetailStoryProps> = ({ story }) => {
|
|
|
16
15
|
|
|
17
16
|
return (
|
|
18
17
|
<View style={styles.container}>
|
|
19
|
-
<
|
|
20
|
-
colors={[tokens.colors.primary + '15', tokens.colors.primary + '05']}
|
|
21
|
-
style={styles.gradient}
|
|
22
|
-
>
|
|
18
|
+
<View style={styles.storyContainer}>
|
|
23
19
|
<AtomicText style={styles.quoteMark}>"</AtomicText>
|
|
24
20
|
<AtomicText style={styles.text}>{story}</AtomicText>
|
|
25
21
|
<View style={styles.quoteEndRow}>
|
|
26
22
|
<AtomicText style={[styles.quoteMark, styles.quoteEnd]}>"</AtomicText>
|
|
27
23
|
</View>
|
|
28
|
-
</
|
|
24
|
+
</View>
|
|
29
25
|
</View>
|
|
30
26
|
);
|
|
31
27
|
};
|
|
@@ -35,11 +31,12 @@ const useStyles = (tokens: DesignTokens) => StyleSheet.create({
|
|
|
35
31
|
paddingHorizontal: tokens.spacing.lg,
|
|
36
32
|
marginBottom: tokens.spacing.lg,
|
|
37
33
|
},
|
|
38
|
-
|
|
34
|
+
storyContainer: {
|
|
39
35
|
padding: tokens.spacing.lg,
|
|
40
36
|
borderRadius: 20,
|
|
41
37
|
borderWidth: 1,
|
|
42
|
-
borderColor: tokens.colors.
|
|
38
|
+
borderColor: tokens.colors.border,
|
|
39
|
+
backgroundColor: tokens.colors.surface,
|
|
43
40
|
},
|
|
44
41
|
quoteMark: {
|
|
45
42
|
fontSize: 48,
|
|
@@ -35,7 +35,7 @@ export function CreationThumbnail({
|
|
|
35
35
|
},
|
|
36
36
|
overlay: {
|
|
37
37
|
...StyleSheet.absoluteFillObject,
|
|
38
|
-
backgroundColor:
|
|
38
|
+
backgroundColor: tokens.colors.modalOverlay,
|
|
39
39
|
borderRadius: tokens.spacing.sm,
|
|
40
40
|
justifyContent: "center",
|
|
41
41
|
alignItems: "center",
|
|
@@ -31,9 +31,6 @@ export { CreationsHomeCard } from "./CreationsHomeCard";
|
|
|
31
31
|
export { CreationImageViewer } from "./CreationImageViewer";
|
|
32
32
|
export { CreationsGrid } from "./CreationsGrid";
|
|
33
33
|
|
|
34
|
-
// Provider
|
|
35
|
-
export { CreationsProvider, useCreationsProvider } from "./CreationsProvider";
|
|
36
|
-
|
|
37
34
|
// Detail Components
|
|
38
35
|
export { DetailHeader } from "./CreationDetail/DetailHeader";
|
|
39
36
|
export { DetailImage } from "./CreationDetail/DetailImage";
|
|
@@ -3,20 +3,21 @@ import { View, ScrollView, StyleSheet } from 'react-native';
|
|
|
3
3
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
4
4
|
import { useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
5
5
|
import type { Creation } from '../../domain/entities/Creation';
|
|
6
|
+
import type { CreationsConfig } from '../../domain/value-objects/CreationsConfig';
|
|
6
7
|
import { hasVideoContent, getPreviewUrl } from '../../domain/utils';
|
|
7
8
|
import { DetailHeader } from '../components/CreationDetail/DetailHeader';
|
|
8
9
|
import { DetailImage } from '../components/CreationDetail/DetailImage';
|
|
9
10
|
import { DetailVideo } from '../components/CreationDetail/DetailVideo';
|
|
10
11
|
import { DetailStory } from '../components/CreationDetail/DetailStory';
|
|
11
12
|
import { DetailActions } from '../components/CreationDetail/DetailActions';
|
|
12
|
-
|
|
13
|
-
import { useCreationsProvider } from '../components/CreationsProvider';
|
|
13
|
+
import { getLocalizedTitle } from '../utils/filterUtils';
|
|
14
14
|
|
|
15
15
|
/** Video creation types */
|
|
16
16
|
const VIDEO_TYPES = ['text-to-video', 'image-to-video'] as const;
|
|
17
17
|
|
|
18
18
|
interface CreationDetailScreenProps {
|
|
19
19
|
readonly creation: Creation;
|
|
20
|
+
readonly config: CreationsConfig;
|
|
20
21
|
readonly onClose: () => void;
|
|
21
22
|
readonly onShare: (creation: Creation) => void;
|
|
22
23
|
readonly onDelete: (creation: Creation) => void;
|
|
@@ -32,6 +33,7 @@ interface CreationMetadata {
|
|
|
32
33
|
|
|
33
34
|
export const CreationDetailScreen: React.FC<CreationDetailScreenProps> = ({
|
|
34
35
|
creation,
|
|
36
|
+
config,
|
|
35
37
|
onClose,
|
|
36
38
|
onShare,
|
|
37
39
|
onDelete,
|
|
@@ -39,7 +41,6 @@ export const CreationDetailScreen: React.FC<CreationDetailScreenProps> = ({
|
|
|
39
41
|
}) => {
|
|
40
42
|
const tokens = useAppDesignTokens();
|
|
41
43
|
const insets = useSafeAreaInsets();
|
|
42
|
-
const { getLocalizedTitle } = useCreationsProvider();
|
|
43
44
|
|
|
44
45
|
// Extract data safely
|
|
45
46
|
const metadata = (creation.metadata || {}) as CreationMetadata;
|
|
@@ -48,7 +49,7 @@ export const CreationDetailScreen: React.FC<CreationDetailScreenProps> = ({
|
|
|
48
49
|
// 1. Manually set names in metadata
|
|
49
50
|
// 2. Localized title from config types mapping
|
|
50
51
|
// 3. Fallback to raw creation type (formatted)
|
|
51
|
-
const title = metadata.names || getLocalizedTitle(creation.type);
|
|
52
|
+
const title = metadata.names || getLocalizedTitle(config, t, creation.type);
|
|
52
53
|
const story = metadata.story || metadata.description || "";
|
|
53
54
|
const date = metadata.date || new Date(creation.createdAt).toLocaleDateString();
|
|
54
55
|
|
|
@@ -19,7 +19,6 @@ import type { Creation } from "../../domain/entities/Creation";
|
|
|
19
19
|
import type { CreationsConfig } from "../../domain/value-objects/CreationsConfig";
|
|
20
20
|
import type { ICreationsRepository } from "../../domain/repositories/ICreationsRepository";
|
|
21
21
|
import { CreationDetailScreen } from "./CreationDetailScreen";
|
|
22
|
-
import { CreationsProvider } from "../components/CreationsProvider";
|
|
23
22
|
|
|
24
23
|
interface CreationsGalleryScreenProps {
|
|
25
24
|
readonly userId: string | null;
|
|
@@ -34,15 +33,7 @@ interface CreationsGalleryScreenProps {
|
|
|
34
33
|
readonly showFilter?: boolean;
|
|
35
34
|
}
|
|
36
35
|
|
|
37
|
-
export function CreationsGalleryScreen(
|
|
38
|
-
return (
|
|
39
|
-
<CreationsProvider config={props.config} t={props.t}>
|
|
40
|
-
<CreationsGalleryScreenContent {...props} />
|
|
41
|
-
</CreationsProvider>
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function CreationsGalleryScreenContent({
|
|
36
|
+
export function CreationsGalleryScreen({
|
|
46
37
|
userId,
|
|
47
38
|
repository,
|
|
48
39
|
config,
|
|
@@ -164,7 +155,7 @@ function CreationsGalleryScreenContent({
|
|
|
164
155
|
), [isLoading, creations, filters.isFiltered, tokens, t, config, emptyActionLabel, onEmptyAction, filters.clearAllFilters]);
|
|
165
156
|
|
|
166
157
|
if (selectedCreation) {
|
|
167
|
-
return <CreationDetailScreen creation={selectedCreation} onClose={() => setSelectedCreation(null)} onShare={handleShare} onDelete={handleDelete} t={t} />;
|
|
158
|
+
return <CreationDetailScreen creation={selectedCreation} config={config} onClose={() => setSelectedCreation(null)} onShare={handleShare} onDelete={handleDelete} t={t} />;
|
|
168
159
|
}
|
|
169
160
|
|
|
170
161
|
return (
|
|
@@ -36,7 +36,7 @@ export const getFilterCategoriesFromConfig = (
|
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
38
|
* Translates the creation types for display.
|
|
39
|
-
*
|
|
39
|
+
*
|
|
40
40
|
* @param config The creations configuration object
|
|
41
41
|
* @param t Translation function
|
|
42
42
|
* @returns Array of types with translated labels
|
|
@@ -50,3 +50,20 @@ export const getTranslatedTypes = (
|
|
|
50
50
|
labelKey: t(type.labelKey)
|
|
51
51
|
}));
|
|
52
52
|
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Gets the localized title for a creation type.
|
|
56
|
+
*
|
|
57
|
+
* @param config The creations configuration object
|
|
58
|
+
* @param t Translation function
|
|
59
|
+
* @param typeId The creation type ID
|
|
60
|
+
* @returns Localized title string
|
|
61
|
+
*/
|
|
62
|
+
export const getLocalizedTitle = (
|
|
63
|
+
config: CreationsConfig,
|
|
64
|
+
t: (key: string) => string,
|
|
65
|
+
typeId: string
|
|
66
|
+
): string => {
|
|
67
|
+
const typeConfig = config.types.find(type => type.id === typeId);
|
|
68
|
+
return typeConfig ? t(typeConfig.labelKey) : typeId;
|
|
69
|
+
};
|
|
@@ -3,328 +3,102 @@
|
|
|
3
3
|
* AI-powered flashcard generation for educational content
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
export
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
createdAt?: string;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface FlashcardGenerationResult {
|
|
30
|
-
success: boolean;
|
|
31
|
-
flashcards: GeneratedFlashcard[];
|
|
32
|
-
creditsUsed: number;
|
|
33
|
-
tokensUsed: number;
|
|
34
|
-
processingTime: number; // milliseconds
|
|
35
|
-
error?: string;
|
|
36
|
-
requestId: string;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export class FlashcardGenerationService {
|
|
40
|
-
private static instance: FlashcardGenerationService;
|
|
41
|
-
|
|
42
|
-
static getInstance(): FlashcardGenerationService {
|
|
43
|
-
if (!FlashcardGenerationService.instance) {
|
|
44
|
-
FlashcardGenerationService.instance = new FlashcardGenerationService();
|
|
45
|
-
}
|
|
46
|
-
return FlashcardGenerationService.instance;
|
|
47
|
-
}
|
|
6
|
+
import type {
|
|
7
|
+
FlashcardGenerationRequest,
|
|
8
|
+
FlashcardGenerationResult,
|
|
9
|
+
FlashcardGenerationResponse,
|
|
10
|
+
FlashcardValidationResult,
|
|
11
|
+
} from "./types/flashcard.types";
|
|
12
|
+
import { buildFlashcardPrompt, calculateMaxTokens } from "./builders/flashcard-prompt.builder";
|
|
13
|
+
import { parseFlashcardsFromResponse } from "./parsers/flashcard-response.parser";
|
|
14
|
+
import { validateFlashcard } from "./validators/flashcard.validator";
|
|
15
|
+
|
|
16
|
+
// Re-export types for backward compatibility
|
|
17
|
+
export type {
|
|
18
|
+
FlashcardGenerationRequest,
|
|
19
|
+
FlashcardGenerationResult,
|
|
20
|
+
FlashcardValidationResult,
|
|
21
|
+
GeneratedFlashcard,
|
|
22
|
+
} from "./types/flashcard.types";
|
|
23
|
+
|
|
24
|
+
const CREDITS_PER_FLASHCARD = 2;
|
|
25
|
+
const MOCK_DELAY_MS = 2000;
|
|
48
26
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const startTime = Date.now();
|
|
57
|
-
|
|
58
|
-
// Create AI generation prompt
|
|
59
|
-
const prompt = this.buildFlashcardPrompt(request);
|
|
60
|
-
|
|
61
|
-
// Execute generation
|
|
62
|
-
const result = await this.executeGeneration(prompt, request.count);
|
|
63
|
-
|
|
64
|
-
// Parse AI response into flashcards
|
|
65
|
-
const flashcards = this.parseFlashcardsFromResult(result, request);
|
|
66
|
-
const processingTime = Date.now() - startTime;
|
|
67
|
-
|
|
68
|
-
return {
|
|
69
|
-
success: true,
|
|
70
|
-
flashcards,
|
|
71
|
-
creditsUsed: request.count * 2, // 2 credits per flashcard
|
|
72
|
-
tokensUsed: result.metadata?.tokensUsed || 0,
|
|
73
|
-
processingTime,
|
|
74
|
-
requestId: result.jobId || `req_${Date.now()}`,
|
|
75
|
-
};
|
|
76
|
-
} catch (error) {
|
|
77
|
-
return {
|
|
78
|
-
success: false,
|
|
79
|
-
flashcards: [],
|
|
80
|
-
creditsUsed: 0,
|
|
81
|
-
tokensUsed: 0,
|
|
82
|
-
processingTime: 0,
|
|
83
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
84
|
-
requestId: "",
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
}
|
|
27
|
+
/**
|
|
28
|
+
* Generate flashcards using AI
|
|
29
|
+
*/
|
|
30
|
+
export async function generateFlashcards(
|
|
31
|
+
request: FlashcardGenerationRequest,
|
|
32
|
+
): Promise<FlashcardGenerationResult> {
|
|
33
|
+
const startTime = Date.now();
|
|
88
34
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
front: string,
|
|
94
|
-
back: string,
|
|
95
|
-
): Promise<{
|
|
96
|
-
accuracy: number;
|
|
97
|
-
relevance: number;
|
|
98
|
-
clarity: number;
|
|
99
|
-
completeness: number;
|
|
100
|
-
overall: number;
|
|
101
|
-
}> {
|
|
102
|
-
// Simple validation heuristic
|
|
103
|
-
const accuracy = this.calculateAccuracy(front, back);
|
|
104
|
-
const relevance = this.calculateRelevance(front, back);
|
|
105
|
-
const clarity = this.calculateClarity(front, back);
|
|
106
|
-
const completeness = this.calculateCompleteness(front, back);
|
|
107
|
-
const overall = (accuracy + relevance + clarity + completeness) / 4;
|
|
35
|
+
try {
|
|
36
|
+
const prompt = buildFlashcardPrompt(request);
|
|
37
|
+
const response = await executeGeneration(prompt, request.count);
|
|
38
|
+
const flashcards = parseFlashcardsFromResponse(response, request);
|
|
108
39
|
|
|
109
40
|
return {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
private buildFlashcardPrompt(request: FlashcardGenerationRequest): string {
|
|
119
|
-
const qualityMap = {
|
|
120
|
-
beginner:
|
|
121
|
-
"simple, clear language appropriate for learners just starting out",
|
|
122
|
-
intermediate:
|
|
123
|
-
"moderate complexity with some technical terms expected to be known",
|
|
124
|
-
advanced:
|
|
125
|
-
"complex content with specialized terminology and nuanced concepts",
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
const formatInstructions = {
|
|
129
|
-
qa: "Format as Question-Answer pairs",
|
|
130
|
-
definition: "Format as Term-Definition pairs",
|
|
131
|
-
fill_blank: "Format as Fill-in-the-blank exercises",
|
|
132
|
-
multiple_choice:
|
|
133
|
-
"Format as Multiple Choice questions with one correct answer",
|
|
41
|
+
success: true,
|
|
42
|
+
flashcards,
|
|
43
|
+
creditsUsed: request.count * CREDITS_PER_FLASHCARD,
|
|
44
|
+
tokensUsed: response.metadata?.tokensUsed ?? 0,
|
|
45
|
+
processingTime: Date.now() - startTime,
|
|
46
|
+
requestId: response.jobId ?? `req_${Date.now()}`,
|
|
134
47
|
};
|
|
135
|
-
|
|
136
|
-
return `Generate ${request.count} educational flashcards about "${request.topic}".
|
|
137
|
-
|
|
138
|
-
Topic Context: ${request.context || "General learning"}
|
|
139
|
-
Difficulty Level: ${request.difficulty} - ${qualityMap[request.difficulty]}
|
|
140
|
-
Format: ${request.format || "qa"} - ${formatInstructions[request.format || "qa"]}
|
|
141
|
-
Language: ${request.language || "English"}
|
|
142
|
-
Tags to include: ${request.tags?.join(", ") || "auto-generated"}
|
|
143
|
-
|
|
144
|
-
Requirements:
|
|
145
|
-
- Questions should be clear and concise
|
|
146
|
-
- Answers should be accurate and comprehensive
|
|
147
|
-
- Content should be age and difficulty appropriate
|
|
148
|
-
- Include relevant educational context
|
|
149
|
-
- Make it engaging and memorable
|
|
150
|
-
|
|
151
|
-
Output format: JSON array with structure:
|
|
152
|
-
[
|
|
153
|
-
{
|
|
154
|
-
"front": "Question text here",
|
|
155
|
-
"back": "Answer text here",
|
|
156
|
-
"difficulty": "easy|medium|hard",
|
|
157
|
-
"tags": ["tag1", "tag2"]
|
|
158
|
-
}
|
|
159
|
-
]`;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
private calculateMaxTokens(count: number): number {
|
|
163
|
-
// Estimate ~50 tokens per flashcard + overhead
|
|
164
|
-
return Math.max(count * 50, 200);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
private async executeGeneration(
|
|
168
|
-
prompt: string,
|
|
169
|
-
count: number,
|
|
170
|
-
): Promise<{
|
|
171
|
-
success: boolean;
|
|
172
|
-
result: string;
|
|
173
|
-
metadata: { tokensUsed: number; processingTime: number };
|
|
174
|
-
jobId: string;
|
|
175
|
-
}> {
|
|
176
|
-
// This would integrate with the actual AI generation orchestrator
|
|
177
|
-
// For now, return mock result
|
|
178
|
-
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
179
|
-
|
|
180
|
-
const maxTokens = this.calculateMaxTokens(count);
|
|
48
|
+
} catch (error) {
|
|
181
49
|
return {
|
|
182
|
-
success:
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
50
|
+
success: false,
|
|
51
|
+
flashcards: [],
|
|
52
|
+
creditsUsed: 0,
|
|
53
|
+
tokensUsed: 0,
|
|
54
|
+
processingTime: 0,
|
|
55
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
56
|
+
requestId: "",
|
|
189
57
|
};
|
|
190
58
|
}
|
|
59
|
+
}
|
|
191
60
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
front: "Define gravity",
|
|
202
|
-
back: "A fundamental force that attracts objects with mass toward each other.",
|
|
203
|
-
difficulty: "easy",
|
|
204
|
-
tags: ["physics", "science", "forces"],
|
|
205
|
-
},
|
|
206
|
-
{
|
|
207
|
-
front: "What is the formula for water?",
|
|
208
|
-
back: "H₂O",
|
|
209
|
-
difficulty: "easy",
|
|
210
|
-
tags: ["chemistry", "science", "molecules"],
|
|
211
|
-
},
|
|
212
|
-
];
|
|
61
|
+
/**
|
|
62
|
+
* Validate flashcard content quality
|
|
63
|
+
*/
|
|
64
|
+
export function validateFlashcardContent(
|
|
65
|
+
front: string,
|
|
66
|
+
back: string,
|
|
67
|
+
): FlashcardValidationResult {
|
|
68
|
+
return validateFlashcard(front, back);
|
|
69
|
+
}
|
|
213
70
|
|
|
214
|
-
|
|
215
|
-
|
|
71
|
+
async function executeGeneration(
|
|
72
|
+
_prompt: string,
|
|
73
|
+
count: number,
|
|
74
|
+
): Promise<FlashcardGenerationResponse> {
|
|
75
|
+
// Mock implementation - integrate with actual AI orchestrator
|
|
76
|
+
await new Promise((resolve) => setTimeout(resolve, MOCK_DELAY_MS));
|
|
77
|
+
|
|
78
|
+
const maxTokens = calculateMaxTokens(count);
|
|
79
|
+
return {
|
|
80
|
+
success: true,
|
|
81
|
+
result: generateMockContent(count),
|
|
82
|
+
metadata: { tokensUsed: maxTokens, processingTime: MOCK_DELAY_MS },
|
|
83
|
+
jobId: `job_${Date.now()}`,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
216
86
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
87
|
+
function generateMockContent(count: number): string {
|
|
88
|
+
const mockFlashcards = [
|
|
89
|
+
{
|
|
90
|
+
front: "What is photosynthesis?",
|
|
91
|
+
back: "The process by which plants convert sunlight into glucose.",
|
|
92
|
+
difficulty: "medium",
|
|
93
|
+
tags: ["biology", "science"],
|
|
223
94
|
},
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
} else if (Array.isArray(result.result)) {
|
|
232
|
-
flashcards = result.result;
|
|
233
|
-
} else {
|
|
234
|
-
throw new Error("Invalid AI response format");
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
return flashcards.map((item: unknown, index) => {
|
|
238
|
-
const flashcard = item as {
|
|
239
|
-
front?: string;
|
|
240
|
-
back?: string;
|
|
241
|
-
difficulty?: "easy" | "medium" | "hard";
|
|
242
|
-
tags?: string | string[];
|
|
243
|
-
};
|
|
244
|
-
return {
|
|
245
|
-
id: `generated_${Date.now()}_${index}`,
|
|
246
|
-
front: flashcard.front || "",
|
|
247
|
-
back: flashcard.back || "",
|
|
248
|
-
difficulty: flashcard.difficulty || "medium",
|
|
249
|
-
tags: Array.isArray(flashcard.tags)
|
|
250
|
-
? flashcard.tags
|
|
251
|
-
: flashcard.tags
|
|
252
|
-
? [flashcard.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
|
-
});
|
|
260
|
-
} catch (error) {
|
|
261
|
-
if (__DEV__) {
|
|
262
|
-
// eslint-disable-next-line no-console
|
|
263
|
-
console.error("Failed to parse AI response:", error);
|
|
264
|
-
}
|
|
265
|
-
return [];
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
private calculateAccuracy(front: string, back: string): number {
|
|
270
|
-
// Simple heuristics for accuracy assessment
|
|
271
|
-
let score = 0.5; // Base score
|
|
272
|
-
|
|
273
|
-
// Check for reasonable length
|
|
274
|
-
if (front.length >= 5 && front.length <= 200) score += 0.2;
|
|
275
|
-
if (back.length >= 10 && back.length <= 500) score += 0.2;
|
|
276
|
-
|
|
277
|
-
// Check for balanced content
|
|
278
|
-
const frontWords = front.split(/\s+/).length;
|
|
279
|
-
const backWords = back.split(/\s+/).length;
|
|
280
|
-
if (backWords >= frontWords * 0.5 && backWords <= frontWords * 3)
|
|
281
|
-
score += 0.1;
|
|
282
|
-
|
|
283
|
-
return Math.min(score, 1.0);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
private calculateRelevance(front: string, back: string): number {
|
|
287
|
-
// Simple relevance check
|
|
288
|
-
let score = 0.6; // Base score
|
|
289
|
-
|
|
290
|
-
// Check for educational content indicators
|
|
291
|
-
const educationalTerms = [
|
|
292
|
-
"define",
|
|
293
|
-
"explain",
|
|
294
|
-
"describe",
|
|
295
|
-
"what is",
|
|
296
|
-
"how does",
|
|
297
|
-
"formula",
|
|
298
|
-
"process",
|
|
299
|
-
"function",
|
|
300
|
-
];
|
|
301
|
-
const hasEducationalTerms = educationalTerms.some(
|
|
302
|
-
(term) =>
|
|
303
|
-
front.toLowerCase().includes(term) || back.toLowerCase().includes(term),
|
|
304
|
-
);
|
|
305
|
-
|
|
306
|
-
if (hasEducationalTerms) score += 0.3;
|
|
307
|
-
if (front.includes("?") || front.toLowerCase().includes("what is"))
|
|
308
|
-
score += 0.1;
|
|
309
|
-
|
|
310
|
-
return Math.min(score, 1.0);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
private calculateClarity(front: string, back: string): number {
|
|
314
|
-
let score = 0.5; // Base score
|
|
315
|
-
|
|
316
|
-
// Check for clear structure
|
|
317
|
-
if (front.trim().endsWith("?")) score += 0.2;
|
|
318
|
-
if (!front.includes("...") && !back.includes("...")) score += 0.2;
|
|
319
|
-
if (!/[A-Z]{2,}/.test(front)) score += 0.1; // Not too many caps
|
|
320
|
-
|
|
321
|
-
return Math.min(score, 1.0);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
private calculateCompleteness(front: string, back: string): number {
|
|
325
|
-
const frontScore = Math.min(front.length / 20, 1.0); // Ideal 20 chars
|
|
326
|
-
const backScore = Math.min(back.length / 50, 1.0); // Ideal 50 chars
|
|
95
|
+
{
|
|
96
|
+
front: "Define gravity",
|
|
97
|
+
back: "A force that attracts objects with mass toward each other.",
|
|
98
|
+
difficulty: "easy",
|
|
99
|
+
tags: ["physics", "science"],
|
|
100
|
+
},
|
|
101
|
+
];
|
|
327
102
|
|
|
328
|
-
|
|
329
|
-
}
|
|
103
|
+
return JSON.stringify(mockFlashcards.slice(0, count));
|
|
330
104
|
}
|