@umituz/react-native-ai-generation-content 1.83.77 → 1.83.79
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/domain/interfaces/provider-lifecycle.interface.ts +5 -4
- package/src/domains/generation/infrastructure/executors/image-executor.ts +15 -15
- package/src/domains/generation/wizard/infrastructure/strategies/image-generation.executor.ts +20 -18
- package/src/domains/generation/wizard/infrastructure/strategies/image-generation.strategy.ts +4 -2
- package/src/domains/generation/wizard/infrastructure/utils/creation-persistence-factory.ts +2 -2
- package/src/domains/generation/wizard/infrastructure/utils/creation-persistence.types.ts +2 -0
- package/src/domains/generation/wizard/infrastructure/utils/creation-update-operations.ts +7 -4
- package/src/domains/generation/wizard/presentation/hooks/usePhotoBlockingGeneration.ts +6 -3
- package/src/infrastructure/services/generation-log-store.ts +48 -32
- package/src/infrastructure/services/multi-image-generation.executor.ts +22 -19
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.79",
|
|
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",
|
|
@@ -32,13 +32,14 @@ export interface IAIProviderLifecycle {
|
|
|
32
32
|
reset(): void;
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
|
-
* Get log entries from
|
|
36
|
-
*
|
|
35
|
+
* Get log entries from a specific generation session.
|
|
36
|
+
* If sessionId omitted, uses the most recent session (not safe for concurrent use).
|
|
37
37
|
*/
|
|
38
|
-
getSessionLogs?(): ProviderLogEntry[];
|
|
38
|
+
getSessionLogs?(sessionId?: string): ProviderLogEntry[];
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
41
|
* End log session and return all entries. Clears the buffer.
|
|
42
|
+
* If sessionId omitted, uses the most recent session (not safe for concurrent use).
|
|
42
43
|
*/
|
|
43
|
-
endLogSession?(): ProviderLogEntry[];
|
|
44
|
+
endLogSession?(sessionId?: string): ProviderLogEntry[];
|
|
44
45
|
}
|
|
@@ -25,12 +25,11 @@ export class ImageExecutor
|
|
|
25
25
|
): Promise<GenerationResult<ImageGenerationOutput>> {
|
|
26
26
|
const TAG = 'GenericImageExecutor';
|
|
27
27
|
const startTime = Date.now();
|
|
28
|
-
|
|
29
|
-
startGenerationLogSession();
|
|
28
|
+
const sid = startGenerationLogSession();
|
|
30
29
|
|
|
31
30
|
try {
|
|
32
31
|
const totalImageSize = input.imageUrls?.reduce((sum, url) => sum + url.length, 0) ?? 0;
|
|
33
|
-
addGenerationLog(TAG, 'Starting generation', 'info', {
|
|
32
|
+
addGenerationLog(sid, TAG, 'Starting generation', 'info', {
|
|
34
33
|
model,
|
|
35
34
|
imageCount: input.imageUrls?.length || 0,
|
|
36
35
|
totalImageSizeKB: Math.round(totalImageSize / 1024),
|
|
@@ -40,19 +39,19 @@ export class ImageExecutor
|
|
|
40
39
|
const provider = providerRegistry.getActiveProvider();
|
|
41
40
|
|
|
42
41
|
if (!provider?.isInitialized()) {
|
|
43
|
-
addGenerationLog(TAG, 'Provider not initialized!', 'error');
|
|
42
|
+
addGenerationLog(sid, TAG, 'Provider not initialized!', 'error');
|
|
44
43
|
return { success: false, error: "AI provider not initialized" };
|
|
45
44
|
}
|
|
46
45
|
|
|
47
46
|
options?.onProgress?.(5);
|
|
48
47
|
const modelInput = this.buildModelInput(input);
|
|
49
|
-
addGenerationLog(TAG, 'Model input prepared', 'info', {
|
|
48
|
+
addGenerationLog(sid, TAG, 'Model input prepared', 'info', {
|
|
50
49
|
imageCount: modelInput.image_urls?.length || 0,
|
|
51
50
|
aspectRatio: modelInput.aspect_ratio,
|
|
52
51
|
});
|
|
53
52
|
|
|
54
53
|
options?.onProgress?.(10);
|
|
55
|
-
addGenerationLog(TAG, 'Calling provider.subscribe()...');
|
|
54
|
+
addGenerationLog(sid, TAG, 'Calling provider.subscribe()...');
|
|
56
55
|
|
|
57
56
|
const result = await provider.subscribe(model, modelInput, {
|
|
58
57
|
timeoutMs: options?.timeoutMs ?? env.generationImageTimeoutMs,
|
|
@@ -65,33 +64,34 @@ export class ImageExecutor
|
|
|
65
64
|
},
|
|
66
65
|
});
|
|
67
66
|
|
|
68
|
-
// Collect provider logs
|
|
69
|
-
const
|
|
70
|
-
|
|
67
|
+
// Collect provider logs — use providerSessionId for concurrent safety
|
|
68
|
+
const providerSessionId = (result as { __providerSessionId?: string })?.__providerSessionId;
|
|
69
|
+
const providerLogs = provider.endLogSession?.(providerSessionId) ?? provider.getSessionLogs?.(providerSessionId) ?? [];
|
|
70
|
+
addGenerationLogs(sid, providerLogs);
|
|
71
71
|
|
|
72
72
|
options?.onProgress?.(90);
|
|
73
73
|
const imageUrl = this.extractImageUrl(result);
|
|
74
74
|
const elapsed = Date.now() - startTime;
|
|
75
75
|
|
|
76
76
|
if (!imageUrl) {
|
|
77
|
-
addGenerationLog(TAG, `No image URL in response after ${elapsed}ms`, 'error');
|
|
77
|
+
addGenerationLog(sid, TAG, `No image URL in response after ${elapsed}ms`, 'error');
|
|
78
78
|
return { success: false, error: "No image generated" };
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
options?.onProgress?.(100);
|
|
82
|
-
addGenerationLog(TAG, `Generation SUCCESS in ${elapsed}ms`, 'info', { imageUrl, elapsed });
|
|
82
|
+
addGenerationLog(sid, TAG, `Generation SUCCESS in ${elapsed}ms`, 'info', { imageUrl, elapsed });
|
|
83
83
|
return { success: true, data: { imageUrl } };
|
|
84
84
|
} catch (error) {
|
|
85
|
-
// Collect provider logs even on failure
|
|
85
|
+
// Collect provider logs even on failure — no providerSessionId available in catch
|
|
86
86
|
const provider = providerRegistry.getActiveProvider();
|
|
87
87
|
if (provider) {
|
|
88
|
-
const providerLogs = provider.endLogSession?.() ??
|
|
89
|
-
addGenerationLogs(providerLogs);
|
|
88
|
+
const providerLogs = provider.endLogSession?.() ?? [];
|
|
89
|
+
addGenerationLogs(sid, providerLogs);
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
const elapsed = Date.now() - startTime;
|
|
93
93
|
const message = error instanceof Error ? error.message : "Generation failed";
|
|
94
|
-
addGenerationLog(TAG, `Generation FAILED after ${elapsed}ms: ${message}`, 'error', { elapsed });
|
|
94
|
+
addGenerationLog(sid, TAG, `Generation FAILED after ${elapsed}ms: ${message}`, 'error', { elapsed });
|
|
95
95
|
return { success: false, error: message };
|
|
96
96
|
}
|
|
97
97
|
}
|
package/src/domains/generation/wizard/infrastructure/strategies/image-generation.executor.ts
CHANGED
|
@@ -18,6 +18,7 @@ interface ExecutionResult {
|
|
|
18
18
|
success: boolean;
|
|
19
19
|
imageUrl?: string;
|
|
20
20
|
error?: string;
|
|
21
|
+
logSessionId?: string;
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
function formatBase64(base64: string): string {
|
|
@@ -54,13 +55,13 @@ export async function executeImageGeneration(
|
|
|
54
55
|
): Promise<ExecutionResult> {
|
|
55
56
|
const TAG = 'ImageExecutor';
|
|
56
57
|
const startTime = Date.now();
|
|
57
|
-
startGenerationLogSession();
|
|
58
|
+
const sid = startGenerationLogSession();
|
|
58
59
|
const { providerRegistry } = await import("../../../../../infrastructure/services/provider-registry.service");
|
|
59
60
|
|
|
60
61
|
const provider = providerRegistry.getActiveProvider();
|
|
61
62
|
if (!provider?.isInitialized()) {
|
|
62
|
-
addGenerationLog(TAG, 'Provider not initialized!', 'error');
|
|
63
|
-
return { success: false, error: "AI provider not initialized" };
|
|
63
|
+
addGenerationLog(sid, TAG, 'Provider not initialized!', 'error');
|
|
64
|
+
return { success: false, error: "AI provider not initialized", logSessionId: sid };
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
try {
|
|
@@ -69,14 +70,14 @@ export async function executeImageGeneration(
|
|
|
69
70
|
const mode = imageUrls.length > 0 ? "Photo-based" : "Text-to-image";
|
|
70
71
|
const totalImageSize = imageUrls.reduce((sum, url) => sum + url.length, 0);
|
|
71
72
|
|
|
72
|
-
addGenerationLog(TAG, `${mode} generation starting`, 'info', {
|
|
73
|
+
addGenerationLog(sid, TAG, `${mode} generation starting`, 'info', {
|
|
73
74
|
model,
|
|
74
75
|
photoCount: imageUrls.length,
|
|
75
76
|
totalImageSizeKB: Math.round(totalImageSize / 1024),
|
|
76
77
|
promptLength: finalPrompt.length,
|
|
77
78
|
timeout: GENERATION_TIMEOUT_MS,
|
|
78
79
|
});
|
|
79
|
-
addGenerationLog(TAG, `Prompt: ${finalPrompt.substring(0, 300)}${finalPrompt.length > 300 ? "..." : ""}`);
|
|
80
|
+
addGenerationLog(sid, TAG, `Prompt: ${finalPrompt.substring(0, 300)}${finalPrompt.length > 300 ? "..." : ""}`);
|
|
80
81
|
|
|
81
82
|
const modelInput: Record<string, unknown> = {
|
|
82
83
|
prompt: finalPrompt,
|
|
@@ -92,7 +93,7 @@ export async function executeImageGeneration(
|
|
|
92
93
|
}
|
|
93
94
|
|
|
94
95
|
let lastStatus = "";
|
|
95
|
-
addGenerationLog(TAG, 'Calling provider.subscribe()...');
|
|
96
|
+
addGenerationLog(sid, TAG, 'Calling provider.subscribe()...');
|
|
96
97
|
const result = await provider.subscribe(model, modelInput, {
|
|
97
98
|
timeoutMs: GENERATION_TIMEOUT_MS,
|
|
98
99
|
onQueueUpdate: (status) => {
|
|
@@ -102,9 +103,10 @@ export async function executeImageGeneration(
|
|
|
102
103
|
},
|
|
103
104
|
});
|
|
104
105
|
|
|
105
|
-
// Collect provider logs
|
|
106
|
-
const
|
|
107
|
-
|
|
106
|
+
// Collect provider logs — use providerSessionId for concurrent safety
|
|
107
|
+
const providerSessionId = (result as { __providerSessionId?: string })?.__providerSessionId;
|
|
108
|
+
const providerLogs = provider.endLogSession?.(providerSessionId) ?? provider.getSessionLogs?.(providerSessionId) ?? [];
|
|
109
|
+
addGenerationLogs(sid, providerLogs);
|
|
108
110
|
|
|
109
111
|
const rawResult = result as Record<string, unknown>;
|
|
110
112
|
const data = (rawResult?.data ?? rawResult) as { images?: Array<{ url: string }> };
|
|
@@ -114,23 +116,23 @@ export async function executeImageGeneration(
|
|
|
114
116
|
onProgress?.(100);
|
|
115
117
|
|
|
116
118
|
if (imageUrl) {
|
|
117
|
-
addGenerationLog(TAG, `Generation SUCCESS in ${elapsed}ms`, 'info', { imageUrl, elapsed });
|
|
118
|
-
return { success: true, imageUrl };
|
|
119
|
+
addGenerationLog(sid, TAG, `Generation SUCCESS in ${elapsed}ms`, 'info', { imageUrl, elapsed });
|
|
120
|
+
return { success: true, imageUrl, logSessionId: sid };
|
|
119
121
|
}
|
|
120
122
|
|
|
121
|
-
addGenerationLog(TAG, `No image in response after ${elapsed}ms`, 'error', {
|
|
123
|
+
addGenerationLog(sid, TAG, `No image in response after ${elapsed}ms`, 'error', {
|
|
122
124
|
responseKeys: Object.keys(data || {}),
|
|
123
125
|
elapsed,
|
|
124
126
|
});
|
|
125
|
-
return { success: false, error: "No image generated" };
|
|
127
|
+
return { success: false, error: "No image generated", logSessionId: sid };
|
|
126
128
|
} catch (error) {
|
|
127
|
-
// Collect provider logs even on failure
|
|
128
|
-
const providerLogs = provider.endLogSession?.() ??
|
|
129
|
-
addGenerationLogs(providerLogs);
|
|
129
|
+
// Collect provider logs even on failure — no providerSessionId available in catch
|
|
130
|
+
const providerLogs = provider.endLogSession?.() ?? [];
|
|
131
|
+
addGenerationLogs(sid, providerLogs);
|
|
130
132
|
|
|
131
133
|
const elapsed = Date.now() - startTime;
|
|
132
134
|
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 };
|
|
135
|
+
addGenerationLog(sid, TAG, `Generation FAILED after ${elapsed}ms: ${errorMsg}`, 'error', { elapsed });
|
|
136
|
+
return { success: false, error: errorMsg, logSessionId: sid };
|
|
135
137
|
}
|
|
136
138
|
}
|
package/src/domains/generation/wizard/infrastructure/strategies/image-generation.strategy.ts
CHANGED
|
@@ -94,10 +94,12 @@ export function createImageStrategy(options: CreateImageStrategyOptions): Wizard
|
|
|
94
94
|
const result = await executeImageGeneration(imageInput, model);
|
|
95
95
|
|
|
96
96
|
if (!result.success || !result.imageUrl) {
|
|
97
|
-
|
|
97
|
+
const error = new Error(result.error || "Image generation failed");
|
|
98
|
+
(error as Error & { logSessionId?: string }).logSessionId = result.logSessionId;
|
|
99
|
+
throw error;
|
|
98
100
|
}
|
|
99
101
|
|
|
100
|
-
return { imageUrl: result.imageUrl };
|
|
102
|
+
return { imageUrl: result.imageUrl, logSessionId: result.logSessionId };
|
|
101
103
|
},
|
|
102
104
|
};
|
|
103
105
|
}
|
|
@@ -20,8 +20,8 @@ export function createCreationPersistence() {
|
|
|
20
20
|
updateToCompleted: (userId: string, creationId: string, data: CompletedCreationData) =>
|
|
21
21
|
updateToCompleted(repository, userId, creationId, data),
|
|
22
22
|
|
|
23
|
-
updateToFailed: (userId: string, creationId: string, error: string) =>
|
|
24
|
-
updateToFailed(repository, userId, creationId, error),
|
|
23
|
+
updateToFailed: (userId: string, creationId: string, error: string, logSessionId?: string) =>
|
|
24
|
+
updateToFailed(repository, userId, creationId, error, logSessionId),
|
|
25
25
|
|
|
26
26
|
updateRequestId: (userId: string, creationId: string, requestId: string, model: string) =>
|
|
27
27
|
updateRequestId(repository, userId, creationId, requestId, model),
|
|
@@ -24,4 +24,6 @@ export interface CompletedCreationData {
|
|
|
24
24
|
readonly thumbnailUrl?: string;
|
|
25
25
|
/** Unix timestamp (ms) when generation was submitted; used to compute durationMs */
|
|
26
26
|
readonly generationStartedAt?: number;
|
|
27
|
+
/** Log session ID for flushing session-scoped generation logs */
|
|
28
|
+
readonly logSessionId?: string;
|
|
27
29
|
}
|
|
@@ -18,8 +18,10 @@ import { consumeGenerationLogs } from "../../../../../infrastructure/services/ge
|
|
|
18
18
|
async function flushLogsToFirestore(
|
|
19
19
|
userId: string,
|
|
20
20
|
creationId: string,
|
|
21
|
+
logSessionId?: string,
|
|
21
22
|
): Promise<void> {
|
|
22
|
-
|
|
23
|
+
if (!logSessionId) return;
|
|
24
|
+
const logs = consumeGenerationLogs(logSessionId);
|
|
23
25
|
if (logs.length === 0) return;
|
|
24
26
|
|
|
25
27
|
try {
|
|
@@ -69,7 +71,7 @@ export async function updateToCompleted(
|
|
|
69
71
|
} as Partial<Creation>);
|
|
70
72
|
|
|
71
73
|
// Flush generation logs to Firestore — awaited to ensure logs are persisted
|
|
72
|
-
await flushLogsToFirestore(userId, creationId);
|
|
74
|
+
await flushLogsToFirestore(userId, creationId, data.logSessionId);
|
|
73
75
|
}
|
|
74
76
|
|
|
75
77
|
/**
|
|
@@ -79,7 +81,8 @@ export async function updateToFailed(
|
|
|
79
81
|
repository: ICreationsRepository,
|
|
80
82
|
userId: string,
|
|
81
83
|
creationId: string,
|
|
82
|
-
error: string
|
|
84
|
+
error: string,
|
|
85
|
+
logSessionId?: string,
|
|
83
86
|
): Promise<void> {
|
|
84
87
|
await repository.update(userId, creationId, {
|
|
85
88
|
status: "failed" as const,
|
|
@@ -88,7 +91,7 @@ export async function updateToFailed(
|
|
|
88
91
|
} as Partial<Creation>);
|
|
89
92
|
|
|
90
93
|
// Flush generation logs to Firestore — awaited to ensure logs are persisted
|
|
91
|
-
await flushLogsToFirestore(userId, creationId);
|
|
94
|
+
await flushLogsToFirestore(userId, creationId, logSessionId);
|
|
92
95
|
}
|
|
93
96
|
|
|
94
97
|
/**
|
|
@@ -48,10 +48,11 @@ export function usePhotoBlockingGeneration(
|
|
|
48
48
|
} = props;
|
|
49
49
|
|
|
50
50
|
const creationIdRef = useRef<string | null>(null);
|
|
51
|
+
const logSessionIdRef = useRef<string | undefined>(undefined);
|
|
51
52
|
|
|
52
53
|
const handleSuccess = useCallback(
|
|
53
54
|
async (result: unknown) => {
|
|
54
|
-
const typedResult = result as { imageUrl?: string; videoUrl?: string };
|
|
55
|
+
const typedResult = result as { imageUrl?: string; videoUrl?: string; logSessionId?: string };
|
|
55
56
|
const creationId = creationIdRef.current;
|
|
56
57
|
|
|
57
58
|
if (creationId && userId) {
|
|
@@ -60,6 +61,7 @@ export function usePhotoBlockingGeneration(
|
|
|
60
61
|
uri: typedResult.imageUrl || typedResult.videoUrl || "",
|
|
61
62
|
imageUrl: typedResult.imageUrl,
|
|
62
63
|
videoUrl: typedResult.videoUrl,
|
|
64
|
+
logSessionId: typedResult.logSessionId,
|
|
63
65
|
});
|
|
64
66
|
} catch (err) {
|
|
65
67
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
@@ -85,12 +87,13 @@ export function usePhotoBlockingGeneration(
|
|
|
85
87
|
);
|
|
86
88
|
|
|
87
89
|
const handleError = useCallback(
|
|
88
|
-
async (err: { message: string }) => {
|
|
90
|
+
async (err: { message: string; originalError?: Error & { logSessionId?: string } }) => {
|
|
89
91
|
const creationId = creationIdRef.current;
|
|
92
|
+
const logSessionId = err.originalError?.logSessionId;
|
|
90
93
|
|
|
91
94
|
if (creationId && userId) {
|
|
92
95
|
try {
|
|
93
|
-
await persistence.updateToFailed(userId, creationId, err.message);
|
|
96
|
+
await persistence.updateToFailed(userId, creationId, err.message, logSessionId);
|
|
94
97
|
} catch (updateErr) {
|
|
95
98
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
96
99
|
console.error("[PhotoBlockingGeneration] updateToFailed error:", updateErr);
|
|
@@ -1,56 +1,71 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Generation Log Store
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* Session-scoped log buffer for generation executors.
|
|
4
|
+
* Each generation gets an isolated session — concurrent generations don't corrupt each other.
|
|
5
5
|
*
|
|
6
6
|
* Flow:
|
|
7
|
-
* 1. Executor calls
|
|
8
|
-
* 2. Executor calls
|
|
9
|
-
* 3. Executor
|
|
10
|
-
* 4.
|
|
11
|
-
*
|
|
12
|
-
* IMPORTANT: Always use addGenerationLogs/addGenerationLog to append.
|
|
13
|
-
* clearGenerationLogs should be called at the start of a new generation.
|
|
7
|
+
* 1. Executor calls startGenerationLogSession() → gets sessionId
|
|
8
|
+
* 2. Executor calls addGenerationLog(sessionId, ...) → logs accumulate
|
|
9
|
+
* 3. Executor calls provider.endLogSession() → gets provider logs
|
|
10
|
+
* 4. Executor calls addGenerationLogs(sessionId, providerLogs) → appends
|
|
11
|
+
* 5. Persistence layer calls consumeGenerationLogs(sessionId) → writes to Firestore
|
|
14
12
|
*/
|
|
15
13
|
|
|
16
14
|
import type { ProviderLogEntry } from "../../domain/interfaces/ai-provider.interface";
|
|
17
15
|
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
interface LogSession {
|
|
17
|
+
readonly startTime: number;
|
|
18
|
+
entries: ProviderLogEntry[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let sessionCounter = 0;
|
|
22
|
+
const sessions = new Map<string, LogSession>();
|
|
20
23
|
|
|
21
24
|
/**
|
|
22
|
-
*
|
|
25
|
+
* Start a new isolated log session. Returns session ID.
|
|
26
|
+
* Call at the beginning of each generation.
|
|
23
27
|
*/
|
|
24
|
-
export function startGenerationLogSession():
|
|
25
|
-
|
|
26
|
-
|
|
28
|
+
export function startGenerationLogSession(): string {
|
|
29
|
+
const id = `gen_${++sessionCounter}_${Date.now()}`;
|
|
30
|
+
sessions.set(id, { startTime: Date.now(), entries: [] });
|
|
31
|
+
return id;
|
|
27
32
|
}
|
|
28
33
|
|
|
29
34
|
/**
|
|
30
|
-
* Append log entries to
|
|
35
|
+
* Append log entries to a specific session (e.g. provider logs)
|
|
31
36
|
*/
|
|
32
|
-
export function addGenerationLogs(logs: ProviderLogEntry[]): void {
|
|
33
|
-
|
|
37
|
+
export function addGenerationLogs(sessionId: string, logs: ProviderLogEntry[]): void {
|
|
38
|
+
const session = sessions.get(sessionId);
|
|
39
|
+
if (session) {
|
|
40
|
+
session.entries.push(...logs);
|
|
41
|
+
}
|
|
34
42
|
}
|
|
35
43
|
|
|
36
44
|
/**
|
|
37
45
|
* Add a single log entry with proper elapsed calculation
|
|
38
46
|
*/
|
|
39
47
|
export function addGenerationLog(
|
|
48
|
+
sessionId: string,
|
|
40
49
|
tag: string,
|
|
41
50
|
message: string,
|
|
42
51
|
level: ProviderLogEntry['level'] = 'info',
|
|
43
52
|
data?: Record<string, unknown>,
|
|
44
53
|
): void {
|
|
54
|
+
const session = sessions.get(sessionId);
|
|
45
55
|
const now = Date.now();
|
|
46
|
-
|
|
56
|
+
|
|
57
|
+
const entry: ProviderLogEntry = {
|
|
47
58
|
timestamp: now,
|
|
48
|
-
elapsed:
|
|
59
|
+
elapsed: session ? now - session.startTime : 0,
|
|
49
60
|
level,
|
|
50
61
|
tag,
|
|
51
62
|
message,
|
|
52
63
|
...(data && { data }),
|
|
53
|
-
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
if (session) {
|
|
67
|
+
session.entries.push(entry);
|
|
68
|
+
}
|
|
54
69
|
|
|
55
70
|
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
56
71
|
const fn = level === 'error' ? console.error : level === 'warn' ? console.warn : console.log;
|
|
@@ -59,26 +74,27 @@ export function addGenerationLog(
|
|
|
59
74
|
}
|
|
60
75
|
|
|
61
76
|
/**
|
|
62
|
-
* Get and clear all stored logs (consume pattern)
|
|
77
|
+
* Get and clear all stored logs for a session (consume pattern).
|
|
78
|
+
* Removes the session after consuming.
|
|
63
79
|
*/
|
|
64
|
-
export function consumeGenerationLogs(): ProviderLogEntry[] {
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
80
|
+
export function consumeGenerationLogs(sessionId: string): ProviderLogEntry[] {
|
|
81
|
+
const session = sessions.get(sessionId);
|
|
82
|
+
if (!session) return [];
|
|
83
|
+
const logs = [...session.entries];
|
|
84
|
+
sessions.delete(sessionId);
|
|
68
85
|
return logs;
|
|
69
86
|
}
|
|
70
87
|
|
|
71
88
|
/**
|
|
72
89
|
* Get logs without clearing
|
|
73
90
|
*/
|
|
74
|
-
export function getGenerationLogs(): ProviderLogEntry[] {
|
|
75
|
-
return [...
|
|
91
|
+
export function getGenerationLogs(sessionId: string): ProviderLogEntry[] {
|
|
92
|
+
return [...(sessions.get(sessionId)?.entries ?? [])];
|
|
76
93
|
}
|
|
77
94
|
|
|
78
95
|
/**
|
|
79
|
-
* Clear
|
|
96
|
+
* Clear a specific session's logs
|
|
80
97
|
*/
|
|
81
|
-
export function clearGenerationLogs(): void {
|
|
82
|
-
|
|
83
|
-
sessionStartTime = 0;
|
|
98
|
+
export function clearGenerationLogs(sessionId: string): void {
|
|
99
|
+
sessions.delete(sessionId);
|
|
84
100
|
}
|
|
@@ -38,6 +38,7 @@ export interface MultiImageGenerationResult {
|
|
|
38
38
|
readonly success: boolean;
|
|
39
39
|
readonly imageUrl?: string;
|
|
40
40
|
readonly error?: string;
|
|
41
|
+
readonly logSessionId?: string;
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
/**
|
|
@@ -48,12 +49,12 @@ export async function executeMultiImageGeneration(
|
|
|
48
49
|
): Promise<MultiImageGenerationResult> {
|
|
49
50
|
const TAG = 'MultiImageExecutor';
|
|
50
51
|
const startTime = Date.now();
|
|
51
|
-
startGenerationLogSession();
|
|
52
|
+
const sid = startGenerationLogSession();
|
|
52
53
|
|
|
53
54
|
const validation = validateProvider("MultiImageExecutor");
|
|
54
55
|
if (!validation.success) {
|
|
55
|
-
addGenerationLog(TAG, 'Provider validation failed', 'error');
|
|
56
|
-
return { success: false, error: ("error" in validation ? validation.error : "Provider validation failed") };
|
|
56
|
+
addGenerationLog(sid, TAG, 'Provider validation failed', 'error');
|
|
57
|
+
return { success: false, error: ("error" in validation ? validation.error : "Provider validation failed"), logSessionId: sid };
|
|
57
58
|
}
|
|
58
59
|
const provider = validation.provider;
|
|
59
60
|
|
|
@@ -61,7 +62,7 @@ export async function executeMultiImageGeneration(
|
|
|
61
62
|
const imageUrls = input.photos.map(formatBase64);
|
|
62
63
|
const totalImageSize = imageUrls.reduce((sum, url) => sum + url.length, 0);
|
|
63
64
|
|
|
64
|
-
addGenerationLog(TAG, 'Generation starting', 'info', {
|
|
65
|
+
addGenerationLog(sid, TAG, 'Generation starting', 'info', {
|
|
65
66
|
model: input.model,
|
|
66
67
|
imageCount: imageUrls.length,
|
|
67
68
|
totalImageSizeKB: Math.round(totalImageSize / 1024),
|
|
@@ -78,14 +79,15 @@ export async function executeMultiImageGeneration(
|
|
|
78
79
|
enable_safety_checker: MODEL_INPUT_DEFAULTS.enableSafetyChecker,
|
|
79
80
|
};
|
|
80
81
|
|
|
81
|
-
addGenerationLog(TAG, 'Calling provider.subscribe()...');
|
|
82
|
+
addGenerationLog(sid, TAG, 'Calling provider.subscribe()...');
|
|
82
83
|
const result = await provider.subscribe(input.model, modelInput, {
|
|
83
84
|
timeoutMs: GENERATION_TIMEOUT_MS,
|
|
84
85
|
});
|
|
85
86
|
|
|
86
|
-
// Collect provider logs
|
|
87
|
-
const
|
|
88
|
-
|
|
87
|
+
// Collect provider logs — use providerSessionId for concurrent safety
|
|
88
|
+
const providerSessionId = (result as { __providerSessionId?: string })?.__providerSessionId;
|
|
89
|
+
const providerLogs = provider.endLogSession?.(providerSessionId) ?? provider.getSessionLogs?.(providerSessionId) ?? [];
|
|
90
|
+
addGenerationLogs(sid, providerLogs);
|
|
89
91
|
|
|
90
92
|
const rawResult = result as Record<string, unknown>;
|
|
91
93
|
const data = rawResult?.data ?? rawResult;
|
|
@@ -99,34 +101,35 @@ export async function executeMultiImageGeneration(
|
|
|
99
101
|
if (typeof imageUrl === "string" && imageUrl.length > 0) {
|
|
100
102
|
const urlValidation = validateURL(imageUrl);
|
|
101
103
|
if (!urlValidation.isValid) {
|
|
102
|
-
addGenerationLog(TAG, `Invalid URL in response: ${urlValidation.errors.join(", ")}`, 'error');
|
|
104
|
+
addGenerationLog(sid, TAG, `Invalid URL in response: ${urlValidation.errors.join(", ")}`, 'error');
|
|
103
105
|
return {
|
|
104
106
|
success: false,
|
|
105
|
-
error: `Invalid image URL received: ${urlValidation.errors.join(", ")}
|
|
107
|
+
error: `Invalid image URL received: ${urlValidation.errors.join(", ")}`,
|
|
108
|
+
logSessionId: sid,
|
|
106
109
|
};
|
|
107
110
|
}
|
|
108
111
|
const elapsed = Date.now() - startTime;
|
|
109
|
-
addGenerationLog(TAG, `Generation SUCCESS in ${elapsed}ms`, 'info', { imageUrl, elapsed });
|
|
110
|
-
return { success: true, imageUrl };
|
|
112
|
+
addGenerationLog(sid, TAG, `Generation SUCCESS in ${elapsed}ms`, 'info', { imageUrl, elapsed });
|
|
113
|
+
return { success: true, imageUrl, logSessionId: sid };
|
|
111
114
|
}
|
|
112
115
|
}
|
|
113
116
|
}
|
|
114
117
|
}
|
|
115
118
|
|
|
116
119
|
const elapsed = Date.now() - startTime;
|
|
117
|
-
addGenerationLog(TAG, `No image in response after ${elapsed}ms`, 'error', {
|
|
120
|
+
addGenerationLog(sid, TAG, `No image in response after ${elapsed}ms`, 'error', {
|
|
118
121
|
dataKeys: data && typeof data === "object" ? Object.keys(data) : [],
|
|
119
122
|
elapsed,
|
|
120
123
|
});
|
|
121
|
-
return { success: false, error: "No image generated" };
|
|
124
|
+
return { success: false, error: "No image generated", logSessionId: sid };
|
|
122
125
|
} catch (error) {
|
|
123
|
-
// Collect provider logs even on failure
|
|
124
|
-
const providerLogs = provider.endLogSession?.() ??
|
|
125
|
-
addGenerationLogs(providerLogs);
|
|
126
|
+
// Collect provider logs even on failure — no providerSessionId available in catch
|
|
127
|
+
const providerLogs = provider.endLogSession?.() ?? [];
|
|
128
|
+
addGenerationLogs(sid, providerLogs);
|
|
126
129
|
|
|
127
130
|
const elapsed = Date.now() - startTime;
|
|
128
131
|
const message = error instanceof Error ? error.message : "Generation failed";
|
|
129
|
-
addGenerationLog(TAG, `Generation FAILED after ${elapsed}ms: ${message}`, 'error', { elapsed });
|
|
130
|
-
return { success: false, error: message };
|
|
132
|
+
addGenerationLog(sid, TAG, `Generation FAILED after ${elapsed}ms: ${message}`, 'error', { elapsed });
|
|
133
|
+
return { success: false, error: message, logSessionId: sid };
|
|
131
134
|
}
|
|
132
135
|
}
|