2026-05-12 00:46:12 +08:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
2026-05-13 16:12:02 +08:00
|
|
|
import hashlib
|
2026-05-12 00:46:12 +08:00
|
|
|
from dataclasses import dataclass
|
2026-05-13 16:12:02 +08:00
|
|
|
from enum import StrEnum
|
|
|
|
|
|
|
|
|
|
MODEL_PICKER_PAGE_SIZE = 8
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PickerState(StrEnum):
|
|
|
|
|
PROVIDERS = "providers"
|
|
|
|
|
LIST = "list"
|
|
|
|
|
SELECT = "select"
|
|
|
|
|
BACK = "back"
|
2026-05-12 00:46:12 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
|
class ModelOption:
|
|
|
|
|
model_id: str
|
|
|
|
|
display_name: str
|
|
|
|
|
provider: str = ""
|
|
|
|
|
description: str = ""
|
|
|
|
|
|
2026-05-13 16:12:02 +08:00
|
|
|
@property
|
|
|
|
|
def button_id(self) -> str:
|
|
|
|
|
return hashlib.sha256(self.model_id.encode()).hexdigest()[:8]
|
|
|
|
|
|
2026-05-12 00:46:12 +08:00
|
|
|
|
|
|
|
|
def get_default_model_options() -> list[ModelOption]:
|
|
|
|
|
return [
|
|
|
|
|
ModelOption("gpt-4o-mini", "GPT-4o Mini", "openai", "轻量快速模型"),
|
|
|
|
|
ModelOption("gpt-4o", "GPT-4o", "openai", "旗舰多模态模型"),
|
|
|
|
|
ModelOption("claude-sonnet-4-20250514", "Claude Sonnet 4", "anthropic", "高性能推理模型"),
|
|
|
|
|
ModelOption("deepseek-v3", "DeepSeek V3", "deepseek", "国产高性能模型"),
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
2026-05-13 16:12:02 +08:00
|
|
|
def get_available_providers(models: list[ModelOption] | None = None) -> list[str]:
|
|
|
|
|
if models is None:
|
|
|
|
|
models = get_default_model_options()
|
|
|
|
|
seen: dict[str, bool] = {}
|
|
|
|
|
result: list[str] = []
|
|
|
|
|
for m in models:
|
|
|
|
|
if m.provider and m.provider not in seen:
|
|
|
|
|
seen[m.provider] = True
|
|
|
|
|
result.append(m.provider)
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
2026-05-12 00:46:12 +08:00
|
|
|
def build_model_picker_actions(
|
|
|
|
|
models: list[ModelOption] | None = None,
|
|
|
|
|
callback_id: str = "model_picker",
|
2026-05-13 16:12:02 +08:00
|
|
|
state: PickerState = PickerState.LIST,
|
|
|
|
|
page: int = 0,
|
2026-05-12 00:46:12 +08:00
|
|
|
) -> list[dict]:
|
|
|
|
|
if models is None:
|
|
|
|
|
models = get_default_model_options()
|
|
|
|
|
|
2026-05-13 16:12:02 +08:00
|
|
|
actions: list[dict] = []
|
|
|
|
|
|
|
|
|
|
if state == PickerState.PROVIDERS:
|
|
|
|
|
providers = get_available_providers(models)
|
|
|
|
|
for provider in providers:
|
|
|
|
|
actions.append(
|
|
|
|
|
{
|
|
|
|
|
"id": f"provider_{provider}",
|
|
|
|
|
"name": f"查看 {provider} 模型",
|
|
|
|
|
"integration": {
|
|
|
|
|
"url": "",
|
|
|
|
|
"context": {
|
|
|
|
|
"action": "list_provider",
|
|
|
|
|
"provider": provider,
|
|
|
|
|
"callback_id": callback_id,
|
|
|
|
|
"state": PickerState.LIST.value,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
"type": "button",
|
|
|
|
|
"text": f"🤖 {provider.upper()}",
|
|
|
|
|
"style": "",
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
actions.append(
|
|
|
|
|
{
|
|
|
|
|
"id": "view_all",
|
|
|
|
|
"name": "查看全部模型",
|
|
|
|
|
"integration": {
|
|
|
|
|
"url": "",
|
|
|
|
|
"context": {
|
|
|
|
|
"action": "list_all",
|
|
|
|
|
"callback_id": callback_id,
|
|
|
|
|
"state": PickerState.LIST.value,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
"type": "button",
|
|
|
|
|
"text": "📋 全部模型",
|
|
|
|
|
"style": "",
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
return actions
|
|
|
|
|
|
|
|
|
|
start = page * MODEL_PICKER_PAGE_SIZE
|
|
|
|
|
page_models = models[start : start + MODEL_PICKER_PAGE_SIZE]
|
|
|
|
|
total_pages = (len(models) + MODEL_PICKER_PAGE_SIZE - 1) // MODEL_PICKER_PAGE_SIZE
|
|
|
|
|
|
|
|
|
|
for m in page_models:
|
2026-05-12 00:46:12 +08:00
|
|
|
actions.append(
|
|
|
|
|
{
|
2026-05-13 16:12:02 +08:00
|
|
|
"id": f"model_{m.button_id}",
|
2026-05-12 00:46:12 +08:00
|
|
|
"name": f"选择 {m.display_name}",
|
|
|
|
|
"integration": {
|
|
|
|
|
"url": "",
|
|
|
|
|
"context": {
|
|
|
|
|
"action": "select_model",
|
|
|
|
|
"model_id": m.model_id,
|
|
|
|
|
"callback_id": callback_id,
|
2026-05-13 16:12:02 +08:00
|
|
|
"state": PickerState.SELECT.value,
|
2026-05-12 00:46:12 +08:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
"type": "button",
|
|
|
|
|
"text": f"🤖 {m.display_name}",
|
|
|
|
|
"style": "",
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
2026-05-13 16:12:02 +08:00
|
|
|
if total_pages > 1:
|
|
|
|
|
nav_actions: list[dict] = []
|
|
|
|
|
if page > 0:
|
|
|
|
|
nav_actions.append(
|
|
|
|
|
{
|
|
|
|
|
"id": "prev_page",
|
|
|
|
|
"name": "上一页",
|
|
|
|
|
"integration": {
|
|
|
|
|
"url": "",
|
|
|
|
|
"context": {
|
|
|
|
|
"action": "page",
|
|
|
|
|
"page": str(page - 1),
|
|
|
|
|
"callback_id": callback_id,
|
|
|
|
|
"state": PickerState.LIST.value,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
"type": "button",
|
|
|
|
|
"text": "⬅️ 上一页",
|
|
|
|
|
"style": "",
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
if page < total_pages - 1:
|
|
|
|
|
nav_actions.append(
|
|
|
|
|
{
|
|
|
|
|
"id": "next_page",
|
|
|
|
|
"name": "下一页",
|
|
|
|
|
"integration": {
|
|
|
|
|
"url": "",
|
|
|
|
|
"context": {
|
|
|
|
|
"action": "page",
|
|
|
|
|
"page": str(page + 1),
|
|
|
|
|
"callback_id": callback_id,
|
|
|
|
|
"state": PickerState.LIST.value,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
"type": "button",
|
|
|
|
|
"text": "➡️ 下一页",
|
|
|
|
|
"style": "",
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
actions.extend(nav_actions)
|
|
|
|
|
|
|
|
|
|
actions.append(
|
|
|
|
|
{
|
|
|
|
|
"id": "back_to_providers",
|
|
|
|
|
"name": "按提供商筛选",
|
|
|
|
|
"integration": {
|
|
|
|
|
"url": "",
|
|
|
|
|
"context": {
|
|
|
|
|
"action": "back",
|
|
|
|
|
"callback_id": callback_id,
|
|
|
|
|
"state": PickerState.PROVIDERS.value,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
"type": "button",
|
|
|
|
|
"text": "🔙 按提供商筛选",
|
|
|
|
|
"style": "",
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
2026-05-12 00:46:12 +08:00
|
|
|
return actions
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def build_model_picker_attachment(
|
|
|
|
|
current_model: str = "",
|
|
|
|
|
callback_id: str = "model_picker",
|
2026-05-13 16:12:02 +08:00
|
|
|
state: PickerState = PickerState.PROVIDERS,
|
|
|
|
|
page: int = 0,
|
|
|
|
|
provider_filter: str = "",
|
2026-05-12 00:46:12 +08:00
|
|
|
) -> dict:
|
|
|
|
|
models = get_default_model_options()
|
|
|
|
|
|
2026-05-13 16:12:02 +08:00
|
|
|
if provider_filter:
|
|
|
|
|
models = [m for m in models if m.provider == provider_filter]
|
|
|
|
|
|
|
|
|
|
if state == PickerState.PROVIDERS:
|
|
|
|
|
providers = get_available_providers(models)
|
|
|
|
|
lines = [f"• **{p.upper()}**" for p in providers]
|
|
|
|
|
text = "按 AI 提供商筛选模型:\n\n" + "\n".join(lines)
|
|
|
|
|
else:
|
|
|
|
|
start = page * MODEL_PICKER_PAGE_SIZE
|
|
|
|
|
page_models = models[start : start + MODEL_PICKER_PAGE_SIZE]
|
|
|
|
|
total_pages = (len(models) + MODEL_PICKER_PAGE_SIZE - 1) // MODEL_PICKER_PAGE_SIZE
|
|
|
|
|
|
|
|
|
|
lines = []
|
|
|
|
|
for m in page_models:
|
|
|
|
|
marker = " ✅" if m.model_id == current_model else ""
|
|
|
|
|
lines.append(f"• **{m.display_name}** ({m.provider}) — {m.description}{marker}")
|
|
|
|
|
|
|
|
|
|
text = f"当前可用模型 (页 {page + 1}/{total_pages}):\n\n" + "\n".join(lines)
|
|
|
|
|
|
|
|
|
|
actions = build_model_picker_actions(models, callback_id, state, page)
|
2026-05-12 00:46:12 +08:00
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
"fallback": "模型选择器",
|
|
|
|
|
"title": "🤖 模型选择",
|
|
|
|
|
"text": text,
|
|
|
|
|
"callback_id": callback_id,
|
|
|
|
|
"actions": actions,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def build_model_select_attachment(provider: str, callback_id: str = "model_select") -> dict:
|
|
|
|
|
all_models = get_default_model_options()
|
|
|
|
|
provider_models = [m for m in all_models if m.provider == provider]
|
2026-05-13 16:12:02 +08:00
|
|
|
actions = build_model_picker_actions(provider_models, callback_id, PickerState.LIST, 0)
|
2026-05-12 00:46:12 +08:00
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
"fallback": f"选择 {provider} 模型",
|
|
|
|
|
"title": f"🤖 {provider} 模型",
|
|
|
|
|
"text": f"选择 {provider} 下的模型:",
|
|
|
|
|
"callback_id": callback_id,
|
|
|
|
|
"actions": actions,
|
|
|
|
|
}
|