@umituz/react-native-ai-generation-content 1.83.75 → 1.83.76
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/core/index.ts +2 -0
- package/src/domain/interfaces/ai-provider.interface.ts +3 -0
- package/src/domain/interfaces/provider-lifecycle.interface.ts +21 -0
- package/src/domains/generation/infrastructure/executors/image-executor.ts +34 -43
- package/src/domains/generation/wizard/infrastructure/strategies/image-generation.executor.ts +42 -8
- package/src/domains/generation/wizard/infrastructure/utils/creation-update-operations.ts +36 -11
- package/src/infrastructure/services/generation-log-store.ts +84 -0
- package/src/infrastructure/services/multi-image-generation.executor.ts +33 -13
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-generation-content",
|
|
3
|
-
"version": "1.83.
|
|
3
|
+
"version": "1.83.76",
|
|
4
4
|
"description": "Provider-agnostic AI generation orchestration for React Native with result preview components",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
package/src/core/index.ts
CHANGED
|
@@ -42,6 +42,9 @@ export type {
|
|
|
42
42
|
VideoFeatureInputData,
|
|
43
43
|
} from "./ai-provider-input.types";
|
|
44
44
|
|
|
45
|
+
// Lifecycle & Logging
|
|
46
|
+
export type { ProviderLogEntry } from "./provider-lifecycle.interface";
|
|
47
|
+
|
|
45
48
|
// Main Interface Composition (Interface Segregation Principle)
|
|
46
49
|
import type { IAIProviderLifecycle } from "./provider-lifecycle.interface";
|
|
47
50
|
import type { IAIProviderCapabilities } from "./provider-capabilities.interface";
|
|
@@ -5,6 +5,16 @@
|
|
|
5
5
|
|
|
6
6
|
import type { AIProviderConfig } from "./ai-provider.interface";
|
|
7
7
|
|
|
8
|
+
/** Log entry from generation session */
|
|
9
|
+
export interface ProviderLogEntry {
|
|
10
|
+
readonly timestamp: number;
|
|
11
|
+
readonly elapsed: number;
|
|
12
|
+
readonly level: 'info' | 'warn' | 'error';
|
|
13
|
+
readonly tag: string;
|
|
14
|
+
readonly message: string;
|
|
15
|
+
readonly data?: Record<string, unknown>;
|
|
16
|
+
}
|
|
17
|
+
|
|
8
18
|
export interface IAIProviderLifecycle {
|
|
9
19
|
/**
|
|
10
20
|
* Initialize provider with configuration
|
|
@@ -20,4 +30,15 @@ export interface IAIProviderLifecycle {
|
|
|
20
30
|
* Reset provider state to uninitialized
|
|
21
31
|
*/
|
|
22
32
|
reset(): void;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get log entries from the current/last generation session.
|
|
36
|
+
* Returns empty array if not supported.
|
|
37
|
+
*/
|
|
38
|
+
getSessionLogs?(): ProviderLogEntry[];
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* End log session and return all entries. Clears the buffer.
|
|
42
|
+
*/
|
|
43
|
+
endLogSession?(): ProviderLogEntry[];
|
|
23
44
|
}
|
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
import type { GenerationResult } from "../../../../domain/entities/generation.types";
|
|
13
13
|
import { providerRegistry } from "../../../../infrastructure/services/provider-registry.service";
|
|
14
14
|
import { env } from "../../../../infrastructure/config/env.config";
|
|
15
|
+
import { addGenerationLogs, addGenerationLog, startGenerationLogSession } from "../../../../infrastructure/services/generation-log-store";
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
export class ImageExecutor
|
|
@@ -22,46 +23,40 @@ export class ImageExecutor
|
|
|
22
23
|
input: ImageGenerationInput,
|
|
23
24
|
options?: GenerationOptions,
|
|
24
25
|
): Promise<GenerationResult<ImageGenerationOutput>> {
|
|
26
|
+
const TAG = 'GenericImageExecutor';
|
|
27
|
+
const startTime = Date.now();
|
|
28
|
+
|
|
29
|
+
startGenerationLogSession();
|
|
30
|
+
|
|
25
31
|
try {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
32
|
+
const totalImageSize = input.imageUrls?.reduce((sum, url) => sum + url.length, 0) ?? 0;
|
|
33
|
+
addGenerationLog(TAG, 'Starting generation', 'info', {
|
|
34
|
+
model,
|
|
35
|
+
imageCount: input.imageUrls?.length || 0,
|
|
36
|
+
totalImageSizeKB: Math.round(totalImageSize / 1024),
|
|
37
|
+
promptLength: input.prompt?.length || 0,
|
|
38
|
+
});
|
|
34
39
|
|
|
35
40
|
const provider = providerRegistry.getActiveProvider();
|
|
36
41
|
|
|
37
42
|
if (!provider?.isInitialized()) {
|
|
38
|
-
|
|
39
|
-
console.error("[ImageExecutor] Provider not initialized");
|
|
40
|
-
}
|
|
43
|
+
addGenerationLog(TAG, 'Provider not initialized!', 'error');
|
|
41
44
|
return { success: false, error: "AI provider not initialized" };
|
|
42
45
|
}
|
|
43
46
|
|
|
44
47
|
options?.onProgress?.(5);
|
|
45
|
-
|
|
46
48
|
const modelInput = this.buildModelInput(input);
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
aspectRatio: modelInput.aspect_ratio,
|
|
52
|
-
outputFormat: modelInput.output_format,
|
|
53
|
-
});
|
|
54
|
-
}
|
|
49
|
+
addGenerationLog(TAG, 'Model input prepared', 'info', {
|
|
50
|
+
imageCount: modelInput.image_urls?.length || 0,
|
|
51
|
+
aspectRatio: modelInput.aspect_ratio,
|
|
52
|
+
});
|
|
55
53
|
|
|
56
54
|
options?.onProgress?.(10);
|
|
55
|
+
addGenerationLog(TAG, 'Calling provider.subscribe()...');
|
|
57
56
|
|
|
58
57
|
const result = await provider.subscribe(model, modelInput, {
|
|
59
58
|
timeoutMs: options?.timeoutMs ?? env.generationImageTimeoutMs,
|
|
60
59
|
onQueueUpdate: (status) => {
|
|
61
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
62
|
-
console.log("[ImageExecutor] Queue status:", status.status);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
60
|
if (status.status === "IN_QUEUE") {
|
|
66
61
|
options?.onProgress?.(20);
|
|
67
62
|
} else if (status.status === "IN_PROGRESS") {
|
|
@@ -70,37 +65,33 @@ export class ImageExecutor
|
|
|
70
65
|
},
|
|
71
66
|
});
|
|
72
67
|
|
|
73
|
-
|
|
68
|
+
// Collect provider logs
|
|
69
|
+
const providerLogs = provider.endLogSession?.() ?? provider.getSessionLogs?.() ?? [];
|
|
70
|
+
addGenerationLogs(providerLogs);
|
|
74
71
|
|
|
72
|
+
options?.onProgress?.(90);
|
|
75
73
|
const imageUrl = this.extractImageUrl(result);
|
|
74
|
+
const elapsed = Date.now() - startTime;
|
|
76
75
|
|
|
77
76
|
if (!imageUrl) {
|
|
78
|
-
|
|
79
|
-
console.error("[ImageExecutor] No image URL in response");
|
|
80
|
-
}
|
|
77
|
+
addGenerationLog(TAG, `No image URL in response after ${elapsed}ms`, 'error');
|
|
81
78
|
return { success: false, error: "No image generated" };
|
|
82
79
|
}
|
|
83
80
|
|
|
84
81
|
options?.onProgress?.(100);
|
|
85
|
-
|
|
86
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
87
|
-
console.log("[ImageExecutor] Generation successful", {
|
|
88
|
-
imageUrl: imageUrl.slice(0, 100),
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
|
|
82
|
+
addGenerationLog(TAG, `Generation SUCCESS in ${elapsed}ms`, 'info', { imageUrl, elapsed });
|
|
92
83
|
return { success: true, data: { imageUrl } };
|
|
93
84
|
} catch (error) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
message,
|
|
100
|
-
error,
|
|
101
|
-
});
|
|
85
|
+
// Collect provider logs even on failure
|
|
86
|
+
const provider = providerRegistry.getActiveProvider();
|
|
87
|
+
if (provider) {
|
|
88
|
+
const providerLogs = provider.endLogSession?.() ?? provider.getSessionLogs?.() ?? [];
|
|
89
|
+
addGenerationLogs(providerLogs);
|
|
102
90
|
}
|
|
103
91
|
|
|
92
|
+
const elapsed = Date.now() - startTime;
|
|
93
|
+
const message = error instanceof Error ? error.message : "Generation failed";
|
|
94
|
+
addGenerationLog(TAG, `Generation FAILED after ${elapsed}ms: ${message}`, 'error', { elapsed });
|
|
104
95
|
return { success: false, error: message };
|
|
105
96
|
}
|
|
106
97
|
}
|
package/src/domains/generation/wizard/infrastructure/strategies/image-generation.executor.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
DEFAULT_STYLE_VALUE,
|
|
12
12
|
MODEL_INPUT_DEFAULTS,
|
|
13
13
|
} from "./wizard-strategy.constants";
|
|
14
|
+
import { addGenerationLogs, addGenerationLog, startGenerationLogSession } from "../../../../../infrastructure/services/generation-log-store";
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
interface ExecutionResult {
|
|
@@ -51,22 +52,31 @@ export async function executeImageGeneration(
|
|
|
51
52
|
model: string,
|
|
52
53
|
onProgress?: (progress: number) => void,
|
|
53
54
|
): Promise<ExecutionResult> {
|
|
55
|
+
const TAG = 'ImageExecutor';
|
|
56
|
+
const startTime = Date.now();
|
|
57
|
+
startGenerationLogSession();
|
|
54
58
|
const { providerRegistry } = await import("../../../../../infrastructure/services/provider-registry.service");
|
|
55
59
|
|
|
56
60
|
const provider = providerRegistry.getActiveProvider();
|
|
57
61
|
if (!provider?.isInitialized()) {
|
|
62
|
+
addGenerationLog(TAG, 'Provider not initialized!', 'error');
|
|
58
63
|
return { success: false, error: "AI provider not initialized" };
|
|
59
64
|
}
|
|
60
65
|
|
|
61
66
|
try {
|
|
62
67
|
const imageUrls = input.photos.map(formatBase64);
|
|
63
68
|
const finalPrompt = buildFinalPrompt(input, imageUrls);
|
|
69
|
+
const mode = imageUrls.length > 0 ? "Photo-based" : "Text-to-image";
|
|
70
|
+
const totalImageSize = imageUrls.reduce((sum, url) => sum + url.length, 0);
|
|
64
71
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
72
|
+
addGenerationLog(TAG, `${mode} generation starting`, 'info', {
|
|
73
|
+
model,
|
|
74
|
+
photoCount: imageUrls.length,
|
|
75
|
+
totalImageSizeKB: Math.round(totalImageSize / 1024),
|
|
76
|
+
promptLength: finalPrompt.length,
|
|
77
|
+
timeout: GENERATION_TIMEOUT_MS,
|
|
78
|
+
});
|
|
79
|
+
addGenerationLog(TAG, `Prompt: ${finalPrompt.substring(0, 300)}${finalPrompt.length > 300 ? "..." : ""}`);
|
|
70
80
|
|
|
71
81
|
const modelInput: Record<string, unknown> = {
|
|
72
82
|
prompt: finalPrompt,
|
|
@@ -82,21 +92,45 @@ export async function executeImageGeneration(
|
|
|
82
92
|
}
|
|
83
93
|
|
|
84
94
|
let lastStatus = "";
|
|
95
|
+
addGenerationLog(TAG, 'Calling provider.subscribe()...');
|
|
85
96
|
const result = await provider.subscribe(model, modelInput, {
|
|
86
97
|
timeoutMs: GENERATION_TIMEOUT_MS,
|
|
87
98
|
onQueueUpdate: (status) => {
|
|
88
|
-
if (status.status !== lastStatus)
|
|
99
|
+
if (status.status !== lastStatus) {
|
|
100
|
+
lastStatus = status.status;
|
|
101
|
+
}
|
|
89
102
|
},
|
|
90
103
|
});
|
|
91
104
|
|
|
105
|
+
// Collect provider logs after subscribe completes
|
|
106
|
+
const providerLogs = provider.endLogSession?.() ?? provider.getSessionLogs?.() ?? [];
|
|
107
|
+
addGenerationLogs(providerLogs);
|
|
108
|
+
|
|
92
109
|
const rawResult = result as Record<string, unknown>;
|
|
93
110
|
const data = (rawResult?.data ?? rawResult) as { images?: Array<{ url: string }> };
|
|
94
111
|
const imageUrl = data?.images?.[0]?.url;
|
|
95
112
|
|
|
113
|
+
const elapsed = Date.now() - startTime;
|
|
96
114
|
onProgress?.(100);
|
|
97
115
|
|
|
98
|
-
|
|
116
|
+
if (imageUrl) {
|
|
117
|
+
addGenerationLog(TAG, `Generation SUCCESS in ${elapsed}ms`, 'info', { imageUrl, elapsed });
|
|
118
|
+
return { success: true, imageUrl };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
addGenerationLog(TAG, `No image in response after ${elapsed}ms`, 'error', {
|
|
122
|
+
responseKeys: Object.keys(data || {}),
|
|
123
|
+
elapsed,
|
|
124
|
+
});
|
|
125
|
+
return { success: false, error: "No image generated" };
|
|
99
126
|
} catch (error) {
|
|
100
|
-
|
|
127
|
+
// Collect provider logs even on failure
|
|
128
|
+
const providerLogs = provider.endLogSession?.() ?? provider.getSessionLogs?.() ?? [];
|
|
129
|
+
addGenerationLogs(providerLogs);
|
|
130
|
+
|
|
131
|
+
const elapsed = Date.now() - startTime;
|
|
132
|
+
const errorMsg = error instanceof Error ? error.message : "Generation failed";
|
|
133
|
+
addGenerationLog(TAG, `Generation FAILED after ${elapsed}ms: ${errorMsg}`, 'error', { elapsed });
|
|
134
|
+
return { success: false, error: errorMsg };
|
|
101
135
|
}
|
|
102
136
|
}
|
|
@@ -1,13 +1,44 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Creation Update Operations
|
|
3
|
-
* Infrastructure: Updates creation status
|
|
3
|
+
* Infrastructure: Updates creation status and flushes generation logs to Firestore
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { getFirestore, doc, setDoc } from "firebase/firestore";
|
|
6
7
|
import type { ICreationsRepository } from "../../../../creations/domain/repositories";
|
|
7
8
|
import type { Creation, CreationOutput } from "../../../../creations/domain/entities/Creation";
|
|
8
9
|
import type { CompletedCreationData } from "./creation-persistence.types";
|
|
10
|
+
import { consumeGenerationLogs } from "../../../../../infrastructure/services/generation-log-store";
|
|
9
11
|
|
|
10
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Flush generation logs to Firestore subcollection.
|
|
15
|
+
* Writes to: users/{userId}/creations/{creationId}/logs/session
|
|
16
|
+
* Non-blocking: errors are caught and logged, never propagated.
|
|
17
|
+
*/
|
|
18
|
+
async function flushLogsToFirestore(
|
|
19
|
+
userId: string,
|
|
20
|
+
creationId: string,
|
|
21
|
+
): Promise<void> {
|
|
22
|
+
const logs = consumeGenerationLogs();
|
|
23
|
+
if (logs.length === 0) return;
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const db = getFirestore();
|
|
27
|
+
const logDocRef = doc(db, 'users', userId, 'creations', creationId, 'logs', 'session');
|
|
28
|
+
await setDoc(logDocRef, {
|
|
29
|
+
entries: logs,
|
|
30
|
+
count: logs.length,
|
|
31
|
+
createdAt: new Date(),
|
|
32
|
+
});
|
|
33
|
+
} catch (error) {
|
|
34
|
+
// Never let log flushing break the main flow
|
|
35
|
+
console.warn(
|
|
36
|
+
'[CreationPersistence] Failed to flush logs to Firestore:',
|
|
37
|
+
error instanceof Error ? error.message : String(error),
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
11
42
|
/**
|
|
12
43
|
* Update creation to status="completed" when generation finishes
|
|
13
44
|
*/
|
|
@@ -37,9 +68,8 @@ export async function updateToCompleted(
|
|
|
37
68
|
...(durationMs !== undefined && { durationMs }),
|
|
38
69
|
} as Partial<Creation>);
|
|
39
70
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
71
|
+
// Flush generation logs to Firestore — awaited to ensure logs are persisted
|
|
72
|
+
await flushLogsToFirestore(userId, creationId);
|
|
43
73
|
}
|
|
44
74
|
|
|
45
75
|
/**
|
|
@@ -57,9 +87,8 @@ export async function updateToFailed(
|
|
|
57
87
|
completedAt: new Date(),
|
|
58
88
|
} as Partial<Creation>);
|
|
59
89
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
90
|
+
// Flush generation logs to Firestore — awaited to ensure logs are persisted
|
|
91
|
+
await flushLogsToFirestore(userId, creationId);
|
|
63
92
|
}
|
|
64
93
|
|
|
65
94
|
/**
|
|
@@ -76,8 +105,4 @@ export async function updateRequestId(
|
|
|
76
105
|
requestId,
|
|
77
106
|
model,
|
|
78
107
|
});
|
|
79
|
-
|
|
80
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
81
|
-
console.log("[CreationPersistence] Updated requestId", { creationId, requestId, model });
|
|
82
|
-
}
|
|
83
108
|
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generation Log Store
|
|
3
|
+
* Temporarily holds log entries from the most recent generation session.
|
|
4
|
+
* The persistence layer reads from here when saving completed/failed creations.
|
|
5
|
+
*
|
|
6
|
+
* Flow:
|
|
7
|
+
* 1. Executor calls provider.subscribe() -> provider collects logs internally
|
|
8
|
+
* 2. Executor calls provider.endLogSession() -> gets log entries
|
|
9
|
+
* 3. Executor appends provider logs + its own entries here
|
|
10
|
+
* 4. Persistence layer reads logs from here when writing to Firestore
|
|
11
|
+
*
|
|
12
|
+
* IMPORTANT: Always use addGenerationLogs/addGenerationLog to append.
|
|
13
|
+
* clearGenerationLogs should be called at the start of a new generation.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { ProviderLogEntry } from "../../domain/interfaces/ai-provider.interface";
|
|
17
|
+
|
|
18
|
+
let sessionLogs: ProviderLogEntry[] = [];
|
|
19
|
+
let sessionStartTime: number = 0;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Clear logs and start a new session. Call at the beginning of each generation.
|
|
23
|
+
*/
|
|
24
|
+
export function startGenerationLogSession(): void {
|
|
25
|
+
sessionLogs = [];
|
|
26
|
+
sessionStartTime = Date.now();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Append log entries to the current session (e.g. provider logs)
|
|
31
|
+
*/
|
|
32
|
+
export function addGenerationLogs(logs: ProviderLogEntry[]): void {
|
|
33
|
+
sessionLogs.push(...logs);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Add a single log entry with proper elapsed calculation
|
|
38
|
+
*/
|
|
39
|
+
export function addGenerationLog(
|
|
40
|
+
tag: string,
|
|
41
|
+
message: string,
|
|
42
|
+
level: ProviderLogEntry['level'] = 'info',
|
|
43
|
+
data?: Record<string, unknown>,
|
|
44
|
+
): void {
|
|
45
|
+
const now = Date.now();
|
|
46
|
+
sessionLogs.push({
|
|
47
|
+
timestamp: now,
|
|
48
|
+
elapsed: sessionStartTime > 0 ? now - sessionStartTime : 0,
|
|
49
|
+
level,
|
|
50
|
+
tag,
|
|
51
|
+
message,
|
|
52
|
+
...(data && { data }),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
56
|
+
const fn = level === 'error' ? console.error : level === 'warn' ? console.warn : console.log;
|
|
57
|
+
fn(`[${tag}] ${message}`, data ?? '');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Get and clear all stored logs (consume pattern)
|
|
63
|
+
*/
|
|
64
|
+
export function consumeGenerationLogs(): ProviderLogEntry[] {
|
|
65
|
+
const logs = [...sessionLogs];
|
|
66
|
+
sessionLogs = [];
|
|
67
|
+
sessionStartTime = 0;
|
|
68
|
+
return logs;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Get logs without clearing
|
|
73
|
+
*/
|
|
74
|
+
export function getGenerationLogs(): ProviderLogEntry[] {
|
|
75
|
+
return [...sessionLogs];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Clear stored logs
|
|
80
|
+
*/
|
|
81
|
+
export function clearGenerationLogs(): void {
|
|
82
|
+
sessionLogs = [];
|
|
83
|
+
sessionStartTime = 0;
|
|
84
|
+
}
|
|
@@ -7,6 +7,7 @@ import { validateProvider } from "../utils/provider-validator.util";
|
|
|
7
7
|
import { formatBase64 } from "../utils/base64.util";
|
|
8
8
|
import { validateURL } from "../validation/base-validator";
|
|
9
9
|
import { env } from "../config/env.config";
|
|
10
|
+
import { addGenerationLogs, addGenerationLog, startGenerationLogSession } from "./generation-log-store";
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
/** Generation timeout in milliseconds */
|
|
@@ -45,23 +46,28 @@ export interface MultiImageGenerationResult {
|
|
|
45
46
|
export async function executeMultiImageGeneration(
|
|
46
47
|
input: MultiImageGenerationInput,
|
|
47
48
|
): Promise<MultiImageGenerationResult> {
|
|
49
|
+
const TAG = 'MultiImageExecutor';
|
|
50
|
+
const startTime = Date.now();
|
|
51
|
+
startGenerationLogSession();
|
|
52
|
+
|
|
48
53
|
const validation = validateProvider("MultiImageExecutor");
|
|
49
54
|
if (!validation.success) {
|
|
55
|
+
addGenerationLog(TAG, 'Provider validation failed', 'error');
|
|
50
56
|
return { success: false, error: ("error" in validation ? validation.error : "Provider validation failed") };
|
|
51
57
|
}
|
|
52
58
|
const provider = validation.provider;
|
|
53
59
|
|
|
54
60
|
try {
|
|
55
61
|
const imageUrls = input.photos.map(formatBase64);
|
|
62
|
+
const totalImageSize = imageUrls.reduce((sum, url) => sum + url.length, 0);
|
|
56
63
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
64
|
+
addGenerationLog(TAG, 'Generation starting', 'info', {
|
|
65
|
+
model: input.model,
|
|
66
|
+
imageCount: imageUrls.length,
|
|
67
|
+
totalImageSizeKB: Math.round(totalImageSize / 1024),
|
|
68
|
+
promptLength: input.prompt.length,
|
|
69
|
+
timeout: GENERATION_TIMEOUT_MS,
|
|
70
|
+
});
|
|
65
71
|
|
|
66
72
|
const modelInput: Record<string, unknown> = {
|
|
67
73
|
prompt: input.prompt,
|
|
@@ -72,14 +78,18 @@ export async function executeMultiImageGeneration(
|
|
|
72
78
|
enable_safety_checker: MODEL_INPUT_DEFAULTS.enableSafetyChecker,
|
|
73
79
|
};
|
|
74
80
|
|
|
81
|
+
addGenerationLog(TAG, 'Calling provider.subscribe()...');
|
|
75
82
|
const result = await provider.subscribe(input.model, modelInput, {
|
|
76
83
|
timeoutMs: GENERATION_TIMEOUT_MS,
|
|
77
84
|
});
|
|
78
85
|
|
|
86
|
+
// Collect provider logs
|
|
87
|
+
const providerLogs = provider.endLogSession?.() ?? provider.getSessionLogs?.() ?? [];
|
|
88
|
+
addGenerationLogs(providerLogs);
|
|
89
|
+
|
|
79
90
|
const rawResult = result as Record<string, unknown>;
|
|
80
91
|
const data = rawResult?.data ?? rawResult;
|
|
81
92
|
|
|
82
|
-
// Type-safe extraction of image URL
|
|
83
93
|
if (data && typeof data === "object" && "images" in data) {
|
|
84
94
|
const images = (data as { images?: unknown }).images;
|
|
85
95
|
if (Array.isArray(images) && images.length > 0) {
|
|
@@ -87,26 +97,36 @@ export async function executeMultiImageGeneration(
|
|
|
87
97
|
if (firstImage && typeof firstImage === "object" && "url" in firstImage) {
|
|
88
98
|
const imageUrl = (firstImage as { url?: unknown }).url;
|
|
89
99
|
if (typeof imageUrl === "string" && imageUrl.length > 0) {
|
|
90
|
-
// Validate URL for security
|
|
91
100
|
const urlValidation = validateURL(imageUrl);
|
|
92
101
|
if (!urlValidation.isValid) {
|
|
102
|
+
addGenerationLog(TAG, `Invalid URL in response: ${urlValidation.errors.join(", ")}`, 'error');
|
|
93
103
|
return {
|
|
94
104
|
success: false,
|
|
95
105
|
error: `Invalid image URL received: ${urlValidation.errors.join(", ")}`
|
|
96
106
|
};
|
|
97
107
|
}
|
|
108
|
+
const elapsed = Date.now() - startTime;
|
|
109
|
+
addGenerationLog(TAG, `Generation SUCCESS in ${elapsed}ms`, 'info', { imageUrl, elapsed });
|
|
98
110
|
return { success: true, imageUrl };
|
|
99
111
|
}
|
|
100
112
|
}
|
|
101
113
|
}
|
|
102
114
|
}
|
|
103
115
|
|
|
116
|
+
const elapsed = Date.now() - startTime;
|
|
117
|
+
addGenerationLog(TAG, `No image in response after ${elapsed}ms`, 'error', {
|
|
118
|
+
dataKeys: data && typeof data === "object" ? Object.keys(data) : [],
|
|
119
|
+
elapsed,
|
|
120
|
+
});
|
|
104
121
|
return { success: false, error: "No image generated" };
|
|
105
122
|
} catch (error) {
|
|
123
|
+
// Collect provider logs even on failure
|
|
124
|
+
const providerLogs = provider.endLogSession?.() ?? provider.getSessionLogs?.() ?? [];
|
|
125
|
+
addGenerationLogs(providerLogs);
|
|
126
|
+
|
|
127
|
+
const elapsed = Date.now() - startTime;
|
|
106
128
|
const message = error instanceof Error ? error.message : "Generation failed";
|
|
107
|
-
|
|
108
|
-
console.error("[MultiImageExecutor] Error:", message);
|
|
109
|
-
}
|
|
129
|
+
addGenerationLog(TAG, `Generation FAILED after ${elapsed}ms: ${message}`, 'error', { elapsed });
|
|
110
130
|
return { success: false, error: message };
|
|
111
131
|
}
|
|
112
132
|
}
|