返回 oh-my-ppt
imageSizeOptions.ts
根目录 / src / renderer / src / components / session-detail / ai-panel / imageSizeOptions.ts
1 import type { ImageModelConfig, ImageModelProvider } from '@shared/image-generation.js'
2
3 export type ImageSizeOption = {
4 value: string
5 label: string
6 }
7
8 const PROVIDER_SIZE_OPTIONS: Record<ImageModelProvider, ImageSizeOption[]> = {
9 jimeng: [
10 { value: '16:9', label: '16:9 · 1664x936' },
11 { value: '1:1', label: '1:1 · 1328x1328' },
12 { value: '4:3', label: '4:3 · 1472x1104' }
13 ],
14 jimeng4: [
15 { value: '16:9', label: '16:9 · 2560x1440' },
16 { value: '1:1', label: '1:1 · 2048x2048' },
17 { value: '4:3', label: '4:3 · 2304x1728' },
18 { value: '9:16', label: '9:16 · 1440x2560' },
19 { value: '3:4', label: '3:4 · 1728x2304' }
20 ],
21 agnes: [
22 { value: '16:9', label: '16:9 · 1024x768' },
23 { value: '1:1', label: '1:1 · 1024x1024' },
24 { value: '4:3', label: '4:3 · 1024x768' }
25 ],
26 siliconflow: [
27 { value: '1024x1024', label: '1:1 · 1024x1024' },
28 { value: '1280x720', label: '16:9 · 1280x720' },
29 { value: '720x1280', label: '9:16 · 720x1280' }
30 ],
31 openaiCompatible: [
32 { value: '1024x1024', label: '1:1 · 1024x1024' },
33 { value: '1536x1024', label: '3:2 · 1536x1024' },
34 { value: '1024x1536', label: '2:3 · 1024x1536' },
35 { value: 'auto', label: 'auto' }
36 ],
37 gemini: [
38 { value: '1:1|1K', label: '1:1 · 1K' },
39 { value: '16:9|1K', label: '16:9 · 1K' },
40 { value: '9:16|1K', label: '9:16 · 1K' },
41 { value: '4:3|1K', label: '4:3 · 1K' },
42 { value: '3:4|1K', label: '3:4 · 1K' },
43 { value: '3:2|1K', label: '3:2 · 1K' },
44 { value: '2:3|1K', label: '2:3 · 1K' },
45 { value: '21:9|1K', label: '21:9 · 1K' },
46 { value: '1:1|2K', label: '1:1 · 2K' },
47 { value: '16:9|2K', label: '16:9 · 2K' },
48 { value: '9:16|2K', label: '9:16 · 2K' },
49 { value: '1:1|4K', label: '1:1 · 4K' },
50 { value: '16:9|4K', label: '16:9 · 4K' },
51 { value: '9:16|4K', label: '9:16 · 4K' }
52 ],
53 seedream: [{ value: '2K', label: '2K' }]
54 }
55
56 const SILICONFLOW_QWEN_SIZE_OPTIONS: ImageSizeOption[] = [
57 { value: '1328x1328', label: '1:1 · 1328x1328' },
58 { value: '1664x928', label: '16:9 · 1664x928' },
59 { value: '928x1664', label: '9:16 · 928x1664' },
60 { value: '1472x1140', label: '4:3 · 1472x1140' },
61 { value: '1140x1472', label: '3:4 · 1140x1472' },
62 { value: '1584x1056', label: '3:2 · 1584x1056' },
63 { value: '1056x1584', label: '2:3 · 1056x1584' }
64 ]
65
66 const SILICONFLOW_KOLORS_SIZE_OPTIONS: ImageSizeOption[] = [
67 { value: '1024x1024', label: '1:1 · 1024x1024' },
68 { value: '960x1280', label: '3:4 · 960x1280' },
69 { value: '768x1024', label: '3:4 · 768x1024' },
70 { value: '720x1440', label: '1:2 · 720x1440' },
71 { value: '720x1280', label: '9:16 · 720x1280' }
72 ]
73
74 const readModelConfigObject = (config?: ImageModelConfig): Record<string, unknown> => {
75 if (!config?.modelConfig) return {}
76 try {
77 const parsed = JSON.parse(config.modelConfig)
78 return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
79 ? (parsed as Record<string, unknown>)
80 : {}
81 } catch {
82 return {}
83 }
84 }
85
86 const readSizeString = (record: Record<string, unknown>, key: string): string => {
87 const value = record[key]
88 return typeof value === 'string' ? value.trim() : ''
89 }
90
91 const buildSizeOption = (value: string, label?: string): ImageSizeOption | null => {
92 const normalizedValue = value.trim()
93 if (!normalizedValue) return null
94 const normalizedLabel = label?.trim()
95 return {
96 value: normalizedValue,
97 label: normalizedLabel || normalizedValue
98 }
99 }
100
101 const normalizeConfiguredSizeOption = (item: unknown): ImageSizeOption | null => {
102 if (typeof item === 'string') return buildSizeOption(item)
103 if (!item || typeof item !== 'object' || Array.isArray(item)) return null
104 const record = item as Record<string, unknown>
105 const value =
106 readSizeString(record, 'value') ||
107 readSizeString(record, 'size') ||
108 readSizeString(record, 'ratio') ||
109 readSizeString(record, 'aspectRatio') ||
110 readSizeString(record, 'aspect_ratio')
111 const width = Number(record.width)
112 const height = Number(record.height)
113 const dimensionLabel =
114 Number.isFinite(width) && Number.isFinite(height) && width > 0 && height > 0
115 ? `${Math.floor(width)}x${Math.floor(height)}`
116 : ''
117 const explicitValue = readSizeString(record, 'value') || readSizeString(record, 'size')
118 return buildSizeOption(
119 explicitValue || dimensionLabel || value,
120 readSizeString(record, 'label') || [value, dimensionLabel].filter(Boolean).join(' · ')
121 )
122 }
123
124 const uniqueSizeOptions = (options: ImageSizeOption[]): ImageSizeOption[] => {
125 const seen = new Set<string>()
126 return options.filter((option) => {
127 if (seen.has(option.value)) return false
128 seen.add(option.value)
129 return true
130 })
131 }
132
133 const isImageSizeOption = (option: ImageSizeOption | null): option is ImageSizeOption =>
134 Boolean(option)
135
136 export const resolveImageSizeOptions = (config?: ImageModelConfig): ImageSizeOption[] => {
137 const modelConfig = readModelConfigObject(config)
138 const configuredOptionsSource =
139 modelConfig.sizes ||
140 modelConfig.supportedSizes ||
141 modelConfig.aspectRatios ||
142 modelConfig.aspect_ratios ||
143 modelConfig.ratios
144 const configuredOptions = Array.isArray(configuredOptionsSource)
145 ? uniqueSizeOptions(
146 configuredOptionsSource.map(normalizeConfiguredSizeOption).filter(isImageSizeOption)
147 )
148 : []
149 if (configuredOptions.length > 0) return configuredOptions
150
151 const fixedSize = readSizeString(modelConfig, 'size')
152 if (fixedSize) return [{ value: fixedSize, label: fixedSize }]
153
154 const width = Number(modelConfig.width)
155 const height = Number(modelConfig.height)
156 if (Number.isFinite(width) && Number.isFinite(height) && width > 0 && height > 0) {
157 const size = `${Math.floor(width)}x${Math.floor(height)}`
158 return [{ value: size, label: size }]
159 }
160
161 const model = readSizeString(modelConfig, 'model')
162 if (config?.provider === 'siliconflow') {
163 if (/qwen\/qwen-image/i.test(model)) return SILICONFLOW_QWEN_SIZE_OPTIONS
164 if (/kolors/i.test(model)) return SILICONFLOW_KOLORS_SIZE_OPTIONS
165 }
166
167 if (config?.provider) return PROVIDER_SIZE_OPTIONS[config.provider]
168 return PROVIDER_SIZE_OPTIONS.jimeng
169 }
170
170 lines TYPESCRIPT