@umituz/react-native-ai-generation-content 1.16.0 → 1.17.1
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 +13 -14
- package/src/domains/creations/domain/entities/Creation.ts +33 -2
- package/src/domains/creations/domain/entities/index.ts +1 -1
- package/src/domains/creations/domain/types/creation-categories.ts +133 -0
- package/src/domains/creations/domain/types/creation-filter.ts +131 -0
- package/src/domains/creations/domain/types/creation-types.ts +63 -0
- package/src/domains/creations/domain/types/index.ts +44 -0
- package/src/domains/creations/domain/utils/creation-helpers.ts +134 -0
- package/src/domains/creations/domain/utils/index.ts +8 -0
- package/src/domains/creations/domain/utils/preview-helpers.ts +84 -0
- package/src/domains/creations/domain/utils/status-helpers.ts +90 -0
- package/src/domains/creations/index.ts +95 -21
- package/src/domains/creations/infrastructure/repositories/CreationsRepository.ts +14 -1
- package/src/domains/creations/presentation/components/CreationActions.tsx +120 -0
- package/src/domains/creations/presentation/components/CreationBadges.tsx +111 -0
- package/src/domains/creations/presentation/components/CreationCard.tsx +201 -102
- package/src/domains/creations/presentation/components/CreationPreview.tsx +117 -0
- package/src/domains/creations/presentation/components/CreationsFilterBar.tsx +254 -0
- package/src/domains/creations/presentation/components/CreationsGrid.tsx +121 -68
- package/src/domains/creations/presentation/components/index.ts +23 -3
- package/src/domains/creations/presentation/hooks/index.ts +1 -0
- package/src/domains/creations/presentation/hooks/useAdvancedFilter.ts +262 -0
- package/src/domains/creations/presentation/screens/CreationsGalleryScreen.tsx +5 -6
- package/src/features/ai-hug/domain/index.ts +5 -0
- package/src/features/ai-hug/domain/types/ai-hug.types.ts +72 -0
- package/src/features/ai-hug/domain/types/index.ts +14 -0
- package/src/features/ai-hug/index.ts +27 -0
- package/src/features/ai-hug/infrastructure/index.ts +5 -0
- package/src/features/ai-hug/infrastructure/services/ai-hug-executor.ts +96 -0
- package/src/features/ai-hug/infrastructure/services/index.ts +6 -0
- package/src/features/ai-hug/presentation/hooks/index.ts +9 -0
- package/src/features/ai-hug/presentation/hooks/useAIHugFeature.ts +157 -0
- package/src/features/ai-hug/presentation/index.ts +5 -0
- package/src/features/ai-kiss/domain/index.ts +5 -0
- package/src/features/ai-kiss/domain/types/ai-kiss.types.ts +72 -0
- package/src/features/ai-kiss/domain/types/index.ts +14 -0
- package/src/features/ai-kiss/index.ts +27 -0
- package/src/features/ai-kiss/infrastructure/index.ts +5 -0
- package/src/features/ai-kiss/infrastructure/services/ai-kiss-executor.ts +96 -0
- package/src/features/ai-kiss/infrastructure/services/index.ts +6 -0
- package/src/features/ai-kiss/presentation/hooks/index.ts +9 -0
- package/src/features/ai-kiss/presentation/hooks/useAIKissFeature.ts +157 -0
- package/src/features/ai-kiss/presentation/index.ts +5 -0
- package/src/features/anime-selfie/domain/index.ts +5 -0
- package/src/features/anime-selfie/domain/types/anime-selfie.types.ts +72 -0
- package/src/features/anime-selfie/domain/types/index.ts +15 -0
- package/src/features/anime-selfie/index.ts +28 -0
- package/src/features/anime-selfie/infrastructure/index.ts +5 -0
- package/src/features/anime-selfie/infrastructure/services/anime-selfie-executor.ts +95 -0
- package/src/features/anime-selfie/infrastructure/services/index.ts +6 -0
- package/src/features/anime-selfie/presentation/hooks/index.ts +9 -0
- package/src/features/anime-selfie/presentation/hooks/useAnimeSelfieFeature.ts +138 -0
- package/src/features/anime-selfie/presentation/index.ts +5 -0
- package/src/features/background/domain/types/index.ts +15 -0
- package/src/features/background/domain/types/replace-background.types.ts +82 -0
- package/src/features/background/index.ts +31 -3
- package/src/features/background/infrastructure/index.ts +5 -0
- package/src/features/background/infrastructure/services/index.ts +6 -0
- package/src/features/background/infrastructure/services/replace-background-executor.ts +95 -0
- package/src/features/background/presentation/hooks/index.ts +6 -1
- package/src/features/background/presentation/hooks/useReplaceBackgroundFeature.ts +160 -0
- package/src/features/face-swap/domain/index.ts +5 -0
- package/src/features/face-swap/domain/types/face-swap.types.ts +72 -0
- package/src/features/face-swap/domain/types/index.ts +14 -0
- package/src/features/face-swap/index.ts +27 -1
- package/src/features/face-swap/infrastructure/index.ts +5 -0
- package/src/features/face-swap/infrastructure/services/face-swap-executor.ts +96 -0
- package/src/features/face-swap/infrastructure/services/index.ts +6 -0
- package/src/features/face-swap/presentation/hooks/index.ts +9 -0
- package/src/features/face-swap/presentation/hooks/useFaceSwapFeature.ts +157 -0
- package/src/features/face-swap/presentation/index.ts +5 -0
- package/src/features/photo-restoration/domain/index.ts +1 -0
- package/src/features/photo-restoration/domain/types/index.ts +10 -0
- package/src/features/photo-restoration/domain/types/photo-restore.types.ts +71 -0
- package/src/features/photo-restoration/index.ts +34 -1
- package/src/features/photo-restoration/infrastructure/index.ts +1 -0
- package/src/features/photo-restoration/infrastructure/services/index.ts +2 -0
- package/src/features/photo-restoration/infrastructure/services/photo-restore-executor.ts +98 -0
- package/src/features/photo-restoration/presentation/components/PhotoRestoreFeature.tsx +175 -0
- package/src/features/photo-restoration/presentation/components/PhotoRestoreResultView.tsx +98 -0
- package/src/features/photo-restoration/presentation/components/index.ts +4 -0
- package/src/features/photo-restoration/presentation/hooks/index.ts +5 -0
- package/src/features/photo-restoration/presentation/hooks/usePhotoRestoreFeature.ts +137 -0
- package/src/features/photo-restoration/presentation/index.ts +2 -0
- package/src/features/remove-background/domain/index.ts +5 -0
- package/src/features/remove-background/domain/types/index.ts +14 -0
- package/src/features/remove-background/domain/types/remove-background.types.ts +69 -0
- package/src/features/remove-background/index.ts +27 -0
- package/src/features/remove-background/infrastructure/index.ts +5 -0
- package/src/features/remove-background/infrastructure/services/index.ts +6 -0
- package/src/features/remove-background/infrastructure/services/remove-background-executor.ts +95 -0
- package/src/features/remove-background/presentation/hooks/index.ts +9 -0
- package/src/features/remove-background/presentation/hooks/useRemoveBackgroundFeature.ts +137 -0
- package/src/features/remove-background/presentation/index.ts +5 -0
- package/src/features/remove-object/domain/index.ts +5 -0
- package/src/features/remove-object/domain/types/index.ts +14 -0
- package/src/features/remove-object/domain/types/remove-object.types.ts +77 -0
- package/src/features/remove-object/index.ts +27 -0
- package/src/features/remove-object/infrastructure/index.ts +5 -0
- package/src/features/remove-object/infrastructure/services/index.ts +6 -0
- package/src/features/remove-object/infrastructure/services/remove-object-executor.ts +99 -0
- package/src/features/remove-object/presentation/hooks/index.ts +9 -0
- package/src/features/remove-object/presentation/hooks/useRemoveObjectFeature.ts +168 -0
- package/src/features/remove-object/presentation/index.ts +5 -0
- package/src/features/upscaling/domain/types/index.ts +0 -1
- package/src/features/upscaling/domain/types/upscale.types.ts +14 -0
- package/src/features/upscaling/index.ts +3 -11
- package/src/features/upscaling/infrastructure/services/index.ts +1 -6
- package/src/features/upscaling/infrastructure/services/upscale-executor.ts +64 -30
- package/src/features/upscaling/presentation/hooks/useUpscaleFeature.ts +12 -7
- package/src/index.ts +45 -0
- package/src/types/jsx.d.ts +19 -0
- package/src/features/face-swap/domain/entities.ts +0 -48
- package/src/features/photo-restoration/domain/entities.ts +0 -48
- package/src/features/upscaling/domain/types/provider.types.ts +0 -23
- package/src/features/upscaling/infrastructure/services/upscale-provider-registry.ts +0 -77
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useAdvancedFilter Hook
|
|
3
|
+
* Advanced filtering with category, status, and search support
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useState, useMemo, useCallback } from "react";
|
|
7
|
+
import type { CreationFilter, CreationStats, FilterOption } from "../../domain/types";
|
|
8
|
+
import {
|
|
9
|
+
DEFAULT_CREATION_FILTER,
|
|
10
|
+
MEDIA_FILTER_OPTIONS,
|
|
11
|
+
STATUS_FILTER_OPTIONS,
|
|
12
|
+
calculateCreationStats,
|
|
13
|
+
getTypesForCategory,
|
|
14
|
+
isTypeInCategory,
|
|
15
|
+
} from "../../domain/types";
|
|
16
|
+
import type { CreationCategory, CreationStatus, CreationTypeId } from "../../domain/types";
|
|
17
|
+
|
|
18
|
+
interface Creation {
|
|
19
|
+
id: string;
|
|
20
|
+
type?: string;
|
|
21
|
+
status?: string;
|
|
22
|
+
prompt?: string;
|
|
23
|
+
createdAt?: Date | number;
|
|
24
|
+
updatedAt?: Date | number;
|
|
25
|
+
metadata?: Record<string, unknown>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface UseAdvancedFilterProps<T extends Creation> {
|
|
29
|
+
creations: T[] | undefined;
|
|
30
|
+
initialFilter?: Partial<CreationFilter>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface UseAdvancedFilterReturn<T extends Creation> {
|
|
34
|
+
// Filtered results
|
|
35
|
+
filtered: T[];
|
|
36
|
+
|
|
37
|
+
// Current filter state
|
|
38
|
+
filter: CreationFilter;
|
|
39
|
+
|
|
40
|
+
// Stats
|
|
41
|
+
stats: CreationStats;
|
|
42
|
+
|
|
43
|
+
// Filter state
|
|
44
|
+
activeMediaFilter: string;
|
|
45
|
+
activeStatusFilter: string;
|
|
46
|
+
hasActiveFilters: boolean;
|
|
47
|
+
|
|
48
|
+
// Filter options with counts
|
|
49
|
+
mediaFilterOptions: FilterOption[];
|
|
50
|
+
statusFilterOptions: FilterOption[];
|
|
51
|
+
|
|
52
|
+
// Actions
|
|
53
|
+
setMediaFilter: (filter: CreationCategory | CreationTypeId) => void;
|
|
54
|
+
setStatusFilter: (status: CreationStatus | "all") => void;
|
|
55
|
+
setSearchQuery: (query: string) => void;
|
|
56
|
+
updateFilter: (update: Partial<CreationFilter>) => void;
|
|
57
|
+
resetFilters: () => void;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Advanced filtering hook for creations
|
|
62
|
+
* Supports category, status, search, and sorting
|
|
63
|
+
*/
|
|
64
|
+
export function useAdvancedFilter<T extends Creation>({
|
|
65
|
+
creations,
|
|
66
|
+
initialFilter,
|
|
67
|
+
}: UseAdvancedFilterProps<T>): UseAdvancedFilterReturn<T> {
|
|
68
|
+
const [filter, setFilter] = useState<CreationFilter>({
|
|
69
|
+
...DEFAULT_CREATION_FILTER,
|
|
70
|
+
...initialFilter,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Calculate stats from all creations
|
|
74
|
+
const stats = useMemo(() => {
|
|
75
|
+
if (!creations) {
|
|
76
|
+
return {
|
|
77
|
+
total: 0,
|
|
78
|
+
byCategory: { all: 0, image: 0, video: 0, voice: 0 },
|
|
79
|
+
byStatus: { pending: 0, queued: 0, processing: 0, completed: 0, failed: 0 },
|
|
80
|
+
byType: {},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return calculateCreationStats(creations);
|
|
84
|
+
}, [creations]);
|
|
85
|
+
|
|
86
|
+
// Filter creations
|
|
87
|
+
const filtered = useMemo(() => {
|
|
88
|
+
if (!creations) return [];
|
|
89
|
+
|
|
90
|
+
let result = [...creations];
|
|
91
|
+
|
|
92
|
+
// Filter by type/category
|
|
93
|
+
if (filter.type && filter.type !== "all") {
|
|
94
|
+
const filterType = filter.type;
|
|
95
|
+
|
|
96
|
+
// Check if it's a category
|
|
97
|
+
if (["image", "video", "voice"].includes(filterType)) {
|
|
98
|
+
const category = filterType as CreationCategory;
|
|
99
|
+
result = result.filter((c) =>
|
|
100
|
+
c.type && isTypeInCategory(c.type as CreationTypeId, category)
|
|
101
|
+
);
|
|
102
|
+
} else {
|
|
103
|
+
// It's a specific type
|
|
104
|
+
result = result.filter((c) => c.type === filterType);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Filter by status
|
|
109
|
+
if (filter.status && filter.status !== "all") {
|
|
110
|
+
result = result.filter((c) => c.status === filter.status);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Filter by search query
|
|
114
|
+
if (filter.searchQuery && filter.searchQuery.trim()) {
|
|
115
|
+
const query = filter.searchQuery.toLowerCase().trim();
|
|
116
|
+
result = result.filter((c) => {
|
|
117
|
+
const prompt = c.prompt?.toLowerCase() || "";
|
|
118
|
+
const type = c.type?.toLowerCase() || "";
|
|
119
|
+
return prompt.includes(query) || type.includes(query);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Filter by date range
|
|
124
|
+
if (filter.startDate) {
|
|
125
|
+
result = result.filter((c) => {
|
|
126
|
+
const createdAt = c.createdAt instanceof Date
|
|
127
|
+
? c.createdAt.getTime()
|
|
128
|
+
: (c.createdAt || 0);
|
|
129
|
+
return createdAt >= filter.startDate!;
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (filter.endDate) {
|
|
134
|
+
result = result.filter((c) => {
|
|
135
|
+
const createdAt = c.createdAt instanceof Date
|
|
136
|
+
? c.createdAt.getTime()
|
|
137
|
+
: (c.createdAt || 0);
|
|
138
|
+
return createdAt <= filter.endDate!;
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Sort
|
|
143
|
+
if (filter.sortField) {
|
|
144
|
+
result.sort((a, b) => {
|
|
145
|
+
let aVal: string | number | Date | undefined;
|
|
146
|
+
let bVal: string | number | Date | undefined;
|
|
147
|
+
|
|
148
|
+
switch (filter.sortField) {
|
|
149
|
+
case "createdAt":
|
|
150
|
+
aVal = a.createdAt;
|
|
151
|
+
bVal = b.createdAt;
|
|
152
|
+
break;
|
|
153
|
+
case "updatedAt":
|
|
154
|
+
aVal = a.updatedAt;
|
|
155
|
+
bVal = b.updatedAt;
|
|
156
|
+
break;
|
|
157
|
+
case "type":
|
|
158
|
+
aVal = a.type;
|
|
159
|
+
bVal = b.type;
|
|
160
|
+
break;
|
|
161
|
+
case "status":
|
|
162
|
+
aVal = a.status;
|
|
163
|
+
bVal = b.status;
|
|
164
|
+
break;
|
|
165
|
+
default:
|
|
166
|
+
return 0;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Convert dates to numbers for comparison
|
|
170
|
+
if (aVal instanceof Date) aVal = aVal.getTime();
|
|
171
|
+
if (bVal instanceof Date) bVal = bVal.getTime();
|
|
172
|
+
|
|
173
|
+
// Handle undefined values
|
|
174
|
+
if (aVal === undefined && bVal === undefined) return 0;
|
|
175
|
+
if (aVal === undefined) return 1;
|
|
176
|
+
if (bVal === undefined) return -1;
|
|
177
|
+
|
|
178
|
+
// Compare
|
|
179
|
+
if (typeof aVal === "string" && typeof bVal === "string") {
|
|
180
|
+
return filter.sortOrder === "desc"
|
|
181
|
+
? bVal.localeCompare(aVal)
|
|
182
|
+
: aVal.localeCompare(bVal);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (typeof aVal === "number" && typeof bVal === "number") {
|
|
186
|
+
return filter.sortOrder === "desc" ? bVal - aVal : aVal - bVal;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return 0;
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Apply limit
|
|
194
|
+
if (filter.limit && filter.limit > 0) {
|
|
195
|
+
result = result.slice(0, filter.limit);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return result;
|
|
199
|
+
}, [creations, filter]);
|
|
200
|
+
|
|
201
|
+
// Media filter options with counts
|
|
202
|
+
const mediaFilterOptions = useMemo<FilterOption[]>(() => {
|
|
203
|
+
return MEDIA_FILTER_OPTIONS.map((opt) => ({
|
|
204
|
+
...opt,
|
|
205
|
+
count: opt.id === "all"
|
|
206
|
+
? stats.total
|
|
207
|
+
: stats.byCategory[opt.id as CreationCategory] || 0,
|
|
208
|
+
}));
|
|
209
|
+
}, [stats]);
|
|
210
|
+
|
|
211
|
+
// Status filter options with counts
|
|
212
|
+
const statusFilterOptions = useMemo<FilterOption[]>(() => {
|
|
213
|
+
return STATUS_FILTER_OPTIONS.map((opt) => ({
|
|
214
|
+
...opt,
|
|
215
|
+
count: opt.id === "all"
|
|
216
|
+
? stats.total
|
|
217
|
+
: stats.byStatus[opt.id as CreationStatus] || 0,
|
|
218
|
+
}));
|
|
219
|
+
}, [stats]);
|
|
220
|
+
|
|
221
|
+
// Actions
|
|
222
|
+
const setMediaFilter = useCallback((type: CreationCategory | CreationTypeId) => {
|
|
223
|
+
setFilter((prev) => ({ ...prev, type }));
|
|
224
|
+
}, []);
|
|
225
|
+
|
|
226
|
+
const setStatusFilter = useCallback((status: CreationStatus | "all") => {
|
|
227
|
+
setFilter((prev) => ({ ...prev, status }));
|
|
228
|
+
}, []);
|
|
229
|
+
|
|
230
|
+
const setSearchQuery = useCallback((searchQuery: string) => {
|
|
231
|
+
setFilter((prev) => ({ ...prev, searchQuery }));
|
|
232
|
+
}, []);
|
|
233
|
+
|
|
234
|
+
const updateFilter = useCallback((update: Partial<CreationFilter>) => {
|
|
235
|
+
setFilter((prev) => ({ ...prev, ...update }));
|
|
236
|
+
}, []);
|
|
237
|
+
|
|
238
|
+
const resetFilters = useCallback(() => {
|
|
239
|
+
setFilter(DEFAULT_CREATION_FILTER);
|
|
240
|
+
}, []);
|
|
241
|
+
|
|
242
|
+
// Derived state
|
|
243
|
+
const hasActiveFilters = filter.type !== "all" || filter.status !== "all" || !!filter.searchQuery;
|
|
244
|
+
const activeMediaFilter = (filter.type as string) || "all";
|
|
245
|
+
const activeStatusFilter = (filter.status as string) || "all";
|
|
246
|
+
|
|
247
|
+
return {
|
|
248
|
+
filtered,
|
|
249
|
+
filter,
|
|
250
|
+
stats,
|
|
251
|
+
activeMediaFilter,
|
|
252
|
+
activeStatusFilter,
|
|
253
|
+
hasActiveFilters,
|
|
254
|
+
mediaFilterOptions,
|
|
255
|
+
statusFilterOptions,
|
|
256
|
+
setMediaFilter,
|
|
257
|
+
setStatusFilter,
|
|
258
|
+
setSearchQuery,
|
|
259
|
+
updateFilter,
|
|
260
|
+
resetFilters,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
@@ -196,14 +196,13 @@ function CreationsGalleryScreenContent({
|
|
|
196
196
|
>
|
|
197
197
|
{/* Main Content Grid - handles empty/loading via ListEmptyComponent */}
|
|
198
198
|
<CreationsGrid
|
|
199
|
-
creations={filtered}
|
|
199
|
+
creations={filtered as any}
|
|
200
200
|
isLoading={isLoading}
|
|
201
201
|
onRefresh={() => void refetch()}
|
|
202
|
-
|
|
203
|
-
onShare={handleShare}
|
|
204
|
-
onDelete={handleDelete}
|
|
205
|
-
onFavorite={handleFavorite}
|
|
206
|
-
locale={locale}
|
|
202
|
+
onPress={handleView}
|
|
203
|
+
onShare={async (creation) => handleShare(creation as any)}
|
|
204
|
+
onDelete={(creation) => handleDelete(creation as any)}
|
|
205
|
+
onFavorite={(creation) => handleFavorite(creation as any, !(creation as any).isFavorite)}
|
|
207
206
|
contentContainerStyle={{ paddingBottom: tokens.spacing.xl }}
|
|
208
207
|
ListEmptyComponent={renderEmptyComponent}
|
|
209
208
|
/>
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Hug Feature Types
|
|
3
|
+
* Request, Result, Config types for AI hug generation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface AIHugOptions {
|
|
7
|
+
intensity?: "gentle" | "warm" | "tight";
|
|
8
|
+
preserveFaces?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface AIHugRequest {
|
|
12
|
+
sourceImageUri: string;
|
|
13
|
+
targetImageUri: string;
|
|
14
|
+
sourceImageBase64?: string;
|
|
15
|
+
targetImageBase64?: string;
|
|
16
|
+
userId: string;
|
|
17
|
+
options?: AIHugOptions;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface AIHugResult {
|
|
21
|
+
success: boolean;
|
|
22
|
+
imageUrl?: string;
|
|
23
|
+
imageBase64?: string;
|
|
24
|
+
error?: string;
|
|
25
|
+
requestId?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface AIHugFeatureState {
|
|
29
|
+
sourceImageUri: string | null;
|
|
30
|
+
targetImageUri: string | null;
|
|
31
|
+
processedUrl: string | null;
|
|
32
|
+
isProcessing: boolean;
|
|
33
|
+
progress: number;
|
|
34
|
+
error: string | null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface AIHugTranslations {
|
|
38
|
+
sourceUploadTitle: string;
|
|
39
|
+
sourceUploadSubtitle: string;
|
|
40
|
+
targetUploadTitle: string;
|
|
41
|
+
targetUploadSubtitle: string;
|
|
42
|
+
uploadChange: string;
|
|
43
|
+
uploadAnalyzing: string;
|
|
44
|
+
description: string;
|
|
45
|
+
processingText: string;
|
|
46
|
+
processButtonText: string;
|
|
47
|
+
successText: string;
|
|
48
|
+
saveButtonText: string;
|
|
49
|
+
tryAnotherText: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type AIHugInputBuilder = (
|
|
53
|
+
sourceBase64: string,
|
|
54
|
+
targetBase64: string,
|
|
55
|
+
options?: AIHugOptions,
|
|
56
|
+
) => Record<string, unknown>;
|
|
57
|
+
|
|
58
|
+
export type AIHugResultExtractor = (result: unknown) => string | undefined;
|
|
59
|
+
|
|
60
|
+
export interface AIHugFeatureConfig {
|
|
61
|
+
providerId?: string;
|
|
62
|
+
creditCost?: number;
|
|
63
|
+
model: string;
|
|
64
|
+
buildInput: AIHugInputBuilder;
|
|
65
|
+
extractResult?: AIHugResultExtractor;
|
|
66
|
+
prepareImage: (imageUri: string) => Promise<string>;
|
|
67
|
+
onSourceImageSelect?: (uri: string) => void;
|
|
68
|
+
onTargetImageSelect?: (uri: string) => void;
|
|
69
|
+
onProcessingStart?: () => void;
|
|
70
|
+
onProcessingComplete?: (result: AIHugResult) => void;
|
|
71
|
+
onError?: (error: string) => void;
|
|
72
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Hug Feature
|
|
3
|
+
* Provider-agnostic AI hug generation feature
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Domain Types
|
|
7
|
+
export type {
|
|
8
|
+
AIHugOptions,
|
|
9
|
+
AIHugRequest,
|
|
10
|
+
AIHugResult,
|
|
11
|
+
AIHugFeatureState,
|
|
12
|
+
AIHugTranslations,
|
|
13
|
+
AIHugFeatureConfig,
|
|
14
|
+
AIHugInputBuilder,
|
|
15
|
+
AIHugResultExtractor,
|
|
16
|
+
} from "./domain";
|
|
17
|
+
|
|
18
|
+
// Infrastructure Services
|
|
19
|
+
export { executeAIHug, hasAIHugSupport } from "./infrastructure";
|
|
20
|
+
export type { ExecuteAIHugOptions } from "./infrastructure";
|
|
21
|
+
|
|
22
|
+
// Presentation Hooks
|
|
23
|
+
export { useAIHugFeature } from "./presentation";
|
|
24
|
+
export type {
|
|
25
|
+
UseAIHugFeatureProps,
|
|
26
|
+
UseAIHugFeatureReturn,
|
|
27
|
+
} from "./presentation";
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Hug Executor
|
|
3
|
+
* Provider-agnostic AI hug execution using active AI provider
|
|
4
|
+
* Model and input format are provided via options from app level
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { providerRegistry } from "../../../../infrastructure/services";
|
|
8
|
+
import { cleanBase64 } from "../../../../infrastructure/utils";
|
|
9
|
+
import type {
|
|
10
|
+
AIHugRequest,
|
|
11
|
+
AIHugResult,
|
|
12
|
+
AIHugInputBuilder,
|
|
13
|
+
AIHugResultExtractor,
|
|
14
|
+
} from "../../domain/types";
|
|
15
|
+
|
|
16
|
+
declare const __DEV__: boolean;
|
|
17
|
+
|
|
18
|
+
export interface ExecuteAIHugOptions {
|
|
19
|
+
model: string;
|
|
20
|
+
buildInput: AIHugInputBuilder;
|
|
21
|
+
extractResult?: AIHugResultExtractor;
|
|
22
|
+
onProgress?: (progress: number) => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function defaultExtractResult(result: unknown): string | undefined {
|
|
26
|
+
if (typeof result !== "object" || result === null) return undefined;
|
|
27
|
+
|
|
28
|
+
const r = result as Record<string, unknown>;
|
|
29
|
+
|
|
30
|
+
if (typeof r.image === "string") return r.image;
|
|
31
|
+
if (Array.isArray(r.images) && r.images[0]?.url) return r.images[0].url;
|
|
32
|
+
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function executeAIHug(
|
|
37
|
+
request: AIHugRequest,
|
|
38
|
+
options: ExecuteAIHugOptions,
|
|
39
|
+
): Promise<AIHugResult> {
|
|
40
|
+
const provider = providerRegistry.getActiveProvider();
|
|
41
|
+
|
|
42
|
+
if (!provider) {
|
|
43
|
+
return { success: false, error: "No AI provider configured" };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!provider.isInitialized()) {
|
|
47
|
+
return { success: false, error: "AI provider not initialized" };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!request.sourceImageBase64 || !request.targetImageBase64) {
|
|
51
|
+
return { success: false, error: "Both source and target image base64 are required" };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const { model, buildInput, extractResult, onProgress } = options;
|
|
55
|
+
|
|
56
|
+
if (__DEV__) {
|
|
57
|
+
// eslint-disable-next-line no-console
|
|
58
|
+
console.log(`[AIHug] Provider: ${provider.providerId}, Model: ${model}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
onProgress?.(10);
|
|
63
|
+
|
|
64
|
+
const sourceBase64 = cleanBase64(request.sourceImageBase64);
|
|
65
|
+
const targetBase64 = cleanBase64(request.targetImageBase64);
|
|
66
|
+
onProgress?.(30);
|
|
67
|
+
|
|
68
|
+
const input = buildInput(sourceBase64, targetBase64, request.options);
|
|
69
|
+
onProgress?.(40);
|
|
70
|
+
|
|
71
|
+
const result = await provider.run(model, input);
|
|
72
|
+
onProgress?.(90);
|
|
73
|
+
|
|
74
|
+
const extractor = extractResult || defaultExtractResult;
|
|
75
|
+
const imageUrl = extractor(result);
|
|
76
|
+
onProgress?.(100);
|
|
77
|
+
|
|
78
|
+
if (!imageUrl) {
|
|
79
|
+
return { success: false, error: "No image in response" };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return { success: true, imageUrl };
|
|
83
|
+
} catch (error) {
|
|
84
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
85
|
+
if (__DEV__) {
|
|
86
|
+
// eslint-disable-next-line no-console
|
|
87
|
+
console.error("[AIHug] Error:", message);
|
|
88
|
+
}
|
|
89
|
+
return { success: false, error: message };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function hasAIHugSupport(): boolean {
|
|
94
|
+
const provider = providerRegistry.getActiveProvider();
|
|
95
|
+
return provider !== null && provider.isInitialized();
|
|
96
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useAIHugFeature Hook
|
|
3
|
+
* Manages AI hug feature state and actions
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useState, useCallback } from "react";
|
|
7
|
+
import { executeAIHug } from "../../infrastructure/services";
|
|
8
|
+
import type {
|
|
9
|
+
AIHugFeatureState,
|
|
10
|
+
AIHugFeatureConfig,
|
|
11
|
+
AIHugResult,
|
|
12
|
+
} from "../../domain/types";
|
|
13
|
+
|
|
14
|
+
declare const __DEV__: boolean;
|
|
15
|
+
|
|
16
|
+
export interface UseAIHugFeatureProps {
|
|
17
|
+
config: AIHugFeatureConfig;
|
|
18
|
+
userId: string;
|
|
19
|
+
onSelectSourceImage: () => Promise<string | null>;
|
|
20
|
+
onSelectTargetImage: () => Promise<string | null>;
|
|
21
|
+
onSaveImage: (imageUrl: string) => Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface UseAIHugFeatureReturn extends AIHugFeatureState {
|
|
25
|
+
selectSourceImage: () => Promise<void>;
|
|
26
|
+
selectTargetImage: () => Promise<void>;
|
|
27
|
+
process: () => Promise<void>;
|
|
28
|
+
save: () => Promise<void>;
|
|
29
|
+
reset: () => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const initialState: AIHugFeatureState = {
|
|
33
|
+
sourceImageUri: null,
|
|
34
|
+
targetImageUri: null,
|
|
35
|
+
processedUrl: null,
|
|
36
|
+
isProcessing: false,
|
|
37
|
+
progress: 0,
|
|
38
|
+
error: null,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export function useAIHugFeature(
|
|
42
|
+
props: UseAIHugFeatureProps,
|
|
43
|
+
): UseAIHugFeatureReturn {
|
|
44
|
+
const { config, userId, onSelectSourceImage, onSelectTargetImage, onSaveImage } = props;
|
|
45
|
+
const [state, setState] = useState<AIHugFeatureState>(initialState);
|
|
46
|
+
|
|
47
|
+
const selectSourceImage = useCallback(async () => {
|
|
48
|
+
try {
|
|
49
|
+
const uri = await onSelectSourceImage();
|
|
50
|
+
if (uri) {
|
|
51
|
+
setState((prev) => ({ ...prev, sourceImageUri: uri, error: null }));
|
|
52
|
+
config.onSourceImageSelect?.(uri);
|
|
53
|
+
}
|
|
54
|
+
} catch (error) {
|
|
55
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
56
|
+
setState((prev) => ({ ...prev, error: message }));
|
|
57
|
+
}
|
|
58
|
+
}, [onSelectSourceImage, config]);
|
|
59
|
+
|
|
60
|
+
const selectTargetImage = useCallback(async () => {
|
|
61
|
+
try {
|
|
62
|
+
const uri = await onSelectTargetImage();
|
|
63
|
+
if (uri) {
|
|
64
|
+
setState((prev) => ({ ...prev, targetImageUri: uri, error: null }));
|
|
65
|
+
config.onTargetImageSelect?.(uri);
|
|
66
|
+
}
|
|
67
|
+
} catch (error) {
|
|
68
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
69
|
+
setState((prev) => ({ ...prev, error: message }));
|
|
70
|
+
}
|
|
71
|
+
}, [onSelectTargetImage, config]);
|
|
72
|
+
|
|
73
|
+
const handleProgress = useCallback((progress: number) => {
|
|
74
|
+
setState((prev) => ({ ...prev, progress }));
|
|
75
|
+
}, []);
|
|
76
|
+
|
|
77
|
+
const process = useCallback(async () => {
|
|
78
|
+
if (!state.sourceImageUri || !state.targetImageUri) return;
|
|
79
|
+
|
|
80
|
+
setState((prev) => ({
|
|
81
|
+
...prev,
|
|
82
|
+
isProcessing: true,
|
|
83
|
+
progress: 0,
|
|
84
|
+
error: null,
|
|
85
|
+
}));
|
|
86
|
+
|
|
87
|
+
config.onProcessingStart?.();
|
|
88
|
+
|
|
89
|
+
if (__DEV__) {
|
|
90
|
+
// eslint-disable-next-line no-console
|
|
91
|
+
console.log("[useAIHugFeature] Starting AI hug process");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const sourceImageBase64 = await config.prepareImage(state.sourceImageUri);
|
|
95
|
+
const targetImageBase64 = await config.prepareImage(state.targetImageUri);
|
|
96
|
+
|
|
97
|
+
const result: AIHugResult = await executeAIHug(
|
|
98
|
+
{
|
|
99
|
+
sourceImageUri: state.sourceImageUri,
|
|
100
|
+
targetImageUri: state.targetImageUri,
|
|
101
|
+
sourceImageBase64,
|
|
102
|
+
targetImageBase64,
|
|
103
|
+
userId,
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
model: config.model,
|
|
107
|
+
buildInput: config.buildInput,
|
|
108
|
+
extractResult: config.extractResult,
|
|
109
|
+
onProgress: handleProgress,
|
|
110
|
+
},
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
if (result.success && result.imageUrl) {
|
|
114
|
+
const url = result.imageUrl;
|
|
115
|
+
setState((prev) => ({
|
|
116
|
+
...prev,
|
|
117
|
+
isProcessing: false,
|
|
118
|
+
processedUrl: url,
|
|
119
|
+
progress: 100,
|
|
120
|
+
}));
|
|
121
|
+
config.onProcessingComplete?.(result);
|
|
122
|
+
} else {
|
|
123
|
+
const errorMessage = result.error || "Processing failed";
|
|
124
|
+
setState((prev) => ({
|
|
125
|
+
...prev,
|
|
126
|
+
isProcessing: false,
|
|
127
|
+
error: errorMessage,
|
|
128
|
+
progress: 0,
|
|
129
|
+
}));
|
|
130
|
+
config.onError?.(errorMessage);
|
|
131
|
+
}
|
|
132
|
+
}, [state.sourceImageUri, state.targetImageUri, userId, config, handleProgress]);
|
|
133
|
+
|
|
134
|
+
const save = useCallback(async () => {
|
|
135
|
+
if (!state.processedUrl) return;
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
await onSaveImage(state.processedUrl);
|
|
139
|
+
} catch (error) {
|
|
140
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
141
|
+
setState((prev) => ({ ...prev, error: message }));
|
|
142
|
+
}
|
|
143
|
+
}, [state.processedUrl, onSaveImage]);
|
|
144
|
+
|
|
145
|
+
const reset = useCallback(() => {
|
|
146
|
+
setState(initialState);
|
|
147
|
+
}, []);
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
...state,
|
|
151
|
+
selectSourceImage,
|
|
152
|
+
selectTargetImage,
|
|
153
|
+
process,
|
|
154
|
+
save,
|
|
155
|
+
reset,
|
|
156
|
+
};
|
|
157
|
+
}
|