Files
fotospiel-app/resources/js/guest-v2/services/aiEditsApi.ts
Codex Agent 36bed12ff9
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
tests / ui (push) Has been cancelled
feat: implement AI styling foundation and billing scope rework
2026-02-06 20:01:58 +01:00

143 lines
3.6 KiB
TypeScript

import { fetchJson } from './apiClient';
import { getDeviceId } from '../lib/device';
export type GuestAiStyle = {
id: number;
key: string;
name: string;
category?: string | null;
description?: string | null;
provider?: string | null;
provider_model?: string | null;
requires_source_image?: boolean;
is_premium?: boolean;
metadata?: Record<string, unknown>;
};
export type GuestAiStylesMeta = {
required_feature?: string | null;
addon_keys?: string[] | null;
allow_custom_prompt?: boolean;
allowed_style_keys?: string[] | null;
policy_message?: string | null;
};
export type GuestAiEditOutput = {
id: number;
storage_disk?: string | null;
storage_path?: string | null;
provider_url?: string | null;
mime_type?: string | null;
width?: number | null;
height?: number | null;
is_primary?: boolean;
safety_state?: string | null;
safety_reasons?: string[];
generated_at?: string | null;
};
export type GuestAiEditRequest = {
id: number;
event_id: number;
photo_id: number;
style?: {
id: number;
key: string;
name: string;
} | null;
provider?: string | null;
provider_model?: string | null;
status: 'queued' | 'processing' | 'succeeded' | 'failed' | 'blocked' | 'canceled' | string;
safety_state?: string | null;
safety_reasons?: string[];
failure_code?: string | null;
failure_message?: string | null;
queued_at?: string | null;
started_at?: string | null;
completed_at?: string | null;
outputs: GuestAiEditOutput[];
};
export type GuestAiStylesResponse = {
data: GuestAiStyle[];
meta: GuestAiStylesMeta;
};
export type GuestAiEditEnvelope = {
message?: string;
duplicate?: boolean;
data: GuestAiEditRequest;
};
function deviceHeaders(): Record<string, string> {
return {
'X-Device-Id': getDeviceId(),
};
}
export async function fetchGuestAiStyles(eventToken: string): Promise<GuestAiStylesResponse> {
const response = await fetchJson<GuestAiStylesResponse>(
`/api/v1/events/${encodeURIComponent(eventToken)}/ai-styles`,
{
headers: deviceHeaders(),
noStore: true,
}
);
const payload = response.data;
return {
data: Array.isArray(payload?.data) ? payload.data : [],
meta: payload?.meta && typeof payload.meta === 'object' ? payload.meta : {},
};
}
export async function createGuestAiEdit(
eventToken: string,
photoId: number,
payload: {
style_key?: string;
prompt?: string;
negative_prompt?: string;
provider_model?: string;
idempotency_key?: string;
session_id?: string;
metadata?: Record<string, unknown>;
}
): Promise<GuestAiEditEnvelope> {
const response = await fetchJson<GuestAiEditEnvelope>(
`/api/v1/events/${encodeURIComponent(eventToken)}/photos/${photoId}/ai-edits`,
{
method: 'POST',
headers: {
...deviceHeaders(),
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
noStore: true,
}
);
if (!response.data || typeof response.data !== 'object' || !response.data.data) {
throw new Error('AI edit request response is invalid.');
}
return response.data;
}
export async function fetchGuestAiEditStatus(eventToken: string, requestId: number): Promise<{ data: GuestAiEditRequest }> {
const response = await fetchJson<{ data: GuestAiEditRequest }>(
`/api/v1/events/${encodeURIComponent(eventToken)}/ai-edits/${requestId}`,
{
headers: deviceHeaders(),
noStore: true,
}
);
if (!response.data || typeof response.data !== 'object' || !response.data.data) {
throw new Error('AI edit status response is invalid.');
}
return response.data;
}