Download frontend/src/features/creative-console/creative-console-api.ts from li2895/li: direct link, hf CLI and curl.
- Browser
- Download file 18.6 kB
-
https://huggingface.co/spaces/li2895/li/resolve/main/frontend/src/features/creative-console/creative-console-api.ts
- Command line
-
hf download hf://spaces/li2895/li/frontend/src/features/creative-console/creative-console-api.ts
-
curl -L -o creative-console-api.ts https://huggingface.co/spaces/li2895/li/resolve/main/frontend/src/features/creative-console/creative-console-api.ts
18.6 kB
| export type ChatMessage = { | |
| role: "system" | "user" | "assistant"; | |
| content: string; | |
| }; | |
| export type ReasoningEffort = "auto" | "none" | "low" | "medium" | "high" | "xhigh"; | |
| export type ChatToolActivity = { | |
| id: string; | |
| type: string; | |
| name: string; | |
| status: "in_progress" | "completed" | "failed"; | |
| detail: string; | |
| }; | |
| export type ChatStreamSnapshot = { | |
| text: string; | |
| reasoning: string; | |
| tools: ChatToolActivity[]; | |
| }; | |
| export type ChatResponseResult = ChatStreamSnapshot; | |
| export type ImageResult = { | |
| url: string; | |
| revisedPrompt?: string; | |
| }; | |
| export type VideoStatus = { | |
| status: "pending" | "done" | "failed"; | |
| model?: string; | |
| progress: number; | |
| video?: { url: string; duration?: number; respectModeration?: boolean }; | |
| error?: { code?: string; message: string }; | |
| }; | |
| class CreativeApiError extends Error { | |
| readonly status: number; | |
| readonly code?: string; | |
| constructor(status: number, message: string, code?: string) { | |
| super(message); | |
| this.name = "CreativeApiError"; | |
| this.status = status; | |
| this.code = code; | |
| } | |
| } | |
| type RequestOptions = { | |
| method?: "GET" | "POST"; | |
| body?: Record<string, unknown>; | |
| signal?: AbortSignal; | |
| }; | |
| export async function createChatResponse(input: { | |
| apiKey: string; | |
| model: string; | |
| messages: ChatMessage[]; | |
| promptCacheKey?: string; | |
| reasoningEffort: ReasoningEffort; | |
| webSearch: boolean; | |
| xSearch: boolean; | |
| onUpdate?: (snapshot: ChatStreamSnapshot) => void; | |
| signal?: AbortSignal; | |
| }): Promise<ChatResponseResult> { | |
| const body: Record<string, unknown> = { | |
| model: input.model, | |
| input: input.messages.map(({ role, content }) => ({ role, content })), | |
| stream: true, | |
| store: false, | |
| }; | |
| if (input.promptCacheKey) body.prompt_cache_key = input.promptCacheKey; | |
| if (input.reasoningEffort === "auto") body.reasoning = { summary: "auto" }; | |
| else if (input.reasoningEffort !== "none") body.reasoning = { effort: input.reasoningEffort, summary: "auto" }; | |
| else body.reasoning = { effort: "none" }; | |
| const tools: Array<{ type: string }> = []; | |
| if (input.webSearch) tools.push({ type: "web_search" }); | |
| if (input.xSearch) tools.push({ type: "x_search" }); | |
| if (tools.length > 0) body.tools = tools; | |
| return publicResponsesStream(input.apiKey, body, input.onUpdate, input.signal); | |
| } | |
| export async function generateImage(input: { | |
| apiKey: string; | |
| model: string; | |
| prompt: string; | |
| count: number; | |
| aspectRatio: string; | |
| resolution: string; | |
| signal?: AbortSignal; | |
| }): Promise<ImageResult[]> { | |
| const payload = await publicApiRequest( | |
| input.apiKey, | |
| "/images/generations", | |
| { | |
| method: "POST", | |
| body: { | |
| model: input.model, | |
| prompt: input.prompt, | |
| n: input.count, | |
| aspect_ratio: input.aspectRatio, | |
| resolution: input.resolution, | |
| response_format: "url", | |
| stream: false, | |
| }, | |
| signal: input.signal, | |
| }, | |
| ); | |
| const images = readImages(payload); | |
| if (images.length === 0) throw new CreativeApiError(200, "The image response did not contain any images", "invalid_response"); | |
| return images.map((image) => ({ ...image, url: resolveMediaURL(image.url) })); | |
| } | |
| export async function createVideo(input: { | |
| apiKey: string; | |
| model: string; | |
| prompt: string; | |
| imageURL?: string; | |
| duration: number; | |
| aspectRatio: string; | |
| resolution: string; | |
| signal?: AbortSignal; | |
| }): Promise<string> { | |
| const body: Record<string, unknown> = { | |
| model: input.model, | |
| prompt: input.prompt, | |
| duration: input.duration, | |
| aspect_ratio: input.aspectRatio, | |
| resolution: input.resolution, | |
| }; | |
| if (input.imageURL) body.image = { url: input.imageURL }; | |
| const payload = await publicApiRequest( | |
| input.apiKey, | |
| "/videos/generations", | |
| { method: "POST", body, signal: input.signal }, | |
| ); | |
| const requestId = readVideoRequestID(payload); | |
| if (!requestId) { | |
| throw new CreativeApiError(200, "The video response did not contain a request ID", "invalid_response"); | |
| } | |
| return requestId; | |
| } | |
| export async function getVideo(input: { | |
| apiKey: string; | |
| requestId: string; | |
| signal?: AbortSignal; | |
| }): Promise<VideoStatus> { | |
| const payload = await publicApiRequest( | |
| input.apiKey, | |
| `/videos/${encodeURIComponent(input.requestId)}`, | |
| { method: "GET", signal: input.signal }, | |
| ); | |
| const status = readVideoStatus(payload); | |
| return status.video ? { ...status, video: { ...status.video, url: resolveMediaURL(status.video.url) } } : status; | |
| } | |
| async function publicApiRequest(apiKey: string, path: string, options: RequestOptions): Promise<unknown> { | |
| const headers = new Headers({ Accept: "application/json", Authorization: `Bearer ${apiKey}` }); | |
| let body: string | undefined; | |
| if (options.body) { | |
| headers.set("Content-Type", "application/json"); | |
| body = JSON.stringify(options.body); | |
| } | |
| const response = await fetch(`/v1${path}`, { | |
| method: options.method ?? "GET", | |
| headers, | |
| body, | |
| signal: options.signal, | |
| }); | |
| const responseText = await response.text(); | |
| let payload: unknown = null; | |
| if (responseText) { | |
| try { | |
| payload = JSON.parse(responseText); | |
| } catch { | |
| payload = null; | |
| } | |
| } | |
| if (!response.ok) { | |
| const error = readError(payload); | |
| const fallback = responseText.trim() || response.statusText || `HTTP ${response.status}`; | |
| throw new CreativeApiError(response.status, error.message ?? fallback, error.code); | |
| } | |
| if (payload === null) throw new CreativeApiError(response.status, "The API returned a non-JSON response", "invalid_response"); | |
| return payload; | |
| } | |
| async function publicResponsesStream(apiKey: string, body: Record<string, unknown>, onUpdate?: (snapshot: ChatStreamSnapshot) => void, signal?: AbortSignal): Promise<ChatResponseResult> { | |
| const response = await fetch("/v1/responses", { | |
| method: "POST", | |
| headers: new Headers({ Accept: "text/event-stream", Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" }), | |
| body: JSON.stringify(body), | |
| signal, | |
| }); | |
| if (!response.ok) { | |
| const responseText = await response.text(); | |
| const payload = parseJSON(responseText); | |
| const error = readError(payload); | |
| throw new CreativeApiError(response.status, error.message ?? (responseText.trim() || response.statusText || `HTTP ${response.status}`), error.code); | |
| } | |
| if (!response.headers.get("content-type")?.includes("text/event-stream")) { | |
| const responseText = await response.text(); | |
| const payload = parseJSON(responseText); | |
| const text = readResponseText(payload); | |
| const reasoning = readResponseReasoning(payload); | |
| const tools = readResponseTools(payload); | |
| if (!text && !reasoning && tools.length === 0) throw new CreativeApiError(response.status, "The Responses API did not return any displayable output", "invalid_response"); | |
| return { text, reasoning, tools }; | |
| } | |
| if (!response.body) throw new CreativeApiError(response.status, "The Responses API stream was empty", "invalid_response"); | |
| const reader = response.body.getReader(); | |
| const decoder = new TextDecoder(); | |
| let buffer = ""; | |
| let text = ""; | |
| let reasoning = ""; | |
| const tools = new Map<string, ChatToolActivity>(); | |
| const snapshot = (): ChatStreamSnapshot => ({ text, reasoning, tools: Array.from(tools.values()) }); | |
| const emit = () => onUpdate?.(snapshot()); | |
| const applyEnvelope = (payload: unknown) => { | |
| const finalText = readResponseText(payload); | |
| const finalReasoning = readResponseReasoning(payload); | |
| const finalTools = readResponseTools(payload); | |
| if (finalText) text = finalText; | |
| if (finalReasoning) reasoning = finalReasoning; | |
| for (const tool of finalTools) tools.set(tool.id, tool); | |
| }; | |
| const consume = (block: string) => { | |
| const data = block.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join("\n"); | |
| if (!data || data === "[DONE]") return; | |
| const payload = parseJSON(data); | |
| if (!isRecord(payload)) return; | |
| const type = typeof payload.type === "string" ? payload.type : ""; | |
| if (type === "response.output_text.delta" && typeof payload.delta === "string") { | |
| text += payload.delta; | |
| emit(); | |
| return; | |
| } | |
| if (type === "response.output_text.done" && typeof payload.text === "string") { | |
| text = payload.text; | |
| emit(); | |
| return; | |
| } | |
| if ((type === "response.reasoning_summary_text.delta" || type === "response.reasoning_text.delta") && typeof payload.delta === "string") { | |
| reasoning += payload.delta; | |
| emit(); | |
| return; | |
| } | |
| if ((type === "response.reasoning_summary_text.done" || type === "response.reasoning_text.done") && typeof payload.text === "string") { | |
| reasoning = payload.text; | |
| emit(); | |
| return; | |
| } | |
| if (type === "response.output_item.added" || type === "response.output_item.done") { | |
| const item = isRecord(payload.item) ? payload.item : undefined; | |
| if (!item) return; | |
| if (item.type === "message") { | |
| const itemText = readContentText(item.content); | |
| if (type === "response.output_item.done" && itemText) text = itemText; | |
| } else if (item.type === "reasoning") { | |
| const itemReasoning = readReasoningItem(item); | |
| if (itemReasoning) reasoning = itemReasoning; | |
| } else { | |
| const tool = readToolItem(item, type === "response.output_item.done" ? "completed" : "in_progress"); | |
| if (tool) tools.set(tool.id, tool); | |
| } | |
| emit(); | |
| return; | |
| } | |
| if (type === "response.function_call_arguments.delta" || type === "response.custom_tool_call_input.delta") { | |
| updateToolDetail(tools, payload, typeof payload.delta === "string" ? payload.delta : "", true); | |
| emit(); | |
| return; | |
| } | |
| if (type === "response.function_call_arguments.done" || type === "response.custom_tool_call_input.done") { | |
| const detail = typeof payload.arguments === "string" ? payload.arguments : typeof payload.input === "string" ? payload.input : ""; | |
| updateToolDetail(tools, payload, detail, false); | |
| emit(); | |
| return; | |
| } | |
| if (type === "response.created" || type === "response.in_progress" || type === "response.completed" || type === "response.incomplete") { | |
| const responsePayload = isRecord(payload.response) ? payload.response : undefined; | |
| if (type === "response.completed" || type === "response.incomplete") { | |
| applyEnvelope(responsePayload); | |
| emit(); | |
| } | |
| if (type === "response.incomplete") { | |
| throw new CreativeApiError(response.status, readIncompleteReason(responsePayload) || "The response ended before completion", "incomplete_response"); | |
| } | |
| return; | |
| } | |
| if (type === "response.failed" || type === "error") { | |
| const error = readError(isRecord(payload.response) ? payload.response : payload); | |
| throw new CreativeApiError(response.status, error.message ?? "The Responses API stream failed", error.code); | |
| } | |
| }; | |
| while (true) { | |
| const { done, value } = await reader.read(); | |
| buffer = (buffer + decoder.decode(value, { stream: !done })).replaceAll("\r\n", "\n"); | |
| let boundary = buffer.indexOf("\n\n"); | |
| while (boundary >= 0) { | |
| consume(buffer.slice(0, boundary)); | |
| buffer = buffer.slice(boundary + 2); | |
| boundary = buffer.indexOf("\n\n"); | |
| } | |
| if (done) break; | |
| } | |
| if (buffer.trim()) consume(buffer); | |
| if (!text.trim() && !reasoning.trim() && tools.size === 0) throw new CreativeApiError(response.status, "The Responses API did not return any displayable output", "invalid_response"); | |
| return snapshot(); | |
| } | |
| function resolveMediaURL(value: string): string { | |
| const url = value.trim(); | |
| if (!url || url.startsWith("data:") || url.startsWith("blob:")) return url; | |
| try { | |
| const browserOrigin = typeof window === "undefined" ? "http://localhost" : window.location.origin; | |
| const resolved = new URL(url, `${browserOrigin}/`); | |
| if (resolved.pathname.startsWith("/v1/media/images/")) { | |
| return `${resolved.pathname}${resolved.search}${resolved.hash}`; | |
| } | |
| return resolved.origin === browserOrigin ? `${resolved.pathname}${resolved.search}${resolved.hash}` : resolved.toString(); | |
| } catch { | |
| return url; | |
| } | |
| } | |
| function readVideoRequestID(payload: unknown): string { | |
| return isRecord(payload) && typeof payload.request_id === "string" ? payload.request_id.trim() : ""; | |
| } | |
| function readResponseText(payload: unknown): string { | |
| if (!isRecord(payload)) return ""; | |
| if (typeof payload.output_text === "string" && payload.output_text.trim()) return payload.output_text.trim(); | |
| if (!Array.isArray(payload.output)) return ""; | |
| return payload.output.flatMap((item) => { | |
| if (!isRecord(item) || item.type !== "message") return []; | |
| return [readContentText(item.content)]; | |
| }).filter(Boolean).join("\n").trim(); | |
| } | |
| function readResponseReasoning(payload: unknown): string { | |
| if (!isRecord(payload) || !Array.isArray(payload.output)) return ""; | |
| return payload.output.flatMap((item) => { | |
| if (!isRecord(item) || item.type !== "reasoning") return []; | |
| return [readReasoningItem(item)]; | |
| }).filter(Boolean).join("\n").trim(); | |
| } | |
| function readResponseTools(payload: unknown): ChatToolActivity[] { | |
| if (!isRecord(payload) || !Array.isArray(payload.output)) return []; | |
| return payload.output.flatMap((item) => { | |
| if (!isRecord(item) || item.type === "message" || item.type === "reasoning") return []; | |
| const tool = readToolItem(item, "completed"); | |
| return tool ? [tool] : []; | |
| }); | |
| } | |
| function readReasoningItem(item: Record<string, unknown>): string { | |
| const summary = readContentText(item.summary); | |
| return summary || readContentText(item.content); | |
| } | |
| function readToolItem(item: Record<string, unknown>, fallbackStatus: ChatToolActivity["status"]): ChatToolActivity | null { | |
| const type = typeof item.type === "string" ? item.type.trim() : ""; | |
| if (!type) return null; | |
| const id = firstString(item.id, item.call_id) || `${type}-${firstString(item.name) || "tool"}`; | |
| const name = firstString(item.name) || toolNameFromType(type); | |
| const action = isRecord(item.action) ? item.action : undefined; | |
| const detail = firstString(item.arguments, item.input, action?.query, item.query); | |
| return { id, type, name, status: readToolStatus(item.status, fallbackStatus), detail }; | |
| } | |
| function updateToolDetail(tools: Map<string, ChatToolActivity>, payload: Record<string, unknown>, detail: string, append: boolean): void { | |
| const id = firstString(payload.item_id, payload.call_id); | |
| if (!id) return; | |
| const current = tools.get(id) ?? { id, type: "function_call", name: "tool", status: "in_progress" as const, detail: "" }; | |
| tools.set(id, { ...current, detail: append ? current.detail + detail : detail || current.detail }); | |
| } | |
| function readToolStatus(value: unknown, fallback: ChatToolActivity["status"]): ChatToolActivity["status"] { | |
| if (value === "completed") return "completed"; | |
| if (value === "failed" || value === "incomplete") return "failed"; | |
| if (value === "in_progress" || value === "searching") return "in_progress"; | |
| return fallback; | |
| } | |
| function toolNameFromType(type: string): string { | |
| if (type === "web_search_call" || type === "web_search") return "web_search"; | |
| if (type === "x_search_call" || type === "x_search") return "x_search"; | |
| return type.replace(/_call$/, ""); | |
| } | |
| function readIncompleteReason(payload: unknown): string { | |
| if (!isRecord(payload) || !isRecord(payload.incomplete_details)) return ""; | |
| const reason = firstString(payload.incomplete_details.reason); | |
| return reason ? `The response was incomplete: ${reason}` : ""; | |
| } | |
| function firstString(...values: unknown[]): string { | |
| for (const value of values) { | |
| if (typeof value === "string" && value.trim()) return value.trim(); | |
| } | |
| return ""; | |
| } | |
| function readContentText(content: unknown): string { | |
| if (typeof content === "string") return content.trim(); | |
| if (!Array.isArray(content)) return ""; | |
| return content.map((item) => { | |
| if (typeof item === "string") return item; | |
| if (!isRecord(item)) return ""; | |
| return typeof item.text === "string" ? item.text : typeof item.content === "string" ? item.content : ""; | |
| }).filter(Boolean).join("\n").trim(); | |
| } | |
| function readImages(payload: unknown): ImageResult[] { | |
| if (!isRecord(payload) || !Array.isArray(payload.data)) return []; | |
| return payload.data.flatMap((item) => { | |
| if (!isRecord(item)) return []; | |
| const url = typeof item.url === "string" && item.url.trim() | |
| ? item.url | |
| : typeof item.b64_json === "string" && item.b64_json.trim() | |
| ? `data:image/png;base64,${item.b64_json}` | |
| : ""; | |
| return url ? [{ url, revisedPrompt: typeof item.revised_prompt === "string" ? item.revised_prompt : undefined }] : []; | |
| }); | |
| } | |
| function readVideoStatus(payload: unknown): VideoStatus { | |
| if (!isRecord(payload) || !isVideoStatus(payload.status)) { | |
| throw new CreativeApiError(200, "The video status response was invalid", "invalid_response"); | |
| } | |
| const result: VideoStatus = { | |
| status: payload.status, | |
| model: typeof payload.model === "string" ? payload.model : undefined, | |
| progress: typeof payload.progress === "number" && Number.isFinite(payload.progress) | |
| ? Math.max(0, Math.min(100, payload.progress)) | |
| : payload.status === "done" ? 100 : 0, | |
| }; | |
| if (isRecord(payload.video) && typeof payload.video.url === "string") { | |
| result.video = { | |
| url: payload.video.url, | |
| duration: typeof payload.video.duration === "number" ? payload.video.duration : undefined, | |
| respectModeration: typeof payload.video.respect_moderation === "boolean" ? payload.video.respect_moderation : undefined, | |
| }; | |
| } | |
| if (isRecord(payload.error) && typeof payload.error.message === "string") { | |
| result.error = { | |
| code: typeof payload.error.code === "string" ? payload.error.code : undefined, | |
| message: payload.error.message, | |
| }; | |
| } | |
| return result; | |
| } | |
| function readError(payload: unknown): { code?: string; message?: string } { | |
| if (!isRecord(payload)) return {}; | |
| const error = isRecord(payload.error) ? payload.error : payload; | |
| return { | |
| code: typeof error.code === "string" ? error.code : undefined, | |
| message: typeof error.message === "string" ? error.message : undefined, | |
| }; | |
| } | |
| function parseJSON(value: string): unknown { | |
| if (!value.trim()) return null; | |
| try { | |
| return JSON.parse(value); | |
| } catch { | |
| return null; | |
| } | |
| } | |
| function isVideoStatus(value: unknown): value is VideoStatus["status"] { | |
| return value === "pending" || value === "done" || value === "failed"; | |
| } | |
| function isRecord(value: unknown): value is Record<string, unknown> { | |
| return typeof value === "object" && value !== null && !Array.isArray(value); | |
| } | |