返回 oh-my-ppt
utils.ts
1 import type { ImageGenerationResult } from '../types'
2
3 export const readRecord = (value: unknown): Record<string, unknown> =>
4 value && typeof value === 'object' && !Array.isArray(value)
5 ? (value as Record<string, unknown>)
6 : {}
7
8 export const readString = (record: Record<string, unknown>, key: string): string =>
9 typeof record[key] === 'string' ? String(record[key]).trim() : ''
10
11 export const joinUrl = (baseUrl: string, path: string): string => {
12 const base = baseUrl.replace(/\/+$/, '')
13 if (!base) return path
14 return `${base}${path.startsWith('/') ? path : `/${path}`}`
15 }
16
17 const mimeToExt = (mimeType: string): string => {
18 if (/jpeg/i.test(mimeType)) return '.jpg'
19 if (/webp/i.test(mimeType)) return '.webp'
20 return '.png'
21 }
22
23 const dataUrlToResult = (value: string): ImageGenerationResult | null => {
24 const match = /^data:([^;,]+);base64,(.+)$/i.exec(value.trim())
25 if (!match) return null
26 const mimeType = match[1] || 'image/png'
27 return {
28 bytes: Buffer.from(match[2], 'base64'),
29 mimeType,
30 extension: mimeToExt(mimeType)
31 }
32 }
33
34 const base64ToResult = (value: string): ImageGenerationResult => ({
35 bytes: Buffer.from(value, 'base64'),
36 mimeType: 'image/png',
37 extension: '.png'
38 })
39
40 const downloadImage = async (
41 url: string,
42 signal?: AbortSignal
43 ): Promise<ImageGenerationResult> => {
44 const response = await fetch(url, { signal })
45 if (!response.ok) throw new Error(`Image download failed: ${response.status}`)
46 const mimeType = response.headers.get('content-type')?.split(';', 1)[0] || 'image/png'
47 const arrayBuffer = await response.arrayBuffer()
48 return {
49 bytes: Buffer.from(arrayBuffer),
50 mimeType,
51 extension: mimeToExt(mimeType)
52 }
53 }
54
55 const collectCandidate = (candidate: unknown, candidates: string[]): void => {
56 if (typeof candidate === 'string' && candidate.trim()) {
57 candidates.push(candidate.trim())
58 return
59 }
60 const record = readRecord(candidate)
61 const b64 = readString(record, 'b64_json') || readString(record, 'base64')
62 const url = readString(record, 'url')
63 if (b64) candidates.push(b64)
64 if (url) candidates.push(url)
65 }
66
67 export const collectImageResults = async (
68 payload: unknown,
69 signal?: AbortSignal
70 ): Promise<ImageGenerationResult[]> => {
71 const record = readRecord(payload)
72 const output = readRecord(record.output)
73 const candidates: string[] = []
74
75 for (const item of Array.isArray(record.data) ? record.data : []) {
76 collectCandidate(item, candidates)
77 }
78 for (const item of Array.isArray(output.results) ? output.results : []) {
79 collectCandidate(item, candidates)
80 }
81 for (const item of Array.isArray(output.images) ? output.images : []) {
82 collectCandidate(item, candidates)
83 }
84
85 const collected: ImageGenerationResult[] = []
86 for (const candidate of candidates) {
87 const dataUrl = dataUrlToResult(candidate)
88 if (dataUrl) {
89 collected.push(dataUrl)
90 } else if (/^https?:\/\//i.test(candidate)) {
91 collected.push(await downloadImage(candidate, signal))
92 } else {
93 collected.push(base64ToResult(candidate))
94 }
95 }
96 return collected
97 }
98
99 export const readJsonResponse = async (response: Response): Promise<unknown> => {
100 const text = await response.text()
101 if (!response.ok) {
102 throw new Error(text || `Image generation failed: ${response.status}`)
103 }
104 try {
105 return JSON.parse(text)
106 } catch {
107 throw new Error('Image generation returned invalid JSON')
108 }
109 }
110
110 lines TYPESCRIPT