388 lines
13 KiB
JavaScript
388 lines
13 KiB
JavaScript
/**
|
|
* Model Config Hook | 模型配置 Hook
|
|
* Manages model configuration with local storage persistence
|
|
*/
|
|
|
|
import { ref, computed, watch } from 'vue'
|
|
import { STORAGE_KEYS } from '@/utils'
|
|
import { useProvider } from './useProvider'
|
|
import {
|
|
CHAT_MODELS,
|
|
IMAGE_MODELS,
|
|
VIDEO_MODELS,
|
|
DEFAULT_CHAT_MODEL,
|
|
DEFAULT_IMAGE_MODEL,
|
|
DEFAULT_VIDEO_MODEL
|
|
} from '@/config/models'
|
|
|
|
/**
|
|
* 检查模型是否支持指定渠道
|
|
* @param {Object} model - 模型配置
|
|
* @param {string} provider - 渠道名称
|
|
* @returns {boolean} 是否支持
|
|
*/
|
|
const isModelSupported = (model, provider) => {
|
|
// 如果没有 provider 字段,默认支持所有渠道
|
|
if (!model.provider) {
|
|
return true
|
|
}
|
|
// 如果有 provider 字段,检查是否包含指定渠道
|
|
return model.provider.includes(provider)
|
|
}
|
|
|
|
/**
|
|
* 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 storage errors
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get stored string value from localStorage
|
|
*/
|
|
const getStored = (key, defaultValue = '') => {
|
|
try {
|
|
return localStorage.getItem(key) || defaultValue
|
|
} catch {
|
|
return defaultValue
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set stored string value to localStorage
|
|
*/
|
|
const setStored = (key, value) => {
|
|
try {
|
|
if (value) {
|
|
localStorage.setItem(key, value)
|
|
} else {
|
|
localStorage.removeItem(key)
|
|
}
|
|
} catch {
|
|
// Ignore storage errors
|
|
}
|
|
}
|
|
|
|
const getValidStoredModel = (key, defaultValue, builtInModels) => {
|
|
const stored = getStored(key, defaultValue)
|
|
return builtInModels.some(model => model.key === stored) ? stored : defaultValue
|
|
}
|
|
|
|
// Shared reactive state (singleton pattern)
|
|
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, []))
|
|
|
|
// 按渠道存储的自定义模型 | 结构: { 'openai': [{key, label}], 'chatfire': [{key, label}] }
|
|
const customChatModelsByProvider = ref(getStoredJson(STORAGE_KEYS.CUSTOM_CHAT_MODELS_BY_PROVIDER || 'custom-chat-models-by-provider', {}))
|
|
const customImageModelsByProvider = ref(getStoredJson(STORAGE_KEYS.CUSTOM_IMAGE_MODELS_BY_PROVIDER || 'custom-image-models-by-provider', {}))
|
|
const customVideoModelsByProvider = ref(getStoredJson(STORAGE_KEYS.CUSTOM_VIDEO_MODELS_BY_PROVIDER || 'custom-video-models-by-provider', {}))
|
|
|
|
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))
|
|
|
|
/**
|
|
* Model Configuration Hook
|
|
*/
|
|
export const useModelConfig = () => {
|
|
// Get current provider | 获取当前渠道
|
|
const { currentProvider } = useProvider()
|
|
|
|
// Combined models (built-in + custom, including provider-specific custom models)
|
|
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(() =>
|
|
IMAGE_MODELS.map(m => ({ ...m, isCustom: false }))
|
|
)
|
|
|
|
const allVideoModels = computed(() =>
|
|
VIDEO_MODELS.map(m => ({ ...m, isCustom: false }))
|
|
)
|
|
|
|
// 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))
|
|
)
|
|
|
|
const availableVideoModels = computed(() =>
|
|
allVideoModels.value.filter(m => isModelSupported(m, currentProvider.value))
|
|
)
|
|
|
|
// All models (including models from all providers, not filtered) | 所有模型(不按渠道过滤)
|
|
const allAvailableChatModels = computed(() => allChatModels.value)
|
|
const allAvailableImageModels = computed(() => allImageModels.value)
|
|
const allAvailableVideoModels = computed(() => allVideoModels.value)
|
|
|
|
// 获取指定渠道的模型(包括内置 + 该渠道自定义)
|
|
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 = IMAGE_MODELS
|
|
.filter(m => isModelSupported(m, provider))
|
|
.map(m => ({ ...m, isCustom: false }))
|
|
const video = VIDEO_MODELS
|
|
.filter(m => isModelSupported(m, provider))
|
|
.map(m => ({ ...m, isCustom: false }))
|
|
return { chat, image, video }
|
|
}
|
|
|
|
// Watch and persist changes
|
|
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 and persist by provider changes
|
|
watch(customChatModelsByProvider, (val) => {
|
|
const key = STORAGE_KEYS.CUSTOM_CHAT_MODELS_BY_PROVIDER || 'custom-chat-models-by-provider'
|
|
setStoredJson(key, val)
|
|
}, { deep: true })
|
|
watch(customImageModelsByProvider, (val) => {
|
|
const key = STORAGE_KEYS.CUSTOM_IMAGE_MODELS_BY_PROVIDER || 'custom-image-models-by-provider'
|
|
setStoredJson(key, val)
|
|
}, { deep: true })
|
|
watch(customVideoModelsByProvider, (val) => {
|
|
const key = STORAGE_KEYS.CUSTOM_VIDEO_MODELS_BY_PROVIDER || 'custom-video-models-by-provider'
|
|
setStoredJson(key, 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))
|
|
|
|
// Add custom model
|
|
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
|
|
}
|
|
|
|
// Remove custom model
|
|
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
|
|
}
|
|
|
|
// 按渠道添加自定义模型
|
|
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
|
|
}
|
|
|
|
// Get model by key
|
|
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)
|
|
|
|
// Clear all custom models
|
|
const clearCustomModels = () => {
|
|
customChatModels.value = []
|
|
customImageModels.value = []
|
|
customVideoModels.value = []
|
|
selectedChatModel.value = DEFAULT_CHAT_MODEL
|
|
selectedImageModel.value = DEFAULT_IMAGE_MODEL
|
|
selectedVideoModel.value = DEFAULT_VIDEO_MODEL
|
|
}
|
|
|
|
return {
|
|
// All models (built-in + custom)
|
|
allChatModels,
|
|
allImageModels,
|
|
allVideoModels,
|
|
|
|
// Available models filtered by provider | 根据渠道过滤的可用模型
|
|
availableChatModels,
|
|
availableImageModels,
|
|
availableVideoModels,
|
|
|
|
// All models (including models from all providers, not filtered) | 所有模型(不按渠道过滤)
|
|
allAvailableChatModels,
|
|
allAvailableImageModels,
|
|
allAvailableVideoModels,
|
|
|
|
// Custom models only
|
|
customChatModels,
|
|
customImageModels,
|
|
customVideoModels,
|
|
|
|
// Selected models
|
|
selectedChatModel,
|
|
selectedImageModel,
|
|
selectedVideoModel,
|
|
|
|
// Add methods
|
|
addCustomChatModel,
|
|
addCustomImageModel,
|
|
addCustomVideoModel,
|
|
|
|
// Remove methods
|
|
removeCustomChatModel,
|
|
removeCustomImageModel,
|
|
removeCustomVideoModel,
|
|
|
|
// Get model
|
|
getChatModel,
|
|
getImageModel,
|
|
getVideoModel,
|
|
|
|
// Get models by provider (for ApiSettings)
|
|
getModelsByProvider,
|
|
|
|
// Custom models by provider
|
|
customChatModelsByProvider,
|
|
customImageModelsByProvider,
|
|
customVideoModelsByProvider,
|
|
|
|
// Add/Remove by provider methods
|
|
addCustomChatModelByProvider,
|
|
addCustomImageModelByProvider,
|
|
addCustomVideoModelByProvider,
|
|
removeCustomChatModelByProvider,
|
|
removeCustomImageModelByProvider,
|
|
removeCustomVideoModelByProvider,
|
|
|
|
// Clear
|
|
clearCustomModels
|
|
}
|
|
}
|