@umituz/react-native-ai-generation-content 1.17.145 → 1.17.147
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/content-moderation/infrastructure/services/moderators/text.moderator.ts +3 -3
- package/src/domains/creations/index.ts +1 -1
- package/src/domains/creations/infrastructure/repositories/CreationsFetcher.ts +6 -6
- package/src/domains/creations/infrastructure/repositories/CreationsRepository.ts +5 -5
- package/src/domains/creations/infrastructure/repositories/CreationsWriter.ts +5 -5
- package/src/domains/creations/presentation/components/CreationsHomeCard.tsx +1 -1
- package/src/domains/creations/presentation/components/GalleryHeader.tsx +1 -1
- package/src/domains/creations/presentation/hooks/advancedFilter.types.ts +38 -0
- package/src/domains/creations/presentation/hooks/filterHelpers.ts +145 -0
- package/src/domains/creations/presentation/hooks/index.ts +5 -0
- package/src/domains/creations/presentation/hooks/useAdvancedFilter.ts +34 -184
- package/src/domains/flashcard-generation/parsers/flashcard-response.parser.ts +1 -1
- package/src/features/image-to-video/infrastructure/services/image-to-video-executor.ts +10 -10
- package/src/features/image-to-video/presentation/hooks/useImageToVideoFeature.ts +8 -8
- package/src/features/image-to-video/presentation/hooks/useImageToVideoForm.ts +5 -5
- package/src/features/meme-generator/infrastructure/services/MemeGenerationService.ts +2 -2
- package/src/features/photo-restoration/presentation/components/PhotoRestoreResultView.tsx +1 -1
- package/src/features/script-generator/presentation/hooks/useScriptGenerator.ts +1 -1
- package/src/features/text-to-image/infrastructure/services/text-to-image-executor.ts +2 -2
- package/src/features/text-to-image/presentation/hooks/useGeneration.ts +12 -12
- package/src/features/text-to-video/infrastructure/services/text-to-video-executor.ts +9 -9
- package/src/features/text-to-video/presentation/hooks/textToVideoExecution.ts +134 -0
- package/src/features/text-to-video/presentation/hooks/useTextToVideoFeature.ts +24 -118
- package/src/features/text-to-video/presentation/hooks/useTextToVideoForm.ts +1 -1
- package/src/features/text-to-voice/infrastructure/services/text-to-voice-executor.ts +2 -2
- package/src/index.ts +1 -1
- package/src/infrastructure/config/app-services.config.ts +2 -2
- package/src/infrastructure/orchestration/GenerationOrchestrator.ts +23 -114
- package/src/infrastructure/orchestration/index.ts +3 -1
- package/src/infrastructure/orchestration/orchestrator.errors.ts +29 -0
- package/src/infrastructure/orchestration/orchestrator.types.ts +48 -0
- package/src/infrastructure/services/generation-orchestrator.service.ts +5 -5
- package/src/infrastructure/services/image-feature-executor.service.ts +2 -2
- package/src/infrastructure/services/job-poller.service.ts +1 -1
- package/src/infrastructure/services/job-poller.ts +3 -3
- package/src/infrastructure/services/provider-registry.service.ts +6 -6
- package/src/infrastructure/services/provider-validator.ts +4 -4
- package/src/infrastructure/services/video-feature-executor.service.ts +2 -2
- package/src/infrastructure/utils/error-classifier.util.ts +2 -2
- package/src/infrastructure/utils/feature-utils.ts +4 -4
- package/src/infrastructure/utils/result-validator.util.ts +1 -1
- package/src/infrastructure/utils/video-helpers.ts +3 -3
- package/src/presentation/components/AIGenerationForm.tsx +2 -2
- package/src/presentation/components/buttons/GenerateButton.tsx +3 -3
- package/src/presentation/hooks/useGenerationCallbacksBuilder.ts +1 -1
package/package.json
CHANGED
|
@@ -44,7 +44,7 @@ class TextModerator extends BaseModerator {
|
|
|
44
44
|
|
|
45
45
|
moderate(content: string): ModerationResult {
|
|
46
46
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
47
|
-
|
|
47
|
+
|
|
48
48
|
console.log("[TextModerator] moderate() called", {
|
|
49
49
|
contentLength: content?.length ?? 0,
|
|
50
50
|
});
|
|
@@ -53,7 +53,7 @@ class TextModerator extends BaseModerator {
|
|
|
53
53
|
const validationError = this.validate(content);
|
|
54
54
|
if (validationError) {
|
|
55
55
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
56
|
-
|
|
56
|
+
|
|
57
57
|
console.log("[TextModerator] validation failed", {
|
|
58
58
|
ruleId: validationError.ruleId,
|
|
59
59
|
violationType: validationError.violationType,
|
|
@@ -65,7 +65,7 @@ class TextModerator extends BaseModerator {
|
|
|
65
65
|
const violations = this.evaluateRules(content);
|
|
66
66
|
|
|
67
67
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
68
|
-
|
|
68
|
+
|
|
69
69
|
console.log("[TextModerator] moderate() completed", {
|
|
70
70
|
isAllowed: violations.length === 0,
|
|
71
71
|
violationsCount: violations.length,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* AI-generated creations gallery with filtering, sharing, and management
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
|
|
7
7
|
if (typeof __DEV__ !== "undefined" && __DEV__) console.log("📍 [LIFECYCLE] creations/index.ts - Module loading");
|
|
8
8
|
|
|
9
9
|
// =============================================================================
|
|
@@ -17,7 +17,7 @@ export class CreationsFetcher {
|
|
|
17
17
|
|
|
18
18
|
async getAll(userId: string): Promise<Creation[]> {
|
|
19
19
|
if (__DEV__) {
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
console.log("[CreationsRepository] getAll()", { userId });
|
|
22
22
|
}
|
|
23
23
|
|
|
@@ -29,7 +29,7 @@ export class CreationsFetcher {
|
|
|
29
29
|
const snapshot = await getDocs(q);
|
|
30
30
|
|
|
31
31
|
if (__DEV__) {
|
|
32
|
-
|
|
32
|
+
|
|
33
33
|
console.log("[CreationsRepository] Fetched:", snapshot.docs.length);
|
|
34
34
|
}
|
|
35
35
|
|
|
@@ -39,7 +39,7 @@ export class CreationsFetcher {
|
|
|
39
39
|
});
|
|
40
40
|
} catch (error) {
|
|
41
41
|
if (__DEV__) {
|
|
42
|
-
|
|
42
|
+
|
|
43
43
|
console.error("[CreationsRepository] getAll() ERROR", error);
|
|
44
44
|
}
|
|
45
45
|
return [];
|
|
@@ -48,7 +48,7 @@ export class CreationsFetcher {
|
|
|
48
48
|
|
|
49
49
|
async getById(userId: string, id: string): Promise<Creation | null> {
|
|
50
50
|
if (__DEV__) {
|
|
51
|
-
|
|
51
|
+
|
|
52
52
|
console.log("[CreationsRepository] getById()", { userId, id });
|
|
53
53
|
}
|
|
54
54
|
|
|
@@ -60,7 +60,7 @@ export class CreationsFetcher {
|
|
|
60
60
|
|
|
61
61
|
if (!docSnap.exists()) {
|
|
62
62
|
if (__DEV__) {
|
|
63
|
-
|
|
63
|
+
|
|
64
64
|
console.log("[CreationsRepository] Document not found");
|
|
65
65
|
}
|
|
66
66
|
return null;
|
|
@@ -70,7 +70,7 @@ export class CreationsFetcher {
|
|
|
70
70
|
return this.documentMapper(docSnap.id, data);
|
|
71
71
|
} catch (error) {
|
|
72
72
|
if (__DEV__) {
|
|
73
|
-
|
|
73
|
+
|
|
74
74
|
console.error("[CreationsRepository] getById() ERROR", error);
|
|
75
75
|
}
|
|
76
76
|
return null;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
|
|
2
2
|
if (typeof __DEV__ !== "undefined" && __DEV__) console.log("📍 [LIFECYCLE] CreationsRepository.ts - Module loading");
|
|
3
3
|
|
|
4
4
|
import { BaseRepository, FirestorePathResolver } from "@umituz/react-native-firebase";
|
|
@@ -39,22 +39,22 @@ export class CreationsRepository
|
|
|
39
39
|
collectionName: string,
|
|
40
40
|
options?: RepositoryOptions,
|
|
41
41
|
) {
|
|
42
|
-
|
|
42
|
+
|
|
43
43
|
if (typeof __DEV__ !== "undefined" && __DEV__) console.log("📍 [LIFECYCLE] CreationsRepository - Constructor start");
|
|
44
44
|
super();
|
|
45
45
|
|
|
46
46
|
const documentMapper = options?.documentMapper ?? mapDocumentToCreation;
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
|
|
49
49
|
if (typeof __DEV__ !== "undefined" && __DEV__) console.log("📍 [LIFECYCLE] CreationsRepository - Getting db");
|
|
50
50
|
const db = this.getDb();
|
|
51
|
-
|
|
51
|
+
|
|
52
52
|
if (typeof __DEV__ !== "undefined" && __DEV__) console.log("📍 [LIFECYCLE] CreationsRepository - db:", db ? "available" : "null");
|
|
53
53
|
|
|
54
54
|
this.pathResolver = new FirestorePathResolver(collectionName, db);
|
|
55
55
|
this.fetcher = new CreationsFetcher(this.pathResolver, documentMapper);
|
|
56
56
|
this.writer = new CreationsWriter(this.pathResolver);
|
|
57
|
-
|
|
57
|
+
|
|
58
58
|
if (typeof __DEV__ !== "undefined" && __DEV__) console.log("📍 [LIFECYCLE] CreationsRepository - Constructor end");
|
|
59
59
|
}
|
|
60
60
|
|
|
@@ -13,7 +13,7 @@ export class CreationsWriter {
|
|
|
13
13
|
|
|
14
14
|
async create(userId: string, creation: Creation): Promise<void> {
|
|
15
15
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
16
|
-
|
|
16
|
+
|
|
17
17
|
console.log("[CreationsWriter] create() start", { userId, creationId: creation.id });
|
|
18
18
|
}
|
|
19
19
|
|
|
@@ -35,12 +35,12 @@ export class CreationsWriter {
|
|
|
35
35
|
try {
|
|
36
36
|
await setDoc(docRef, data);
|
|
37
37
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
38
|
-
|
|
38
|
+
|
|
39
39
|
console.log("[CreationsWriter] create() success", { creationId: creation.id });
|
|
40
40
|
}
|
|
41
41
|
} catch (error) {
|
|
42
42
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
43
|
-
|
|
43
|
+
|
|
44
44
|
console.error("[CreationsWriter] create() error", error);
|
|
45
45
|
}
|
|
46
46
|
throw error;
|
|
@@ -53,7 +53,7 @@ export class CreationsWriter {
|
|
|
53
53
|
updates: Partial<Creation>,
|
|
54
54
|
): Promise<boolean> {
|
|
55
55
|
if (__DEV__) {
|
|
56
|
-
|
|
56
|
+
|
|
57
57
|
console.log("[CreationsRepository] update()", { userId, id, updates });
|
|
58
58
|
}
|
|
59
59
|
|
|
@@ -89,7 +89,7 @@ export class CreationsWriter {
|
|
|
89
89
|
return true;
|
|
90
90
|
} catch (error) {
|
|
91
91
|
if (__DEV__) {
|
|
92
|
-
|
|
92
|
+
|
|
93
93
|
console.error("[CreationsRepository] update() ERROR", error);
|
|
94
94
|
}
|
|
95
95
|
return false;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Advanced Filter Types
|
|
3
|
+
* Type definitions for advanced filtering hook
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { CreationFilter, CreationStats, FilterOption } from "../../domain/types";
|
|
7
|
+
import type { CreationCategory, CreationStatus, CreationTypeId } from "../../domain/types";
|
|
8
|
+
|
|
9
|
+
export interface FilterableCreation {
|
|
10
|
+
id: string;
|
|
11
|
+
type?: string;
|
|
12
|
+
status?: string;
|
|
13
|
+
prompt?: string;
|
|
14
|
+
createdAt?: Date | number;
|
|
15
|
+
updatedAt?: Date | number;
|
|
16
|
+
metadata?: Record<string, unknown>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface UseAdvancedFilterProps<T extends FilterableCreation> {
|
|
20
|
+
creations: T[] | undefined;
|
|
21
|
+
initialFilter?: Partial<CreationFilter>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface UseAdvancedFilterReturn<T extends FilterableCreation> {
|
|
25
|
+
filtered: T[];
|
|
26
|
+
filter: CreationFilter;
|
|
27
|
+
stats: CreationStats;
|
|
28
|
+
activeMediaFilter: string;
|
|
29
|
+
activeStatusFilter: string;
|
|
30
|
+
hasActiveFilters: boolean;
|
|
31
|
+
mediaFilterOptions: FilterOption[];
|
|
32
|
+
statusFilterOptions: FilterOption[];
|
|
33
|
+
setMediaFilter: (filter: CreationCategory | CreationTypeId) => void;
|
|
34
|
+
setStatusFilter: (status: CreationStatus | "all") => void;
|
|
35
|
+
setSearchQuery: (query: string) => void;
|
|
36
|
+
updateFilter: (update: Partial<CreationFilter>) => void;
|
|
37
|
+
resetFilters: () => void;
|
|
38
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filter Helpers
|
|
3
|
+
* Utility functions for filtering and sorting creations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { CreationFilter } from "../../domain/types";
|
|
7
|
+
import { isTypeInCategory } from "../../domain/types";
|
|
8
|
+
import type { CreationCategory, CreationTypeId } from "../../domain/types";
|
|
9
|
+
import type { FilterableCreation } from "./advancedFilter.types";
|
|
10
|
+
|
|
11
|
+
export function filterByType<T extends FilterableCreation>(
|
|
12
|
+
creations: T[],
|
|
13
|
+
filterType: string | undefined,
|
|
14
|
+
): T[] {
|
|
15
|
+
if (!filterType || filterType === "all") return creations;
|
|
16
|
+
|
|
17
|
+
if (["image", "video", "voice"].includes(filterType)) {
|
|
18
|
+
const category = filterType as CreationCategory;
|
|
19
|
+
return creations.filter(
|
|
20
|
+
(c) => c.type && isTypeInCategory(c.type as CreationTypeId, category),
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return creations.filter((c) => c.type === filterType);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function filterByStatus<T extends FilterableCreation>(
|
|
28
|
+
creations: T[],
|
|
29
|
+
status: string | undefined,
|
|
30
|
+
): T[] {
|
|
31
|
+
if (!status || status === "all") return creations;
|
|
32
|
+
return creations.filter((c) => c.status === status);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function filterBySearch<T extends FilterableCreation>(
|
|
36
|
+
creations: T[],
|
|
37
|
+
searchQuery: string | undefined,
|
|
38
|
+
): T[] {
|
|
39
|
+
if (!searchQuery?.trim()) return creations;
|
|
40
|
+
|
|
41
|
+
const query = searchQuery.toLowerCase().trim();
|
|
42
|
+
return creations.filter((c) => {
|
|
43
|
+
const prompt = c.prompt?.toLowerCase() || "";
|
|
44
|
+
const type = c.type?.toLowerCase() || "";
|
|
45
|
+
return prompt.includes(query) || type.includes(query);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function filterByDateRange<T extends FilterableCreation>(
|
|
50
|
+
creations: T[],
|
|
51
|
+
startDate: number | undefined,
|
|
52
|
+
endDate: number | undefined,
|
|
53
|
+
): T[] {
|
|
54
|
+
let result = creations;
|
|
55
|
+
|
|
56
|
+
if (startDate) {
|
|
57
|
+
result = result.filter((c) => {
|
|
58
|
+
const createdAt =
|
|
59
|
+
c.createdAt instanceof Date ? c.createdAt.getTime() : c.createdAt || 0;
|
|
60
|
+
return createdAt >= startDate;
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (endDate) {
|
|
65
|
+
result = result.filter((c) => {
|
|
66
|
+
const createdAt =
|
|
67
|
+
c.createdAt instanceof Date ? c.createdAt.getTime() : c.createdAt || 0;
|
|
68
|
+
return createdAt <= endDate;
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function sortCreations<T extends FilterableCreation>(
|
|
76
|
+
creations: T[],
|
|
77
|
+
sortField: CreationFilter["sortField"],
|
|
78
|
+
sortOrder: CreationFilter["sortOrder"],
|
|
79
|
+
): T[] {
|
|
80
|
+
if (!sortField) return creations;
|
|
81
|
+
|
|
82
|
+
return [...creations].sort((a, b) => {
|
|
83
|
+
let aVal: string | number | Date | undefined;
|
|
84
|
+
let bVal: string | number | Date | undefined;
|
|
85
|
+
|
|
86
|
+
switch (sortField) {
|
|
87
|
+
case "createdAt":
|
|
88
|
+
aVal = a.createdAt;
|
|
89
|
+
bVal = b.createdAt;
|
|
90
|
+
break;
|
|
91
|
+
case "updatedAt":
|
|
92
|
+
aVal = a.updatedAt;
|
|
93
|
+
bVal = b.updatedAt;
|
|
94
|
+
break;
|
|
95
|
+
case "type":
|
|
96
|
+
aVal = a.type;
|
|
97
|
+
bVal = b.type;
|
|
98
|
+
break;
|
|
99
|
+
case "status":
|
|
100
|
+
aVal = a.status;
|
|
101
|
+
bVal = b.status;
|
|
102
|
+
break;
|
|
103
|
+
default:
|
|
104
|
+
return 0;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (aVal instanceof Date) aVal = aVal.getTime();
|
|
108
|
+
if (bVal instanceof Date) bVal = bVal.getTime();
|
|
109
|
+
|
|
110
|
+
if (aVal === undefined && bVal === undefined) return 0;
|
|
111
|
+
if (aVal === undefined) return 1;
|
|
112
|
+
if (bVal === undefined) return -1;
|
|
113
|
+
|
|
114
|
+
if (typeof aVal === "string" && typeof bVal === "string") {
|
|
115
|
+
return sortOrder === "desc"
|
|
116
|
+
? bVal.localeCompare(aVal)
|
|
117
|
+
: aVal.localeCompare(bVal);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (typeof aVal === "number" && typeof bVal === "number") {
|
|
121
|
+
return sortOrder === "desc" ? bVal - aVal : aVal - bVal;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return 0;
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function applyAllFilters<T extends FilterableCreation>(
|
|
129
|
+
creations: T[],
|
|
130
|
+
filter: CreationFilter,
|
|
131
|
+
): T[] {
|
|
132
|
+
let result = [...creations];
|
|
133
|
+
|
|
134
|
+
result = filterByType(result, filter.type);
|
|
135
|
+
result = filterByStatus(result, filter.status);
|
|
136
|
+
result = filterBySearch(result, filter.searchQuery);
|
|
137
|
+
result = filterByDateRange(result, filter.startDate, filter.endDate);
|
|
138
|
+
result = sortCreations(result, filter.sortField, filter.sortOrder);
|
|
139
|
+
|
|
140
|
+
if (filter.limit && filter.limit > 0) {
|
|
141
|
+
result = result.slice(0, filter.limit);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return result;
|
|
145
|
+
}
|
|
@@ -6,6 +6,11 @@ export { useCreations } from "./useCreations";
|
|
|
6
6
|
export { useDeleteCreation } from "./useDeleteCreation";
|
|
7
7
|
export { useCreationsFilter } from "./useCreationsFilter";
|
|
8
8
|
export { useAdvancedFilter } from "./useAdvancedFilter";
|
|
9
|
+
export type {
|
|
10
|
+
FilterableCreation,
|
|
11
|
+
UseAdvancedFilterProps,
|
|
12
|
+
UseAdvancedFilterReturn,
|
|
13
|
+
} from "./advancedFilter.types";
|
|
9
14
|
export { useFilter, useStatusFilter, useMediaFilter } from "./useFilter";
|
|
10
15
|
export type { UseFilterProps, UseFilterReturn } from "./useFilter";
|
|
11
16
|
export { useGalleryFilters } from "./useGalleryFilters";
|
|
@@ -4,63 +4,29 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { useState, useMemo, useCallback } from "react";
|
|
7
|
-
import type { CreationFilter,
|
|
7
|
+
import type { CreationFilter, FilterOption } from "../../domain/types";
|
|
8
8
|
import {
|
|
9
9
|
DEFAULT_CREATION_FILTER,
|
|
10
10
|
MEDIA_FILTER_OPTIONS,
|
|
11
11
|
STATUS_FILTER_OPTIONS,
|
|
12
12
|
calculateCreationStats,
|
|
13
|
-
isTypeInCategory,
|
|
14
13
|
} from "../../domain/types";
|
|
15
14
|
import type { CreationCategory, CreationStatus, CreationTypeId } from "../../domain/types";
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
interface UseAdvancedFilterReturn<T extends Creation> {
|
|
33
|
-
// Filtered results
|
|
34
|
-
filtered: T[];
|
|
35
|
-
|
|
36
|
-
// Current filter state
|
|
37
|
-
filter: CreationFilter;
|
|
38
|
-
|
|
39
|
-
// Stats
|
|
40
|
-
stats: CreationStats;
|
|
41
|
-
|
|
42
|
-
// Filter state
|
|
43
|
-
activeMediaFilter: string;
|
|
44
|
-
activeStatusFilter: string;
|
|
45
|
-
hasActiveFilters: boolean;
|
|
46
|
-
|
|
47
|
-
// Filter options with counts
|
|
48
|
-
mediaFilterOptions: FilterOption[];
|
|
49
|
-
statusFilterOptions: FilterOption[];
|
|
50
|
-
|
|
51
|
-
// Actions
|
|
52
|
-
setMediaFilter: (filter: CreationCategory | CreationTypeId) => void;
|
|
53
|
-
setStatusFilter: (status: CreationStatus | "all") => void;
|
|
54
|
-
setSearchQuery: (query: string) => void;
|
|
55
|
-
updateFilter: (update: Partial<CreationFilter>) => void;
|
|
56
|
-
resetFilters: () => void;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Advanced filtering hook for creations
|
|
61
|
-
* Supports category, status, search, and sorting
|
|
62
|
-
*/
|
|
63
|
-
export function useAdvancedFilter<T extends Creation>({
|
|
15
|
+
import type {
|
|
16
|
+
FilterableCreation,
|
|
17
|
+
UseAdvancedFilterProps,
|
|
18
|
+
UseAdvancedFilterReturn,
|
|
19
|
+
} from "./advancedFilter.types";
|
|
20
|
+
import { applyAllFilters } from "./filterHelpers";
|
|
21
|
+
|
|
22
|
+
const EMPTY_STATS = {
|
|
23
|
+
total: 0,
|
|
24
|
+
byCategory: { all: 0, image: 0, video: 0, voice: 0 },
|
|
25
|
+
byStatus: { pending: 0, queued: 0, processing: 0, completed: 0, failed: 0 },
|
|
26
|
+
byType: {},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export function useAdvancedFilter<T extends FilterableCreation>({
|
|
64
30
|
creations,
|
|
65
31
|
initialFilter,
|
|
66
32
|
}: UseAdvancedFilterProps<T>): UseAdvancedFilterReturn<T> {
|
|
@@ -69,158 +35,42 @@ export function useAdvancedFilter<T extends Creation>({
|
|
|
69
35
|
...initialFilter,
|
|
70
36
|
});
|
|
71
37
|
|
|
72
|
-
// Calculate stats from all creations
|
|
73
38
|
const stats = useMemo(() => {
|
|
74
|
-
if (!creations)
|
|
75
|
-
return {
|
|
76
|
-
total: 0,
|
|
77
|
-
byCategory: { all: 0, image: 0, video: 0, voice: 0 },
|
|
78
|
-
byStatus: { pending: 0, queued: 0, processing: 0, completed: 0, failed: 0 },
|
|
79
|
-
byType: {},
|
|
80
|
-
};
|
|
81
|
-
}
|
|
39
|
+
if (!creations) return EMPTY_STATS;
|
|
82
40
|
return calculateCreationStats(creations);
|
|
83
41
|
}, [creations]);
|
|
84
42
|
|
|
85
|
-
// Filter creations
|
|
86
43
|
const filtered = useMemo(() => {
|
|
87
44
|
if (!creations) return [];
|
|
88
|
-
|
|
89
|
-
let result = [...creations];
|
|
90
|
-
|
|
91
|
-
// Filter by type/category
|
|
92
|
-
if (filter.type && filter.type !== "all") {
|
|
93
|
-
const filterType = filter.type;
|
|
94
|
-
|
|
95
|
-
// Check if it's a category
|
|
96
|
-
if (["image", "video", "voice"].includes(filterType)) {
|
|
97
|
-
const category = filterType as CreationCategory;
|
|
98
|
-
result = result.filter((c) =>
|
|
99
|
-
c.type && isTypeInCategory(c.type as CreationTypeId, category)
|
|
100
|
-
);
|
|
101
|
-
} else {
|
|
102
|
-
// It's a specific type
|
|
103
|
-
result = result.filter((c) => c.type === filterType);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Filter by status
|
|
108
|
-
if (filter.status && filter.status !== "all") {
|
|
109
|
-
result = result.filter((c) => c.status === filter.status);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Filter by search query
|
|
113
|
-
if (filter.searchQuery && filter.searchQuery.trim()) {
|
|
114
|
-
const query = filter.searchQuery.toLowerCase().trim();
|
|
115
|
-
result = result.filter((c) => {
|
|
116
|
-
const prompt = c.prompt?.toLowerCase() || "";
|
|
117
|
-
const type = c.type?.toLowerCase() || "";
|
|
118
|
-
return prompt.includes(query) || type.includes(query);
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Filter by date range
|
|
123
|
-
if (filter.startDate) {
|
|
124
|
-
result = result.filter((c) => {
|
|
125
|
-
const createdAt = c.createdAt instanceof Date
|
|
126
|
-
? c.createdAt.getTime()
|
|
127
|
-
: (c.createdAt || 0);
|
|
128
|
-
return createdAt >= filter.startDate!;
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (filter.endDate) {
|
|
133
|
-
result = result.filter((c) => {
|
|
134
|
-
const createdAt = c.createdAt instanceof Date
|
|
135
|
-
? c.createdAt.getTime()
|
|
136
|
-
: (c.createdAt || 0);
|
|
137
|
-
return createdAt <= filter.endDate!;
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Sort
|
|
142
|
-
if (filter.sortField) {
|
|
143
|
-
result.sort((a, b) => {
|
|
144
|
-
let aVal: string | number | Date | undefined;
|
|
145
|
-
let bVal: string | number | Date | undefined;
|
|
146
|
-
|
|
147
|
-
switch (filter.sortField) {
|
|
148
|
-
case "createdAt":
|
|
149
|
-
aVal = a.createdAt;
|
|
150
|
-
bVal = b.createdAt;
|
|
151
|
-
break;
|
|
152
|
-
case "updatedAt":
|
|
153
|
-
aVal = a.updatedAt;
|
|
154
|
-
bVal = b.updatedAt;
|
|
155
|
-
break;
|
|
156
|
-
case "type":
|
|
157
|
-
aVal = a.type;
|
|
158
|
-
bVal = b.type;
|
|
159
|
-
break;
|
|
160
|
-
case "status":
|
|
161
|
-
aVal = a.status;
|
|
162
|
-
bVal = b.status;
|
|
163
|
-
break;
|
|
164
|
-
default:
|
|
165
|
-
return 0;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Convert dates to numbers for comparison
|
|
169
|
-
if (aVal instanceof Date) aVal = aVal.getTime();
|
|
170
|
-
if (bVal instanceof Date) bVal = bVal.getTime();
|
|
171
|
-
|
|
172
|
-
// Handle undefined values
|
|
173
|
-
if (aVal === undefined && bVal === undefined) return 0;
|
|
174
|
-
if (aVal === undefined) return 1;
|
|
175
|
-
if (bVal === undefined) return -1;
|
|
176
|
-
|
|
177
|
-
// Compare
|
|
178
|
-
if (typeof aVal === "string" && typeof bVal === "string") {
|
|
179
|
-
return filter.sortOrder === "desc"
|
|
180
|
-
? bVal.localeCompare(aVal)
|
|
181
|
-
: aVal.localeCompare(bVal);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (typeof aVal === "number" && typeof bVal === "number") {
|
|
185
|
-
return filter.sortOrder === "desc" ? bVal - aVal : aVal - bVal;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
return 0;
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Apply limit
|
|
193
|
-
if (filter.limit && filter.limit > 0) {
|
|
194
|
-
result = result.slice(0, filter.limit);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
return result;
|
|
45
|
+
return applyAllFilters(creations, filter);
|
|
198
46
|
}, [creations, filter]);
|
|
199
47
|
|
|
200
|
-
// Media filter options with counts
|
|
201
48
|
const mediaFilterOptions = useMemo<FilterOption[]>(() => {
|
|
202
49
|
return MEDIA_FILTER_OPTIONS.map((opt) => ({
|
|
203
50
|
...opt,
|
|
204
|
-
count:
|
|
205
|
-
|
|
206
|
-
|
|
51
|
+
count:
|
|
52
|
+
opt.id === "all"
|
|
53
|
+
? stats.total
|
|
54
|
+
: stats.byCategory[opt.id as CreationCategory] || 0,
|
|
207
55
|
}));
|
|
208
56
|
}, [stats]);
|
|
209
57
|
|
|
210
|
-
// Status filter options with counts
|
|
211
58
|
const statusFilterOptions = useMemo<FilterOption[]>(() => {
|
|
212
59
|
return STATUS_FILTER_OPTIONS.map((opt) => ({
|
|
213
60
|
...opt,
|
|
214
|
-
count:
|
|
215
|
-
|
|
216
|
-
|
|
61
|
+
count:
|
|
62
|
+
opt.id === "all"
|
|
63
|
+
? stats.total
|
|
64
|
+
: stats.byStatus[opt.id as CreationStatus] || 0,
|
|
217
65
|
}));
|
|
218
66
|
}, [stats]);
|
|
219
67
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
68
|
+
const setMediaFilter = useCallback(
|
|
69
|
+
(type: CreationCategory | CreationTypeId) => {
|
|
70
|
+
setFilter((prev) => ({ ...prev, type }));
|
|
71
|
+
},
|
|
72
|
+
[],
|
|
73
|
+
);
|
|
224
74
|
|
|
225
75
|
const setStatusFilter = useCallback((status: CreationStatus | "all") => {
|
|
226
76
|
setFilter((prev) => ({ ...prev, status }));
|
|
@@ -238,8 +88,8 @@ export function useAdvancedFilter<T extends Creation>({
|
|
|
238
88
|
setFilter(DEFAULT_CREATION_FILTER);
|
|
239
89
|
}, []);
|
|
240
90
|
|
|
241
|
-
|
|
242
|
-
|
|
91
|
+
const hasActiveFilters =
|
|
92
|
+
filter.type !== "all" || filter.status !== "all" || !!filter.searchQuery;
|
|
243
93
|
const activeMediaFilter = (filter.type as string) || "all";
|
|
244
94
|
const activeStatusFilter = (filter.status as string) || "all";
|
|
245
95
|
|
|
@@ -26,7 +26,7 @@ export function parseFlashcardsFromResponse(
|
|
|
26
26
|
return rawFlashcards.map((item, index) => mapToGeneratedFlashcard(item, request, index));
|
|
27
27
|
} catch (error) {
|
|
28
28
|
if (__DEV__) {
|
|
29
|
-
|
|
29
|
+
|
|
30
30
|
console.error("Failed to parse AI response:", error);
|
|
31
31
|
}
|
|
32
32
|
return [];
|