File size: 3,771 Bytes
a21c316
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import { useMemo, useEffect } from 'react';
import { MODEL_CONFIG } from '../config/modelConfig';
import { useAccountStore } from '../stores/useAccountStore';
import { Bot } from 'lucide-react';
import { useTranslation } from 'react-i18next';

export const useProxyModels = () => {
    const { t } = useTranslation();
    const { accounts, fetchAccounts } = useAccountStore();

    // 确保账号数据已加载(针对未触发 fetchAccounts 的页面,如 ApiProxy)
    useEffect(() => {
        if (accounts.length === 0) {
            fetchAccounts();
        }
    }, []); // eslint-disable-line react-hooks/exhaustive-deps

    const models = useMemo(() => {
        // Step 1: 从所有账号中收集动态模型
        // 以 name(小写)为 key 去重,优先保留含 display_name 的条目
        const dynamicMap = new Map<string, { name: string; display_name?: string }>();
        for (const account of accounts) {
            for (const m of account.quota?.models ?? []) {
                const key = m.name.toLowerCase();
                if (!dynamicMap.has(key) || m.display_name) {
                    dynamicMap.set(key, { name: m.name, display_name: m.display_name });
                }
            }
        }

        const result = [];
        const seenIds = new Set<string>();

        // Step 2: 优先展示来自账号的动态模型(display_name 为主名称,name 为 ID)
        for (const [key, m] of dynamicMap) {
            if (seenIds.has(key)) continue;
            seenIds.add(key);

            // 尝试匹配 MODEL_CONFIG 里的图标与分组
            const cfgEntry = Object.entries(MODEL_CONFIG).find(
                ([cfgId, cfg]) =>
                    cfgId.toLowerCase() === key ||
                    (cfg.protectedKey && cfg.protectedKey.toLowerCase() === key)
            );

            const primaryName = m.display_name || m.name;
            const CfgIcon = cfgEntry?.[1].Icon;
            const icon = CfgIcon
                ? <CfgIcon size={16} />
                : <Bot size={16} className="text-gray-400 dark:text-gray-500" />;
            const group = cfgEntry ? (cfgEntry[1].group || 'Other') : 'Dynamic';

            result.push({
                id: m.name,           // 原始模型 name,作为 ID 展示
                name: primaryName,    // display_name(主要展示名称)
                desc: primaryName,    // 描述栏同样用 display_name
                group,
                icon,
            });
        }

        // Step 3: 对于 MODEL_CONFIG 里有但账号未下发的型号,作为静态兜底补充
        const addedLabels = new Set<string>();
        for (const [id, config] of Object.entries(MODEL_CONFIG)) {
            const key = id.toLowerCase();
            if (seenIds.has(key)) {
                addedLabels.add((config.shortLabel || config.label).toLowerCase());
                continue;
            }
            // 跳过 thinking 变体(这类模型的动态版本由账号数据中 supports_thinking 标记覆盖)
            if (key.includes('-thinking')) continue;
            // 跳过 label 重复的别名条目
            const labelKey = (config.shortLabel || config.label).toLowerCase();
            if (addedLabels.has(labelKey)) continue;
            addedLabels.add(labelKey);
            seenIds.add(key);

            const displayName = config.i18nKey ? t(config.i18nKey, config.label) : config.label;
            result.push({
                id,
                name: displayName,
                desc: displayName,
                group: config.group || 'Other',
                icon: <config.Icon size={16} />,
            });
        }

        return result;
    }, [accounts, t]);

    return { models };
};