import { useEffect, useMemo, useState, type ReactNode } from 'react'; import { Brain, Image, ListChecks, Plus, Trash2, Video } from 'lucide-react'; import { Button, Checkbox, Input, Select } from '../../components/ui'; import { addCapabilityType, defaultCapabilityConfig, modelTypeGroup, modelTypeLabel, removeCapabilityType, updateCapabilityConfig, type CapabilityEditorState, } from './base-model-capabilities'; type FieldType = | 'boolean' | 'number' | 'text' | 'list' | 'numberList' | 'range' | 'ratioRange' | 'scopedList' | 'scopedNumberList' | 'scopedRange' | 'scopedNumber'; type FieldScope = 'mode' | 'resolution' | 'mixed'; type FieldDefinition = { key: string; label: string; hint?: string; placeholder?: string; type: FieldType; scope?: FieldScope; }; type ValueOption = { label: string; value: string }; const textFields: FieldDefinition[] = [ { key: 'supportTool', label: '工具调用', hint: 'function calling / tools', type: 'boolean' }, { key: 'supportStructuredOutput', label: '结构化输出', hint: 'JSON Schema 等输出', type: 'boolean' }, { key: 'supportThinking', label: '推理能力', hint: '支持 reasoning / thinking 参数', type: 'boolean' }, { key: 'supportThinkingModeSwitch', label: '思考开关', hint: '可按请求切换', type: 'boolean' }, { key: 'supportWebSearch', label: '联网搜索', type: 'boolean' }, { key: 'max_context_tokens', label: '上下文 Token', placeholder: '128000', type: 'number' }, { key: 'max_input_tokens', label: '最大输入 Token', placeholder: '64000', type: 'number' }, { key: 'max_output_tokens', label: '最大输出 Token', placeholder: '8192', type: 'number' }, { key: 'max_thinking_tokens', label: '最大思考 Token', placeholder: '32768', type: 'number' }, { key: 'thinkingEffortLevels', label: '推理深度', hint: '声明模型支持的 reasoning_effort 取值,可填写 max 等供应商自定义值', placeholder: 'none, minimal, low, medium, high, xhigh, max', type: 'list' }, ]; const embeddingFields: FieldDefinition[] = [ { key: 'dimensions', label: '向量维度', placeholder: '1024, 768, 512', type: 'numberList' }, ]; const imageFields: FieldDefinition[] = [ { key: 'support_base64_input', label: 'Base64 输入', type: 'boolean' }, { key: 'support_url_input', label: 'URL 输入', type: 'boolean' }, { key: 'input_multiple_images', label: '多图输入', type: 'boolean' }, { key: 'input_max_images_count', label: '最多输入图片', placeholder: '10', type: 'number' }, { key: 'output_multiple_images', label: '多图输出', type: 'boolean' }, { key: 'output_max_images_count', label: '最多输出图片', placeholder: '4', type: 'number' }, { key: 'output_resolutions', label: '输出分辨率', placeholder: '1K, 2K, 4K', type: 'list' }, { key: 'output_max_size', label: '最大输出尺寸', placeholder: '16777216', type: 'number' }, { key: 'aspect_ratio_allowed', label: '支持宽高比', placeholder: '16:9, 1:1, 9:16', type: 'list' }, { key: 'aspect_ratio_range', label: '宽高比范围', hint: '按宽/高数值限制,如 0.125 - 8', placeholder: '0.125 - 8', type: 'ratioRange' }, ]; const videoFields: FieldDefinition[] = [ { key: 'support_base64_input', label: 'Base64 输入', type: 'boolean' }, { key: 'support_url_input', label: 'URL 输入', type: 'boolean' }, { key: 'output_resolutions', label: '输出分辨率', hint: '可直接配置,也可按首帧/首尾帧等模式配置', placeholder: '720p, 1080p', type: 'scopedList', scope: 'mode' }, { key: 'aspect_ratio_allowed', label: '支持宽高比', hint: '支持按分辨率配置可选宽高比', placeholder: '16:9, 9:16, 1:1', type: 'scopedList', scope: 'resolution' }, { key: 'aspect_ratio_range', label: '宽高比范围', hint: '按宽/高数值限制,如 0.125 - 8', placeholder: '0.125 - 8', type: 'ratioRange' }, { key: 'duration_range', label: '时长范围', hint: '支持按分辨率或模式配置不同范围', placeholder: '5 - 10', type: 'scopedRange', scope: 'mixed' }, { key: 'duration_options', label: '可选时长', hint: '与时长范围二选一;只允许这些秒数', placeholder: '5, 10', type: 'scopedNumberList', scope: 'mixed' }, { key: 'duration_step', label: '时长间隔', hint: '自定义时长按该秒数递增或取整', placeholder: '1', type: 'scopedNumber', scope: 'mixed' }, { key: 'input_audio', label: '音频输入', type: 'boolean' }, { key: 'output_audio', label: '输出音频', type: 'boolean' }, { key: 'output_bgm', label: '背景音乐', type: 'boolean' }, { key: 'input_first_frame', label: '首帧输入', type: 'boolean' }, { key: 'input_last_frame', label: '尾帧输入', type: 'boolean' }, { key: 'input_first_last_frame', label: '首尾帧模式', type: 'boolean' }, { key: 'input_reference_generate_single', label: '单参考图', type: 'boolean' }, { key: 'input_reference_generate_multiple', label: '多参考图', type: 'boolean' }, { key: 'input_smart_multi_frame', label: '智能多帧', type: 'boolean' }, { key: 'smart_multi_frame_range', label: '智能多帧数量', placeholder: '2 - 9', type: 'range' }, { key: 'smart_multi_frame_mode', label: '智能多帧模式', placeholder: 'native 或 stitch', type: 'text' }, { key: 'smart_multi_frame_duration_range', label: '智能多帧每段时长', placeholder: '2 - 7', type: 'range' }, { key: 'support_video_effect_template', label: '视频特效模板', type: 'boolean' }, { key: 'supported_modes', label: '支持模式', placeholder: 'text_to_video, image_reference', type: 'list' }, { key: 'max_videos', label: '最多视频', placeholder: '1', type: 'number' }, { key: 'max_images', label: '最多图片', placeholder: '4', type: 'number' }, { key: 'max_audios', label: '最多音频', placeholder: '1', type: 'number' }, { key: 'max_elements', label: '最多主体', placeholder: '4', type: 'number' }, { key: 'max_images_and_elements', label: '图片主体合计', placeholder: '4', type: 'number' }, { key: 'support_instruction_edit', label: '指令编辑', type: 'boolean' }, ]; const model3dFields: FieldDefinition[] = [ { key: 'support_texture', label: '纹理生成', type: 'boolean' }, { key: 'support_part_generation', label: '分部生成', type: 'boolean' }, { key: 'support_image_autofix', label: '图片自动优化', type: 'boolean' }, { key: 'support_quad', label: '四边面输出', type: 'boolean' }, { key: 'support_smart_low_poly', label: '智能低模', type: 'boolean' }, { key: 'geometry_quality_options', label: '几何质量', placeholder: 'standard, detailed', type: 'list' }, { key: 'max_face_limit', label: '三角面上限', placeholder: '20000', type: 'number' }, { key: 'max_face_limit_quad', label: '四边面上限', placeholder: '10000', type: 'number' }, ]; export function BaseModelCapabilityEditor(props: { modelType: string; typeOptions: string[]; value: CapabilityEditorState; onChange: (value: CapabilityEditorState) => void; }) { const enabledTypes = props.value.types; const [activeType, setActiveType] = useState(enabledTypes[0] ?? props.modelType); const [pendingType, setPendingType] = useState(''); const availableTypes = useMemo( () => Array.from(new Set([props.modelType, ...props.typeOptions].filter(Boolean))), [props.modelType, props.typeOptions], ); const addableTypes = availableTypes.filter((type) => !enabledTypes.includes(type)); const activeConfig = props.value.typeConfigs[activeType] ?? defaultCapabilityConfig(activeType); const fieldGroups = capabilityFieldGroups(activeType); const capabilitySummary = useMemo( () => summarizeEnabledCapabilities(enabledTypes, props.value.typeConfigs), [enabledTypes, props.value.typeConfigs], ); useEffect(() => { if (!enabledTypes.includes(activeType)) setActiveType(enabledTypes[0] ?? props.modelType); }, [activeType, enabledTypes, props.modelType]); function addType() { const nextType = pendingType || addableTypes[0]; if (!nextType) return; props.onChange(addCapabilityType(props.value, nextType)); setActiveType(nextType); setPendingType(''); } function removeType(type: string) { const next = removeCapabilityType(props.value, type); props.onChange(next); setActiveType(next.types[0] ?? props.modelType); } function patchConfig(key: string, value: unknown) { const nextConfig = { ...activeConfig }; if (value === undefined) delete nextConfig[key]; else nextConfig[key] = value; const exclusiveKey = exclusiveCapabilityFields[key]; if (exclusiveKey && hasMeaningfulValue(value)) delete nextConfig[exclusiveKey]; props.onChange(updateCapabilityConfig(props.value, activeType, sanitizeCapabilityConfig(nextConfig))); } return ( 模型能力 左侧维护已启用能力,右侧编辑该能力的默认配置。 {fieldGroups.length ? ( fieldGroups.map((group) => ( {group.fields.map((field) => ( patchConfig(field.key, value)} /> ))} )) ) : ( 当前能力暂无通用表单 仍会保存该能力条目,并保留原始能力字段;后续可按平台专属能力继续扩展。 )} } title="保留字段"> {preservedConfigCount(activeConfig, fieldGroups)} 个未展示字段 这些字段保存时会继续保留在 {activeType} 能力配置内。 ); } function CapabilityFormGroup(props: { children: ReactNode; icon: ReactNode; title: string }) { return ( {props.icon} {props.title} {props.children} ); } function CapabilityFormRow(props: { config: Record; defaultConfig: Record; field: FieldDefinition; onChange: (value: unknown) => void; }) { const value = props.config[props.field.key]; return ( {props.field.label} {props.field.hint && {props.field.hint}} ); } function FieldControl(props: { config: Record; defaultValue: unknown; field: FieldDefinition; value: unknown; onChange: (value: unknown) => void; }) { if (props.field.type === 'boolean') { return ; } if (props.field.type === 'range') { const range = Array.isArray(props.value) ? props.value : []; return ( props.onChange([numberValue(event.target.value), numberValue(range[1])])} /> props.onChange([numberValue(range[0]), numberValue(event.target.value)])} /> ); } if (props.field.type === 'ratioRange') { const range = Array.isArray(props.value) ? props.value : []; return ( props.onChange([numberValue(event.target.value), numberValue(range[1])])} /> props.onChange([numberValue(range[0]), numberValue(event.target.value)])} /> ); } if (props.field.type === 'number') { return props.onChange(numberValue(event.target.value))} />; } if (isScopedField(props.field)) { return ; } if (props.field.type === 'text') { return props.onChange(event.target.value)} />; } return ; } function BooleanFieldControl(props: { defaultText: string; onChange: (value: unknown) => void; value: unknown; }) { return ( props.onChange(booleanValueFromSelect(event.target.value))} > 未设置({props.defaultText}) 支持 不支持 ); } function ScopedFieldControl(props: { config: Record; field: FieldDefinition; value: unknown; onChange: (value: unknown) => void; }) { const grouped = isPlainRecord(props.value); const directValue = grouped ? defaultDirectValue(props.field) : props.value; const scopeOptions = scopeOptionsForField(props.field, props.config, props.value); const entries = grouped ? Object.entries(props.value as Record) : []; const canGroup = scopeOptions.length > 0 || entries.length > 0; function switchMode(nextGrouped: boolean) { if (nextGrouped === grouped) return; if (!nextGrouped) { props.onChange(entries[0]?.[1] ?? defaultDirectValue(props.field)); return; } const firstKey = firstAvailableScopeKey(scopeOptions, []); props.onChange({ [firstKey]: normalizeScopedValue(props.field, props.value) }); } function setEntryKey(oldKey: string, nextKey: string) { if (!nextKey || !grouped) return; const next = { ...(props.value as Record) }; const current = next[oldKey]; delete next[oldKey]; next[nextKey] = current; props.onChange(next); } function setEntryValue(key: string, nextValue: unknown) { props.onChange({ ...(props.value as Record), [key]: nextValue }); } function removeEntry(key: string) { const next = { ...(props.value as Record) }; delete next[key]; props.onChange(Object.keys(next).length ? next : defaultDirectValue(props.field)); } function addEntry() { const used = entries.map(([key]) => key); const nextKey = firstAvailableScopeKey(scopeOptions, used); const current = grouped ? (props.value as Record) : {}; props.onChange({ ...current, [nextKey]: defaultDirectValue(props.field) }); } return ( switchMode(false)}>统一配置 switchMode(true)}>条件分组 {!grouped ? ( ) : ( {entries.map(([key, value]) => ( setEntryKey(key, event.target.value)}> {ensureScopeOption(scopeOptions, key).map((option) => ( {option.label} ))} setEntryValue(key, nextValue)} /> removeEntry(key)}> ))} 添加条件 )} ); } function ScopedValueInput(props: { config: Record; field: FieldDefinition; onChange: (value: unknown) => void; placeholder?: string; value: unknown; }) { if (props.field.type === 'scopedRange') { const range = Array.isArray(props.value) ? props.value : []; return ( props.onChange([numberValue(event.target.value), numberValue(range[1])])} /> props.onChange([numberValue(range[0]), numberValue(event.target.value)])} /> ); } if (props.field.type === 'scopedNumber') { return props.onChange(numberValue(event.target.value))} />; } return ; } function MultiValueControl(props: { config: Record; field: FieldDefinition; onChange: (value: unknown) => void; value: unknown; }) { const [customValue, setCustomValue] = useState(''); const selected = valueArray(props.value); const options = multiOptionsForField(props.field, props.config, props.value); const selectedSet = new Set(selected); function emit(nextValues: string[]) { const values = uniqueStrings(nextValues); props.onChange(isNumberListField(props.field) ? values.map(Number).filter(Number.isFinite) : values); } function toggle(value: string) { emit(selectedSet.has(value) ? selected.filter((item) => item !== value) : [...selected, value]); } function addCustom() { const values = customValue.split(/[,,]/).map((item) => item.trim()).filter(Boolean); if (!values.length) return; emit([...selected, ...values]); setCustomValue(''); } return ( {options.map((option) => ( toggle(option.value)} /> {option.label} ))} setCustomValue(event.target.value)} onKeyDown={(event) => { if (event.key === 'Enter') { event.preventDefault(); addCustom(); } }} /> 添加 ); } type ScopeOption = { label: string; requires?: string; value: string }; const modeScopeOptions: ScopeOption[] = [ { value: 'input_first_frame', label: '首帧模式 (input_first_frame)', requires: 'input_first_frame' }, { value: 'input_first_last_frame', label: '首尾帧模式 (input_first_last_frame)', requires: 'input_first_last_frame' }, { value: 'input_last_frame', label: '尾帧模式 (input_last_frame)', requires: 'input_last_frame' }, { value: 'input_smart_multi_frame', label: '智能多帧 (input_smart_multi_frame)', requires: 'input_smart_multi_frame' }, ]; const videoResolutionOptions = ['480p', '720p', '1080p', '1440p', '2160p']; const imageResolutionOptions = ['1K', '2K', '3K', '4K', '8K']; const videoAspectRatioOptions = ['adaptive', '16:9', '9:16', '1:1', '4:3', '3:4']; const imageAspectRatioOptions = [ 'adaptive', '1:1', '16:9', '9:16', '4:3', '3:4', '3:2', '2:3', '5:4', '4:5', '5:3', '3:5', '21:9', '9:21', '2:1', '1:2', '4:1', '1:4', '8:1', '1:8', '7:4', '4:7', ]; const thinkingEffortOptions = ['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max']; const omniVideoModeOptions = ['text_to_video', 'image_reference', 'element_reference', 'first_last_frame', 'video_reference', 'video_edit', 'multi_shot']; const durationOptionValues = ['1', '2', '3', '4', '5', '6', '8', '10', '15', '20', '25', '30']; const exclusiveCapabilityFields: Record = { duration_range: 'duration_options', duration_options: 'duration_range', }; function isScopedField(field: FieldDefinition) { return field.type === 'scopedList' || field.type === 'scopedNumberList' || field.type === 'scopedRange' || field.type === 'scopedNumber'; } function scopeOptionsForField(field: FieldDefinition, config: Record, value: unknown): ScopeOption[] { const options: ScopeOption[] = []; if (field.scope === 'mode' || field.scope === 'mixed') { options.push(...modeScopeOptions.filter((option) => !option.requires || config[option.requires] === true)); flattenStringValues(config.supported_modes).forEach((mode) => options.push({ value: mode, label: `${friendlyScopeLabel(mode)} (${mode})` })); } if (field.scope === 'resolution' || field.scope === 'mixed') { [...flattenStringValues(config.output_resolutions), ...videoResolutionOptions].forEach((resolution) => { options.push({ value: resolution, label: `${friendlyScopeLabel(resolution)} (${resolution})` }); }); } if (isPlainRecord(value)) { Object.keys(value).forEach((key) => options.push({ value: key, label: `${friendlyScopeLabel(key)} (${key})` })); } return dedupeScopeOptions(options); } function ensureScopeOption(options: ScopeOption[], value: string) { if (!value || options.some((option) => option.value === value)) return options; return [...options, { value, label: `${friendlyScopeLabel(value)} (${value})` }]; } function firstAvailableScopeKey(options: ScopeOption[], used: string[]) { return options.find((option) => !used.includes(option.value))?.value ?? `condition_${used.length + 1}`; } function sanitizeCapabilityConfig(config: Record) { const supportedModeKeys = new Set([ ...modeScopeOptions.filter((option) => !option.requires || config[option.requires] === true).map((option) => option.value), ...flattenStringValues(config.supported_modes), ]); const keepScopedModeKey = (key: string) => !isKnownModeScopeKey(key) || supportedModeKeys.has(key); return Object.fromEntries( Object.entries(config).map(([key, value]) => { if (!isScopedConfigKey(key) || !isPlainRecord(value)) return [key, value]; const filtered = Object.fromEntries(Object.entries(value).filter(([scopeKey]) => keepScopedModeKey(scopeKey))); if (Object.keys(filtered).length > 0) return [key, filtered]; return [key, defaultDirectValueForConfigKey(key)]; }), ); } function isScopedConfigKey(key: string) { return key === 'output_resolutions' || key === 'aspect_ratio_allowed' || key === 'duration_range' || key === 'duration_options' || key === 'duration_step'; } function isKnownModeScopeKey(key: string) { return modeScopeOptions.some((option) => option.value === key) || ['text_to_video', 'image_reference', 'element_reference', 'first_last_frame', 'video_reference', 'video_edit', 'multi_shot'].includes(key); } function defaultDirectValueForConfigKey(key: string) { if (key === 'duration_range') return [0, 0]; if (key === 'duration_step') return 0; return []; } function multiOptionsForField(field: FieldDefinition, config: Record, value: unknown): ValueOption[] { let options: string[] = []; if (field.key === 'output_resolutions') options = field.scope ? videoResolutionOptions : imageResolutionOptions; if (field.key === 'aspect_ratio_allowed') options = field.scope ? videoAspectRatioOptions : imageAspectRatioOptions; if (field.key === 'supported_modes') options = omniVideoModeOptions; if (field.key === 'thinkingEffortLevels') options = thinkingEffortOptions; if (field.key === 'duration_options') options = durationOptionValues; if (field.key === 'dimensions') options = ['3072', '1536', '1024', '768', '512', '256']; if (field.key === 'geometry_quality_options') options = ['standard', 'detailed']; options = orderedOptionValues(options, [...valueArray(value), ...flattenStringValues(config[field.key])], field); return options.map((item) => ({ value: item, label: friendlyOptionLabel(field, item) })); } function orderedOptionValues(baseOptions: string[], extraOptions: string[], field: FieldDefinition) { const base = uniqueStrings(baseOptions); const baseSet = new Set(base); const extras = uniqueStrings(extraOptions).filter((item) => !baseSet.has(item)); if (field.key === 'duration_options') { return uniqueStrings([...base, ...extras]).sort((a, b) => Number(a) - Number(b)); } const sortedExtras = isNumberListField(field) ? extras.sort((a, b) => Number(a) - Number(b)) : extras.sort((a, b) => a.localeCompare(b)); return [...base, ...sortedExtras]; } function friendlyOptionLabel(field: FieldDefinition, value: string) { if (field.key === 'duration_options') return `${value}秒`; if (field.key === 'supported_modes') return friendlyScopeLabel(value); return value; } function valueArray(value: unknown): string[] { if (Array.isArray(value)) return value.map(String).filter(Boolean); if (value === undefined || value === null || value === '') return []; return [String(value)]; } function isNumberListField(field: FieldDefinition) { return field.type === 'numberList' || field.type === 'scopedNumberList'; } function friendlyScopeLabel(key: string) { const labels: Record = { input_first_frame: '首帧模式', input_first_last_frame: '首尾帧模式', input_last_frame: '尾帧模式', input_smart_multi_frame: '智能多帧', text_to_video: '文生视频', image_reference: '图片参考', element_reference: '主体参考', first_last_frame: '首尾帧', video_reference: '视频参考', video_edit: '视频编辑', multi_shot: '多镜头', }; return labels[key] ?? key; } function dedupeScopeOptions(options: ScopeOption[]) { const seen = new Set(); return options.filter((option) => { if (!option.value || seen.has(option.value)) return false; seen.add(option.value); return true; }); } function flattenStringValues(value: unknown): string[] { if (Array.isArray(value)) return value.map(String).filter(Boolean); if (!isPlainRecord(value)) return []; return Object.values(value).flatMap((item) => Array.isArray(item) ? item.map(String).filter(Boolean) : []); } function defaultDirectValue(field: FieldDefinition) { if (field.type === 'scopedRange') return [0, 0]; if (field.type === 'scopedNumber') return 0; return []; } function normalizeScopedValue(field: FieldDefinition, value: unknown) { if (isPlainRecord(value)) return Object.values(value)[0] ?? defaultDirectValue(field); if (field.type === 'scopedRange') return Array.isArray(value) ? value : defaultDirectValue(field); if (field.type === 'scopedNumber') return typeof value === 'number' || typeof value === 'string' ? numberValue(value) : 0; return Array.isArray(value) ? value : defaultDirectValue(field); } function hasMeaningfulValue(value: unknown): boolean { if (value === undefined || value === null || value === '') return false; if (Array.isArray(value)) return value.length > 0; if (isPlainRecord(value)) return Object.keys(value).length > 0; return true; } function capabilityFieldGroups(type: string): Array<{ title: string; icon: ReactNode; fields: FieldDefinition[] }> { if (isTextType(type)) return [{ title: '文本默认能力', icon: , fields: textFields }]; if (type === 'text_embedding') return [{ title: '向量默认能力', icon: , fields: embeddingFields }]; if (isImageType(type)) return [{ title: '图像默认能力', icon: , fields: imageFieldsFor(type) }]; if (isVideoType(type)) return [{ title: '视频默认能力', icon: , fields: videoFieldsFor(type) }]; if (isModel3dType(type)) return [{ title: '3D 默认能力', icon: , fields: model3dFields }]; return []; } function imageFieldsFor(type: string) { if (type === 'image_edit') return imageFields; return imageFields.filter((field) => !field.key.startsWith('input_')); } function videoFieldsFor(type: string) { if (type === 'video_generate') return videoFields.filter((field) => !field.key.startsWith('input_') && field.key !== 'supported_modes'); if (type === 'image_to_video') return videoFields.filter((field) => field.key !== 'supported_modes' && field.key !== 'max_audios'); if (type === 'video_edit') return videoFields.filter((field) => !field.key.includes('reference_generate') && field.key !== 'supported_modes'); return videoFields; } function preservedConfigCount(config: Record, groups: Array<{ fields: FieldDefinition[] }>) { const shown = new Set(groups.flatMap((group) => group.fields.map((field) => field.key))); return Object.keys(config).filter((key) => !shown.has(key)).length; } function summarizeEnabledCapabilities(types: string[], typeConfigs: Record>) { if (!types.length) return '暂无启用能力,添加能力后会在这里显示模型支持范围。'; const groups = uniqueStrings(types.map(modelTypeGroup)); const names = types.map(modelTypeLabel); const nameText = names.length > 3 ? `${names.slice(0, 3).join('、')}等 ${names.length} 项能力` : names.join('、'); const highlights = capabilitySummaryHighlights(types, typeConfigs); const base = `已启用 ${types.length} 项能力,覆盖${groups.join('、')}。包含 ${nameText}`; return highlights.length ? `${base};${highlights.join(',')}。` : `${base}。`; } function capabilitySummaryHighlights(types: string[], typeConfigs: Record>) { const highlights: string[] = []; const textLimits = uniqueStrings(types.flatMap((type) => stringValue(typeConfigs[type]?.max_context_tokens ? typeConfigs[type].max_context_tokens : '').split(',').filter(Boolean))); if (textLimits.length) highlights.push(`上下文 ${textLimits[0]} Token`); const resolutions = uniqueStrings(types.flatMap((type) => flattenStringValues(typeConfigs[type]?.output_resolutions))); if (resolutions.length) highlights.push(`输出分辨率 ${resolutions.slice(0, 4).join('/')}`); const aspectRatioRanges = uniqueStrings(types.flatMap((type) => aspectRatioRangeSummaryValues(typeConfigs[type]))); if (aspectRatioRanges.length) highlights.push(`宽高比 ${aspectRatioRanges.slice(0, 2).join('/')}`); const durations = uniqueStrings(types.flatMap((type) => durationSummaryValues(typeConfigs[type]))); if (durations.length) highlights.push(`时长 ${durations.slice(0, 3).join('/')}`); const booleanLabels = types.flatMap((type) => enabledBooleanLabels(typeConfigs[type])); if (booleanLabels.length) highlights.push(`支持${uniqueStrings(booleanLabels).slice(0, 3).join('、')}`); return highlights.slice(0, 4); } function aspectRatioRangeSummaryValues(config?: Record) { const range = config?.aspect_ratio_range; if (!Array.isArray(range) || range.length < 2) return []; return [`${range[0]}-${range[1]}`]; } function durationSummaryValues(config?: Record) { if (!config) return []; const options = flattenStringValues(config.duration_options).map((item) => `${item}秒`); if (options.length) return options; const range = config.duration_range; if (Array.isArray(range) && range.length >= 2) return [`${range[0]}-${range[1]}秒`]; if (!isPlainRecord(range)) return []; return Object.values(range).flatMap((item) => Array.isArray(item) && item.length >= 2 ? [`${item[0]}-${item[1]}秒`] : []); } function enabledBooleanLabels(config?: Record) { if (!config) return []; const labels: Record = { supportTool: '工具调用', supportStructuredOutput: '结构化输出', supportThinking: '思考能力', supportWebSearch: '联网搜索', support_base64_input: 'Base64 输入', support_url_input: 'URL 输入', input_multiple_images: '多图输入', output_multiple_images: '多图输出', input_audio: '音频输入', output_audio: '输出音频', output_bgm: '背景音乐', input_first_frame: '首帧输入', input_first_last_frame: '首尾帧模式', input_reference_generate_single: '单参考图', input_reference_generate_multiple: '多参考图', support_video_effect_template: '视频特效模板', }; return Object.entries(labels).filter(([key]) => config[key] === true).map(([, label]) => label); } function isTextType(type: string) { return ['text_generate', 'image_analysis', 'video_understanding', 'audio_understanding', 'omni', 'tools_call'].includes(type); } function isImageType(type: string) { return type === 'image_generate' || type === 'image_edit'; } function isVideoType(type: string) { return ['video_generate', 'image_to_video', 'video_edit', 'omni_video'].includes(type); } function isModel3dType(type: string) { return ['text_to_model', 'image_to_model', 'multiview_to_model', 'mesh_edit'].includes(type); } function numberValue(value: unknown) { const parsed = Number(value); return Number.isFinite(parsed) ? parsed : 0; } function booleanSelectValue(value: unknown) { if (value === true) return 'true'; if (value === false) return 'false'; return 'unset'; } function booleanDefaultText(field: FieldDefinition, defaultValue: unknown) { if (field.key === 'support_base64_input' || field.key === 'support_url_input') return '默认自动判断'; return defaultValue === true ? '默认支持' : '默认不支持'; } function booleanValueFromSelect(value: string) { if (value === 'true') return true; if (value === 'false') return false; return undefined; } function stringValue(value: unknown) { return value === undefined || value === null ? '' : String(value); } function uniqueStrings(values: string[]) { return Array.from(new Set(values.map((item) => item.trim()).filter(Boolean))); } function isPlainRecord(value: unknown): value is Record { return Boolean(value) && !Array.isArray(value) && typeof value === 'object'; }