from __future__ import annotations from dataclasses import dataclass from typing import Literal, TypedDict ModelPurpose = Literal["text", "image", "vision"] class ProviderTypeDef(TypedDict): id: str label: str default_url: str PROVIDER_TYPES: list[ProviderTypeDef] = [ { "id": "chatgpt", "label": "ChatGPT", "default_url": "https://api.openai.com/v1", }, { "id": "doubao", "label": "豆包", "default_url": "https://ark.cn-beijing.volces.com/api/v3", }, { "id": "deepseek", "label": "DeepSeek", "default_url": "https://api.deepseek.com", }, { "id": "zhipu", "label": "智谱", "default_url": "https://open.bigmodel.cn/api/paas/v4", }, ] _PROVIDER_MAP = {p["id"]: p for p in PROVIDER_TYPES} # 默认平台卡片(稳定 id,首次启动写入) DEFAULT_PROVIDER_IDS: list[tuple[str, str]] = [ ("ai_chatgpt", "chatgpt"), ("ai_doubao", "doubao"), ("ai_deepseek", "deepseek"), ("ai_zhipu", "zhipu"), ] # 使用 zai-sdk 官方客户端(非 OpenAI 兼容 HTTP) ZHIPU_SDK_TYPES = frozenset({"zhipu"}) ZHIPU_CHAT_MODEL = "glm-5.1" ZHIPU_VISION_MODEL = "glm-5v-turbo" ZHIPU_IMAGE_MODEL = "glm-image" ZHIPU_IMAGE_SIZE = "1280x1280" MODEL_ROLE_TEXT = "文字" MODEL_ROLE_IMAGE = "图片" MODEL_ROLE_VISION = "图文" IMAGE_MODEL_DEFAULTS: dict[str, str] = { "chatgpt": "dall-e-3", } @dataclass(frozen=True) class AiModelOption: model_id: str role: str purpose: ModelPurpose def is_vision_model(model_id: str) -> bool: return (model_id or "").strip() == ZHIPU_VISION_MODEL def selectable_models(provider_type: str, purpose: ModelPurpose) -> list[AiModelOption]: """爆款创作 / AI 分析等场景下可勾选的模型列表。""" if purpose == "text": model_id = default_model(provider_type) return [AiModelOption(model_id, MODEL_ROLE_TEXT, "text")] if purpose == "image": if provider_type in ZHIPU_SDK_TYPES: return [AiModelOption(ZHIPU_IMAGE_MODEL, MODEL_ROLE_IMAGE, "image")] image_model = IMAGE_MODEL_DEFAULTS.get(provider_type, "dall-e-3") return [AiModelOption(image_model, MODEL_ROLE_IMAGE, "image")] # vision — AI 分析(图文理解) if provider_type in ZHIPU_SDK_TYPES: return [ AiModelOption(ZHIPU_VISION_MODEL, MODEL_ROLE_VISION, "vision"), AiModelOption(ZHIPU_CHAT_MODEL, MODEL_ROLE_TEXT, "text"), ] model_id = default_model(provider_type) return [AiModelOption(model_id, MODEL_ROLE_TEXT, "text")] def provider_model_tags(type_id: str) -> list[tuple[str, str]]: """(model_id, role) for provider card chips.""" seen: set[str] = set() tags: list[tuple[str, str]] = [] for purpose in ("text", "image", "vision"): for opt in selectable_models(type_id, purpose): # type: ignore[arg-type] if opt.model_id in seen: continue seen.add(opt.model_id) tags.append((opt.model_id, opt.role)) return tags def list_provider_types() -> list[tuple[str, str]]: """(type_id, label)""" return [(p["id"], p["label"]) for p in PROVIDER_TYPES] def provider_label(type_id: str) -> str: p = _PROVIDER_MAP.get(type_id) return p["label"] if p else type_id def default_base_url(type_id: str) -> str: p = _PROVIDER_MAP.get(type_id) return p["default_url"] if p else "" def is_valid_provider_type(type_id: str) -> bool: return type_id in _PROVIDER_MAP def uses_zhipu_sdk(type_id: str) -> bool: return type_id in ZHIPU_SDK_TYPES def default_model(type_id: str) -> str: defaults = { "chatgpt": "gpt-4o-mini", "deepseek": "deepseek-chat", "doubao": "doubao-1-5-pro-32k-250115", "zhipu": ZHIPU_CHAT_MODEL, } return defaults.get(type_id, "gpt-4o-mini")