128 lines
3.7 KiB
TypeScript
128 lines
3.7 KiB
TypeScript
import { defineStore } from 'pinia';
|
||
|
||
export type ModelKey = string;
|
||
|
||
export interface ModelOption {
|
||
key: ModelKey;
|
||
label: string;
|
||
description: string;
|
||
visible?: boolean;
|
||
multimodal?: string;
|
||
supportsImage?: boolean;
|
||
supportsVideo?: boolean;
|
||
contextWindow?: number | null;
|
||
maxOutputTokens?: number | null;
|
||
fastOnly: boolean;
|
||
supportsThinking: boolean;
|
||
deepOnly?: boolean;
|
||
}
|
||
|
||
interface ModelState {
|
||
currentModelKey: ModelKey;
|
||
models: ModelOption[];
|
||
}
|
||
|
||
export const useModelStore = defineStore('model', {
|
||
state: (): ModelState => ({
|
||
currentModelKey: 'kimi-k2.5',
|
||
models: [
|
||
{
|
||
key: 'kimi-k2.5',
|
||
label: 'Kimi-k2.5',
|
||
description: '新版 Kimi,支持图文 & 思考开关',
|
||
multimodal: 'image,video',
|
||
supportsImage: true,
|
||
supportsVideo: true,
|
||
fastOnly: false,
|
||
supportsThinking: true
|
||
},
|
||
{
|
||
key: 'kimi',
|
||
label: 'Kimi-k2',
|
||
description: '综合能力较强',
|
||
fastOnly: false,
|
||
supportsThinking: true
|
||
},
|
||
{
|
||
key: 'deepseek',
|
||
label: 'Deepseek-V3.2',
|
||
description: '数学能力较强',
|
||
fastOnly: false,
|
||
supportsThinking: true
|
||
},
|
||
{
|
||
key: 'qwen3-vl-plus',
|
||
label: 'Qwen3.5',
|
||
description: '图文视频多模态 + 深度思考',
|
||
multimodal: 'image,video',
|
||
supportsImage: true,
|
||
supportsVideo: true,
|
||
fastOnly: false,
|
||
supportsThinking: true
|
||
},
|
||
{
|
||
key: 'minimax-m2.5',
|
||
label: 'MiniMax-M2.5',
|
||
description: '仅深度思考,超长上下文',
|
||
fastOnly: false,
|
||
supportsThinking: true,
|
||
deepOnly: true
|
||
}
|
||
]
|
||
}),
|
||
getters: {
|
||
currentModel(state): ModelOption {
|
||
return state.models.find((m) => m.key === state.currentModelKey) || state.models[0];
|
||
}
|
||
},
|
||
actions: {
|
||
setModels(models: ModelOption[]) {
|
||
if (!Array.isArray(models) || !models.length) return;
|
||
this.models = models;
|
||
const exists = this.models.some((m) => m.key === this.currentModelKey);
|
||
if (!exists && this.models[0]) {
|
||
this.currentModelKey = this.models[0].key;
|
||
}
|
||
},
|
||
setModel(key: ModelKey) {
|
||
if (this.currentModelKey === key) return;
|
||
const exists = this.models.some((m) => m.key === key);
|
||
if (exists) {
|
||
this.currentModelKey = key;
|
||
}
|
||
},
|
||
async fetchModels() {
|
||
const resp = await fetch('/api/v1/models', { cache: 'no-store' });
|
||
const data = await resp.json();
|
||
if (!resp.ok || !data?.success || !Array.isArray(data.items)) {
|
||
throw new Error(data?.error || '加载模型列表失败');
|
||
}
|
||
const mapped: ModelOption[] = data.items
|
||
.filter((item: any) => item && item.model_key)
|
||
.map((item: any) => {
|
||
const multimodal = String(item.multimodal || 'none');
|
||
const supportsImage = multimodal === 'image' || multimodal === 'image,video';
|
||
const supportsVideo = multimodal === 'video' || multimodal === 'image,video';
|
||
return {
|
||
key: String(item.model_key),
|
||
label: String(item.name || item.model_key),
|
||
description: String(item.description || ''),
|
||
visible: item.visible !== false,
|
||
multimodal,
|
||
supportsImage,
|
||
supportsVideo,
|
||
contextWindow: typeof item.context_window === 'number' ? item.context_window : null,
|
||
maxOutputTokens:
|
||
typeof item.max_output_tokens === 'number' ? item.max_output_tokens : null,
|
||
fastOnly: !!item.fast_only,
|
||
supportsThinking: !!item.supports_thinking,
|
||
deepOnly: !!item.deep_only
|
||
};
|
||
});
|
||
if (mapped.length) {
|
||
this.setModels(mapped);
|
||
}
|
||
}
|
||
}
|
||
});
|