715 lines
22 KiB
JavaScript
715 lines
22 KiB
JavaScript
/**
|
|
* Pinia Store: Model Config | 模型配置 Store
|
|
* 管理模型配置、渠道切换和模型选择
|
|
*/
|
|
|
|
import { defineStore } from 'pinia'
|
|
import { ref, computed, watch } from 'vue'
|
|
import {
|
|
CHAT_MODELS,
|
|
IMAGE_MODELS,
|
|
VIDEO_MODELS,
|
|
DEFAULT_CHAT_MODEL,
|
|
DEFAULT_IMAGE_MODEL,
|
|
DEFAULT_VIDEO_MODEL
|
|
} from '@/config/models'
|
|
import { PROVIDERS, getProviderList, getDefaultProvider, getProviderConfig, getDefaultBaseUrl } from '@/config/providers'
|
|
|
|
// 存储键名
|
|
const STORAGE_KEYS = {
|
|
PROVIDER: 'api-provider',
|
|
CUSTOM_CHAT_MODELS: 'custom-chat-models',
|
|
CUSTOM_IMAGE_MODELS: 'custom-image-models',
|
|
CUSTOM_VIDEO_MODELS: 'custom-video-models',
|
|
SELECTED_CHAT_MODEL: 'selected-chat-model',
|
|
SELECTED_IMAGE_MODEL: 'selected-image-model',
|
|
SELECTED_VIDEO_MODEL: 'selected-video-model',
|
|
CUSTOM_CHAT_MODELS_BY_PROVIDER: 'custom-chat-models-by-provider',
|
|
CUSTOM_IMAGE_MODELS_BY_PROVIDER: 'custom-image-models-by-provider',
|
|
CUSTOM_VIDEO_MODELS_BY_PROVIDER: 'custom-video-models-by-provider',
|
|
API_KEYS_BY_PROVIDER: 'api-keys-by-provider',
|
|
BASE_URLS_BY_PROVIDER: 'base-urls-by-provider'
|
|
}
|
|
|
|
/**
|
|
* Get stored value from localStorage
|
|
*/
|
|
const getStored = (key, defaultValue = '') => {
|
|
try {
|
|
return localStorage.getItem(key) || defaultValue
|
|
} catch {
|
|
return defaultValue
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set stored value to localStorage
|
|
*/
|
|
const setStored = (key, value) => {
|
|
try {
|
|
if (value) {
|
|
localStorage.setItem(key, value)
|
|
} else {
|
|
localStorage.removeItem(key)
|
|
}
|
|
} catch {
|
|
// ignore
|
|
}
|
|
}
|
|
|
|
const removeStored = (key) => {
|
|
try {
|
|
localStorage.removeItem(key)
|
|
} catch {
|
|
// ignore
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get stored JSON value from localStorage
|
|
*/
|
|
const getStoredJson = (key, defaultValue = []) => {
|
|
try {
|
|
const stored = localStorage.getItem(key)
|
|
return stored ? JSON.parse(stored) : defaultValue
|
|
} catch {
|
|
return defaultValue
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set stored JSON value to localStorage
|
|
*/
|
|
const setStoredJson = (key, value) => {
|
|
try {
|
|
localStorage.setItem(key, JSON.stringify(value))
|
|
} catch {
|
|
// ignore
|
|
}
|
|
}
|
|
|
|
const getValidStoredModel = (key, defaultValue, builtInModels) => {
|
|
const stored = getStored(key, defaultValue)
|
|
return builtInModels.some(model => model.key === stored) ? stored : defaultValue
|
|
}
|
|
|
|
/**
|
|
* 检查模型是否支持指定渠道
|
|
*/
|
|
const isModelSupported = (model, provider) => {
|
|
if (!model.provider) {
|
|
return true
|
|
}
|
|
return model.provider.includes(provider)
|
|
}
|
|
|
|
const normalizeRuntimeSizeOptions = (items = []) => {
|
|
if (!Array.isArray(items)) return []
|
|
return items
|
|
.map(item => {
|
|
const key = item?.value || item?.key || item?.id
|
|
if (!key) return null
|
|
return {
|
|
label: item.label || key,
|
|
key
|
|
}
|
|
})
|
|
.filter(Boolean)
|
|
}
|
|
|
|
const normalizeRuntimeImageModel = (item) => {
|
|
const key = item?.id || item?.model
|
|
if (!key) return null
|
|
const sizeOptions = normalizeRuntimeSizeOptions(item.size_options)
|
|
return {
|
|
label: item.label || item.model || key,
|
|
key,
|
|
provider: ['chatfire'],
|
|
sizes: sizeOptions.map(option => option.key),
|
|
sizeOptions,
|
|
qualities: [{ label: '标准', key: 'standard' }],
|
|
defaultParams: {
|
|
size: item.default_size || sizeOptions[0]?.key || 'auto',
|
|
quality: 'standard',
|
|
style: item.provider === 'ark_seedream' ? 'commercial' : 'vivid'
|
|
},
|
|
available: item.available !== false,
|
|
providerName: item.provider,
|
|
isRuntime: true
|
|
}
|
|
}
|
|
|
|
const normalizeRuntimeDurationOptions = (items = []) => {
|
|
if (!Array.isArray(items)) return []
|
|
return items
|
|
.map(item => {
|
|
const key = typeof item === 'object' ? item?.value || item?.key || item?.id : item
|
|
if (!key) return null
|
|
return {
|
|
label: typeof item === 'object' ? item.label || `${key} 秒` : `${key} 秒`,
|
|
key
|
|
}
|
|
})
|
|
.filter(Boolean)
|
|
}
|
|
|
|
const normalizeRuntimeResolutionOptions = (items = []) => {
|
|
if (!Array.isArray(items)) return []
|
|
return items
|
|
.map(item => {
|
|
const key = typeof item === 'object' ? item?.value || item?.key || item?.id : item
|
|
if (!key) return null
|
|
return {
|
|
label: typeof item === 'object' ? item.label || key : key,
|
|
key
|
|
}
|
|
})
|
|
.filter(Boolean)
|
|
}
|
|
|
|
const normalizeRuntimeVideoModel = (item) => {
|
|
const key = item?.id || item?.model
|
|
if (!key) return null
|
|
const sizeOptions = normalizeRuntimeSizeOptions(item.size_options)
|
|
const durationOptions = normalizeRuntimeDurationOptions(item.duration_options)
|
|
const resolutionOptions = normalizeRuntimeResolutionOptions(item.resolution_options)
|
|
const resolutions = resolutionOptions.length ? resolutionOptions.map(option => option.key) : ['720p']
|
|
const defaultResolution = item.default_resolution || resolutions[0] || '720p'
|
|
return {
|
|
label: item.label || item.model || key,
|
|
key,
|
|
provider: ['chatfire'],
|
|
type: 't2v+i2v',
|
|
model: item.model,
|
|
ratios: sizeOptions.map(option => option.key),
|
|
durs: durationOptions,
|
|
resolutions,
|
|
resolutionOptions,
|
|
defaultResolution,
|
|
defaultParams: {
|
|
ratio: sizeOptions[0]?.key || '720x1280',
|
|
duration: durationOptions[0]?.key || 5,
|
|
resolution: defaultResolution
|
|
},
|
|
available: item.available !== false,
|
|
isRuntime: true
|
|
}
|
|
}
|
|
|
|
const mergeModels = (builtInModels, runtimeModels) => {
|
|
const byKey = new Map()
|
|
builtInModels.forEach(model => byKey.set(model.key, { ...model, isCustom: false }))
|
|
runtimeModels.forEach(model => byKey.set(model.key, { ...byKey.get(model.key), ...model, isCustom: false }))
|
|
return Array.from(byKey.values())
|
|
}
|
|
|
|
export const useModelStore = defineStore('model', () => {
|
|
// ============ Provider 状态 | Provider State ============
|
|
|
|
// 当前选中的渠道
|
|
const storedProvider = getStored(STORAGE_KEYS.PROVIDER)
|
|
const currentProvider = ref(PROVIDERS[storedProvider] ? storedProvider : getDefaultProvider())
|
|
|
|
// 渠道列表
|
|
const providerList = computed(() => getProviderList())
|
|
|
|
// 当前渠道配置
|
|
const providerConfig = computed(() => getProviderConfig(currentProvider.value))
|
|
|
|
// 当前渠道标签
|
|
const providerLabel = computed(() => providerConfig.value.label || currentProvider.value)
|
|
|
|
// 设置当前渠道
|
|
const setProvider = (provider) => {
|
|
if (PROVIDERS[provider]) {
|
|
currentProvider.value = provider
|
|
setStored(STORAGE_KEYS.PROVIDER, provider)
|
|
}
|
|
}
|
|
|
|
// 清除渠道配置
|
|
const clearProvider = () => {
|
|
currentProvider.value = getDefaultProvider()
|
|
removeStored(STORAGE_KEYS.PROVIDER)
|
|
}
|
|
|
|
// 适配请求参数
|
|
const adaptRequest = (type, params) => {
|
|
const config = providerConfig.value
|
|
if (config.requestAdapter && config.requestAdapter[type]) {
|
|
return config.requestAdapter[type](params)
|
|
}
|
|
return params
|
|
}
|
|
|
|
// 适配响应数据
|
|
const adaptResponse = (type, response) => {
|
|
const config = providerConfig.value
|
|
if (config.responseAdapter && config.responseAdapter[type]) {
|
|
return config.responseAdapter[type](response)
|
|
}
|
|
return response
|
|
}
|
|
|
|
// ============ Custom Models 状态 | Custom Models State ============
|
|
|
|
// 全局自定义模型(不区分渠道)
|
|
const customChatModels = ref(getStoredJson(STORAGE_KEYS.CUSTOM_CHAT_MODELS, []))
|
|
const customImageModels = ref(getStoredJson(STORAGE_KEYS.CUSTOM_IMAGE_MODELS, []))
|
|
const customVideoModels = ref(getStoredJson(STORAGE_KEYS.CUSTOM_VIDEO_MODELS, []))
|
|
|
|
// 按渠道存储的自定义模型 | 结构: { 'skg': [{key, label}] }
|
|
const customChatModelsByProvider = ref(getStoredJson(STORAGE_KEYS.CUSTOM_CHAT_MODELS_BY_PROVIDER, {}))
|
|
const customImageModelsByProvider = ref(getStoredJson(STORAGE_KEYS.CUSTOM_IMAGE_MODELS_BY_PROVIDER, {}))
|
|
const customVideoModelsByProvider = ref(getStoredJson(STORAGE_KEYS.CUSTOM_VIDEO_MODELS_BY_PROVIDER, {}))
|
|
const runtimeImageModels = ref([])
|
|
const runtimeVideoModels = ref([])
|
|
const runtimeVideoModelsLoaded = ref(false)
|
|
|
|
// 选中的模型
|
|
const selectedChatModel = ref(getStored(STORAGE_KEYS.SELECTED_CHAT_MODEL, DEFAULT_CHAT_MODEL))
|
|
const selectedImageModel = ref(getValidStoredModel(STORAGE_KEYS.SELECTED_IMAGE_MODEL, DEFAULT_IMAGE_MODEL, IMAGE_MODELS))
|
|
const selectedVideoModel = ref(getValidStoredModel(STORAGE_KEYS.SELECTED_VIDEO_MODEL, DEFAULT_VIDEO_MODEL, VIDEO_MODELS))
|
|
|
|
// 按渠道存储的 API 配置
|
|
const apiKeysByProvider = ref(getStoredJson(STORAGE_KEYS.API_KEYS_BY_PROVIDER, {}))
|
|
const baseUrlsByProvider = ref(getStoredJson(STORAGE_KEYS.BASE_URLS_BY_PROVIDER, {}))
|
|
|
|
// 内部模式由服务端会话鉴权,不在浏览器暴露上游模型密钥。
|
|
const currentApiKey = computed(() => 'internal-session')
|
|
const currentBaseUrl = computed(() => baseUrlsByProvider.value[currentProvider.value] || getDefaultBaseUrl(currentProvider.value))
|
|
|
|
// 设置指定渠道凭据(兼容旧本地状态)
|
|
const setApiKeyByProvider = (provider, apiKey) => {
|
|
apiKeysByProvider.value[provider] = apiKey
|
|
}
|
|
|
|
// 设置指定渠道的 Base URL
|
|
const setBaseUrlByProvider = (provider, baseUrl) => {
|
|
baseUrlsByProvider.value[provider] = baseUrl
|
|
}
|
|
|
|
// 清除指定渠道的 API 配置
|
|
const clearApiConfigByProvider = (provider) => {
|
|
delete apiKeysByProvider.value[provider]
|
|
delete baseUrlsByProvider.value[provider]
|
|
}
|
|
|
|
// ============ Computed: All Models (built-in + custom + by provider) ============
|
|
|
|
const allChatModels = computed(() => [
|
|
...CHAT_MODELS.map(m => ({ ...m, isCustom: false })),
|
|
...customChatModels.value.map(m => ({
|
|
label: m.label || m.key,
|
|
key: m.key,
|
|
isCustom: true
|
|
})),
|
|
// 添加当前渠道的自定义模型
|
|
...(customChatModelsByProvider.value[currentProvider.value] || []).map(m => ({
|
|
label: m.label || m.key,
|
|
key: m.key,
|
|
isCustom: true,
|
|
provider: [currentProvider.value]
|
|
}))
|
|
])
|
|
|
|
const allImageModels = computed(() =>
|
|
mergeModels(IMAGE_MODELS, runtimeImageModels.value)
|
|
)
|
|
|
|
const allVideoModels = computed(() =>
|
|
runtimeVideoModelsLoaded.value
|
|
? runtimeVideoModels.value
|
|
: mergeModels(VIDEO_MODELS, runtimeVideoModels.value)
|
|
)
|
|
|
|
// ============ Computed: Available Models (filtered by provider) ============
|
|
|
|
// 按渠道过滤的可用模型
|
|
const availableChatModels = computed(() =>
|
|
allChatModels.value.filter(m => isModelSupported(m, currentProvider.value))
|
|
)
|
|
|
|
const availableImageModels = computed(() =>
|
|
allImageModels.value.filter(m => isModelSupported(m, currentProvider.value) && m.available !== false)
|
|
)
|
|
|
|
const availableVideoModels = computed(() =>
|
|
allVideoModels.value.filter(m => isModelSupported(m, currentProvider.value) && m.available !== false)
|
|
)
|
|
|
|
// ============ Computed: Model Options for UI (all models, not filtered by provider) ============
|
|
|
|
// 返回适合 n-dropdown 使用的选项格式(全部模型,不按渠道过滤)
|
|
const allImageModelOptions = computed(() =>
|
|
allImageModels.value.map(m => ({
|
|
label: m.label,
|
|
key: m.key,
|
|
disabled: false
|
|
}))
|
|
)
|
|
|
|
const allVideoModelOptions = computed(() =>
|
|
allVideoModels.value.map(m => ({
|
|
label: m.label,
|
|
key: m.key,
|
|
disabled: false
|
|
}))
|
|
)
|
|
|
|
const allChatModelOptions = computed(() =>
|
|
allChatModels.value.map(m => ({
|
|
label: m.label,
|
|
key: m.key
|
|
}))
|
|
)
|
|
|
|
// ============ Computed: Model Options for UI (filtered by provider - deprecated, use all* instead) ============
|
|
|
|
// 返回适合 n-dropdown 使用的选项格式
|
|
const imageModelOptions = computed(() =>
|
|
availableImageModels.value.map(m => ({
|
|
label: m.label,
|
|
key: m.key,
|
|
disabled: m.available === false
|
|
}))
|
|
)
|
|
|
|
const videoModelOptions = computed(() =>
|
|
availableVideoModels.value.map(m => ({
|
|
label: m.label,
|
|
key: m.key
|
|
}))
|
|
)
|
|
|
|
const chatModelOptions = computed(() =>
|
|
availableChatModels.value.map(m => ({
|
|
label: m.label,
|
|
key: m.key
|
|
}))
|
|
)
|
|
|
|
// ============ Methods: Add/Remove Custom Models ============
|
|
|
|
const addCustomChatModel = (modelKey, label = '') => {
|
|
if (!modelKey || customChatModels.value.some(m => m.key === modelKey)) return false
|
|
customChatModels.value.push({ key: modelKey, label: label || modelKey })
|
|
return true
|
|
}
|
|
|
|
const addCustomImageModel = (modelKey, label = '') => {
|
|
if (!modelKey || customImageModels.value.some(m => m.key === modelKey)) return false
|
|
customImageModels.value.push({ key: modelKey, label: label || modelKey })
|
|
return true
|
|
}
|
|
|
|
const addCustomVideoModel = (modelKey, label = '') => {
|
|
if (!modelKey || customVideoModels.value.some(m => m.key === modelKey)) return false
|
|
customVideoModels.value.push({ key: modelKey, label: label || modelKey })
|
|
return true
|
|
}
|
|
|
|
const removeCustomChatModel = (modelKey) => {
|
|
const idx = customChatModels.value.findIndex(m => m.key === modelKey)
|
|
if (idx > -1) {
|
|
customChatModels.value.splice(idx, 1)
|
|
if (selectedChatModel.value === modelKey) {
|
|
selectedChatModel.value = DEFAULT_CHAT_MODEL
|
|
}
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
const removeCustomImageModel = (modelKey) => {
|
|
const idx = customImageModels.value.findIndex(m => m.key === modelKey)
|
|
if (idx > -1) {
|
|
customImageModels.value.splice(idx, 1)
|
|
if (selectedImageModel.value === modelKey) {
|
|
selectedImageModel.value = DEFAULT_IMAGE_MODEL
|
|
}
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
const removeCustomVideoModel = (modelKey) => {
|
|
const idx = customVideoModels.value.findIndex(m => m.key === modelKey)
|
|
if (idx > -1) {
|
|
customVideoModels.value.splice(idx, 1)
|
|
if (selectedVideoModel.value === modelKey) {
|
|
selectedVideoModel.value = DEFAULT_VIDEO_MODEL
|
|
}
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ============ Methods: Get Model Config ============
|
|
|
|
const getChatModel = (key) => allChatModels.value.find(m => m.key === key)
|
|
const getImageModel = (key) => allImageModels.value.find(m => m.key === key)
|
|
const getVideoModel = (key) => allVideoModels.value.find(m => m.key === key)
|
|
|
|
const loadRuntimeModels = async () => {
|
|
try {
|
|
const response = await fetch('/api/health', { credentials: 'include' })
|
|
if (!response.ok) return false
|
|
const data = await response.json()
|
|
const imageOptions = data?.models?.image_options || []
|
|
runtimeImageModels.value = imageOptions
|
|
.filter(item => item?.id && item.id !== 'auto')
|
|
.map(normalizeRuntimeImageModel)
|
|
.filter(Boolean)
|
|
const videoOptions = data?.models?.video_options || []
|
|
runtimeVideoModels.value = videoOptions
|
|
.filter(item => item?.id && item.available !== false)
|
|
.map(normalizeRuntimeVideoModel)
|
|
.filter(Boolean)
|
|
runtimeVideoModelsLoaded.value = true
|
|
if (!availableVideoModels.value.some(model => model.key === selectedVideoModel.value)) {
|
|
selectedVideoModel.value = availableVideoModels.value[0]?.key || DEFAULT_VIDEO_MODEL
|
|
}
|
|
return true
|
|
} catch (err) {
|
|
console.warn('[model store] runtime model load failed', err)
|
|
return false
|
|
}
|
|
}
|
|
|
|
// ============ Methods: Get API Endpoints ============
|
|
|
|
// 获取图片端点
|
|
const getImageEndpoint = () => {
|
|
const endpoint = providerConfig.value.endpoints?.image || '/images/generations'
|
|
return `${currentBaseUrl.value}${endpoint}`
|
|
}
|
|
|
|
// 获取视频生成端点
|
|
const getVideoEndpoint = () => {
|
|
const endpoint = providerConfig.value.endpoints?.video || '/videos'
|
|
return `${currentBaseUrl.value}${endpoint}`
|
|
}
|
|
|
|
// 获取视频任务查询端点
|
|
const getVideoTaskEndpoint = () => {
|
|
const config = providerConfig.value
|
|
// 优先使用 videoQuery 端点,支持 {taskId} 占位符替换
|
|
let endpoint = config.endpoints?.videoQuery || config.endpoints?.video || '/videos'
|
|
return `${currentBaseUrl.value}${endpoint}`
|
|
}
|
|
|
|
// 获取聊天端点(支持参考图片)
|
|
const getChatEndpoint = () => {
|
|
const endpoint = providerConfig.value?.endpoints?.chat || '/chat/completions'
|
|
return `${currentBaseUrl.value}${endpoint}`
|
|
}
|
|
|
|
// ============ Methods: Get Models By Provider ============
|
|
|
|
const getModelsByProvider = (provider) => {
|
|
const chat = [
|
|
...CHAT_MODELS.filter(m => isModelSupported(m, provider)).map(m => ({ ...m, isCustom: false })),
|
|
...(customChatModelsByProvider.value[provider] || []).map(m => ({
|
|
label: m.label || m.key,
|
|
key: m.key,
|
|
isCustom: true,
|
|
provider: [provider]
|
|
}))
|
|
]
|
|
const image = allImageModels.value
|
|
.filter(m => isModelSupported(m, provider))
|
|
.map(m => ({ ...m, isCustom: false }))
|
|
const video = allVideoModels.value
|
|
.filter(m => isModelSupported(m, provider))
|
|
.map(m => ({ ...m, isCustom: false }))
|
|
return { chat, image, video }
|
|
}
|
|
|
|
// ============ Methods: Add/Remove Custom Models By Provider ============
|
|
|
|
const addCustomChatModelByProvider = (modelKey, provider, label = '') => {
|
|
if (!modelKey) return false
|
|
if (!customChatModelsByProvider.value[provider]) {
|
|
customChatModelsByProvider.value[provider] = []
|
|
}
|
|
if (customChatModelsByProvider.value[provider].some(m => m.key === modelKey)) return false
|
|
customChatModelsByProvider.value[provider].push({ key: modelKey, label: label || modelKey })
|
|
return true
|
|
}
|
|
|
|
const addCustomImageModelByProvider = (modelKey, provider, label = '') => {
|
|
if (!modelKey) return false
|
|
if (!customImageModelsByProvider.value[provider]) {
|
|
customImageModelsByProvider.value[provider] = []
|
|
}
|
|
if (customImageModelsByProvider.value[provider].some(m => m.key === modelKey)) return false
|
|
customImageModelsByProvider.value[provider].push({ key: modelKey, label: label || modelKey })
|
|
return true
|
|
}
|
|
|
|
const addCustomVideoModelByProvider = (modelKey, provider, label = '') => {
|
|
if (!modelKey) return false
|
|
if (!customVideoModelsByProvider.value[provider]) {
|
|
customVideoModelsByProvider.value[provider] = []
|
|
}
|
|
if (customVideoModelsByProvider.value[provider].some(m => m.key === modelKey)) return false
|
|
customVideoModelsByProvider.value[provider].push({ key: modelKey, label: label || modelKey })
|
|
return true
|
|
}
|
|
|
|
const removeCustomChatModelByProvider = (modelKey, provider) => {
|
|
if (!customChatModelsByProvider.value[provider]) return false
|
|
const idx = customChatModelsByProvider.value[provider].findIndex(m => m.key === modelKey)
|
|
if (idx > -1) {
|
|
customChatModelsByProvider.value[provider].splice(idx, 1)
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
const removeCustomImageModelByProvider = (modelKey, provider) => {
|
|
if (!customImageModelsByProvider.value[provider]) return false
|
|
const idx = customImageModelsByProvider.value[provider].findIndex(m => m.key === modelKey)
|
|
if (idx > -1) {
|
|
customImageModelsByProvider.value[provider].splice(idx, 1)
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
const removeCustomVideoModelByProvider = (modelKey, provider) => {
|
|
if (!customVideoModelsByProvider.value[provider]) return false
|
|
const idx = customVideoModelsByProvider.value[provider].findIndex(m => m.key === modelKey)
|
|
if (idx > -1) {
|
|
customVideoModelsByProvider.value[provider].splice(idx, 1)
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// 清除所有自定义模型
|
|
const clearCustomModels = () => {
|
|
customChatModels.value = []
|
|
customImageModels.value = []
|
|
customVideoModels.value = []
|
|
selectedChatModel.value = DEFAULT_CHAT_MODEL
|
|
selectedImageModel.value = DEFAULT_IMAGE_MODEL
|
|
selectedVideoModel.value = DEFAULT_VIDEO_MODEL
|
|
}
|
|
|
|
// ============ Watch & Persist ============
|
|
|
|
// 监听并持久化自定义模型
|
|
watch(customChatModels, (val) => setStoredJson(STORAGE_KEYS.CUSTOM_CHAT_MODELS, val), { deep: true })
|
|
watch(customImageModels, (val) => setStoredJson(STORAGE_KEYS.CUSTOM_IMAGE_MODELS, val), { deep: true })
|
|
watch(customVideoModels, (val) => setStoredJson(STORAGE_KEYS.CUSTOM_VIDEO_MODELS, val), { deep: true })
|
|
|
|
// 监听并持久化按渠道的自定义模型
|
|
watch(customChatModelsByProvider, (val) => setStoredJson(STORAGE_KEYS.CUSTOM_CHAT_MODELS_BY_PROVIDER, val), { deep: true })
|
|
watch(customImageModelsByProvider, (val) => setStoredJson(STORAGE_KEYS.CUSTOM_IMAGE_MODELS_BY_PROVIDER, val), { deep: true })
|
|
watch(customVideoModelsByProvider, (val) => setStoredJson(STORAGE_KEYS.CUSTOM_VIDEO_MODELS_BY_PROVIDER, val), { deep: true })
|
|
|
|
// 监听并持久化选中的模型
|
|
watch(selectedChatModel, (val) => setStored(STORAGE_KEYS.SELECTED_CHAT_MODEL, val))
|
|
watch(selectedImageModel, (val) => setStored(STORAGE_KEYS.SELECTED_IMAGE_MODEL, val))
|
|
watch(selectedVideoModel, (val) => setStored(STORAGE_KEYS.SELECTED_VIDEO_MODEL, val))
|
|
|
|
// 监听并持久化 API 配置
|
|
watch(apiKeysByProvider, (val) => setStoredJson(STORAGE_KEYS.API_KEYS_BY_PROVIDER, val), { deep: true })
|
|
watch(baseUrlsByProvider, (val) => setStoredJson(STORAGE_KEYS.BASE_URLS_BY_PROVIDER, val), { deep: true })
|
|
|
|
return {
|
|
// Provider
|
|
currentProvider,
|
|
providerList,
|
|
providerConfig,
|
|
providerLabel,
|
|
setProvider,
|
|
clearProvider,
|
|
adaptRequest,
|
|
adaptResponse,
|
|
|
|
// All models (built-in + custom)
|
|
allChatModels,
|
|
allImageModels,
|
|
allVideoModels,
|
|
runtimeImageModels,
|
|
runtimeVideoModels,
|
|
runtimeVideoModelsLoaded,
|
|
|
|
// Available models filtered by provider
|
|
availableChatModels,
|
|
availableImageModels,
|
|
availableVideoModels,
|
|
|
|
// Model options for UI (dropdown format)
|
|
imageModelOptions,
|
|
videoModelOptions,
|
|
chatModelOptions,
|
|
|
|
// All model options (not filtered by provider)
|
|
allImageModelOptions,
|
|
allVideoModelOptions,
|
|
allChatModelOptions,
|
|
|
|
// Selected models
|
|
selectedChatModel,
|
|
selectedImageModel,
|
|
selectedVideoModel,
|
|
|
|
// Custom models
|
|
customChatModels,
|
|
customImageModels,
|
|
customVideoModels,
|
|
|
|
// Custom models by provider
|
|
customChatModelsByProvider,
|
|
customImageModelsByProvider,
|
|
customVideoModelsByProvider,
|
|
|
|
// Add/Remove methods
|
|
addCustomChatModel,
|
|
addCustomImageModel,
|
|
addCustomVideoModel,
|
|
removeCustomChatModel,
|
|
removeCustomImageModel,
|
|
removeCustomVideoModel,
|
|
|
|
// Add/Remove by provider methods
|
|
addCustomChatModelByProvider,
|
|
addCustomImageModelByProvider,
|
|
addCustomVideoModelByProvider,
|
|
removeCustomChatModelByProvider,
|
|
removeCustomImageModelByProvider,
|
|
removeCustomVideoModelByProvider,
|
|
|
|
// Get model
|
|
getChatModel,
|
|
getImageModel,
|
|
getVideoModel,
|
|
loadRuntimeModels,
|
|
|
|
// Get API endpoints
|
|
getImageEndpoint,
|
|
getVideoEndpoint,
|
|
getVideoTaskEndpoint,
|
|
getChatEndpoint,
|
|
|
|
// Get models by provider
|
|
getModelsByProvider,
|
|
|
|
// Clear all custom models
|
|
clearCustomModels,
|
|
|
|
// API Config by provider
|
|
currentApiKey,
|
|
currentBaseUrl,
|
|
apiKeysByProvider,
|
|
baseUrlsByProvider,
|
|
setApiKeyByProvider,
|
|
setBaseUrlByProvider,
|
|
clearApiConfigByProvider
|
|
}
|
|
})
|