refactor: remove models snapshot feature and related code

- Remove models snapshot UI section from Models page
- Delete models API route handler and related logic
- Remove models store implementation and exports
- Clean up internationalization messages for removed feature
- Update main entry point exports to exclude models store
This commit is contained in:
DEV_DSW
2026-04-20 14:44:27 +08:00
parent 3de3629d12
commit c9a2f3631e
8 changed files with 1 additions and 361 deletions

View File

@@ -1,6 +1,6 @@
"use strict";
require("electron");
require("./main-XRVcGPgv.js");
require("./main-Bc3HYL6W.js");
require("electron-squirrel-startup");
require("electron-log");
require("bytenode");

View File

@@ -11,7 +11,6 @@ import { handleCronRoutes } from './routes/cron';
import { handleFileRoutes } from './routes/files';
import { handleGatewayRoutes } from './routes/gateway';
import { handleKnowledgeRoutes } from './routes/knowledge';
import { handleModelRoutes } from './routes/models';
import { handleProviderRoutes } from './routes/providers';
import { handleSessionRoutes } from './routes/sessions';
import { handleSkillRoutes } from './routes/skills';
@@ -25,7 +24,6 @@ const routeHandlers: RouteHandler[] = [
handleProviderRoutes,
handleChannelRoutes,
handleAgentRoutes,
handleModelRoutes,
handleCronRoutes,
handleGatewayRoutes,
handleKnowledgeRoutes,

View File

@@ -1,92 +0,0 @@
import type { ProviderAccount } from '@runtime/lib/providers';
import {
DEFAULT_AGENT_ID,
DEFAULT_MAIN_SESSION_SUFFIX,
buildMainSessionKey,
normalizeAgentId,
type AgentSummary,
} from '@runtime/lib/models';
import type { HostApiContext } from '../context';
import type { NormalizedHostApiRequest } from '../route-utils';
import { ok } from '../route-utils';
function formatModelDisplay(modelRef: string | null | undefined, fallbackLabel: string): string {
const trimmed = String(modelRef ?? '').trim();
if (!trimmed) return fallbackLabel;
const parts = trimmed.split('/');
return parts[parts.length - 1] || trimmed;
}
function buildMainModel(defaultAccount: ProviderAccount | null): AgentSummary {
return {
id: DEFAULT_AGENT_ID,
name: 'Main Model',
isDefault: true,
providerAccountId: defaultAccount?.id ?? null,
modelRef: defaultAccount?.model ?? null,
modelDisplay: formatModelDisplay(defaultAccount?.model, defaultAccount?.label || 'Unassigned'),
mainSessionKey: buildMainSessionKey(DEFAULT_AGENT_ID, DEFAULT_MAIN_SESSION_SUFFIX),
vendorId: defaultAccount?.vendorId ?? null,
source: 'synthetic-main',
};
}
function buildProviderBackedModels(accounts: ProviderAccount[]): AgentSummary[] {
const seen = new Set<string>();
const summaries: AgentSummary[] = [];
for (const account of accounts) {
const agentId = normalizeAgentId(account.id);
if (seen.has(agentId) || agentId === DEFAULT_AGENT_ID) continue;
seen.add(agentId);
summaries.push({
id: agentId,
name: account.label || agentId,
isDefault: false,
providerAccountId: account.id,
modelRef: account.model ?? null,
modelDisplay: formatModelDisplay(account.model, account.label || agentId),
mainSessionKey: buildMainSessionKey(agentId, DEFAULT_MAIN_SESSION_SUFFIX),
vendorId: account.vendorId,
source: 'provider-account',
});
}
return summaries;
}
export async function handleModelRoutes(
request: NormalizedHostApiRequest,
ctx: HostApiContext,
) {
const { pathname, method } = request;
if (pathname !== '/api/models' || method !== 'GET') {
return null;
}
const accounts = ctx.providerApiService
.getAccounts()
.filter((account) => account.enabled !== false);
const defaultAccountId = ctx.providerApiService.getDefault().accountId;
const defaultAccount = accounts.find((account) => account.id === defaultAccountId) ?? accounts[0] ?? null;
const models = [
buildMainModel(defaultAccount),
...buildProviderBackedModels(accounts),
];
return ok({
success: true,
models,
agents: models,
defaultAgentId: DEFAULT_AGENT_ID,
defaultProviderAccountId: defaultAccount?.id ?? null,
defaultModelRef: defaultAccount?.model ?? null,
mainSessionSuffix: DEFAULT_MAIN_SESSION_SUFFIX,
configuredChannelTypes: [],
channelOwners: {},
channelAccountOwners: {},
});
}

View File

@@ -1,86 +0,0 @@
import { useEffect } from 'react';
import { modelsStore, useModelsStore } from '../../../stores';
import { useModelsCopy } from '../copy';
const CHIP_CLASS_NAME = [
'rounded-full border px-2.5 py-1 text-[11px] leading-none',
'border-[#E5E8EE] text-[#525866] dark:border-[#2a2a2d] dark:text-gray-300',
].join(' ');
export default function ModelsSection() {
const modelsState = useModelsStore();
const t = useModelsCopy();
useEffect(() => {
void modelsStore.init();
}, []);
return (
<section className="space-y-5">
<div className="flex items-end justify-between gap-4">
<div>
<h3 className="text-[18px] font-semibold leading-[24px] text-[#171717] dark:text-gray-100">
{t('models.snapshot.title')}
</h3>
<p className="mt-1 text-[13px] leading-[20px] text-[#99A0AE] dark:text-gray-500">
{t('models.snapshot.subtitle')}
</p>
</div>
<button
type="button"
className="rounded-full border border-[#E5E8EE] px-3 py-1.5 text-[12px] text-[#525866] transition-colors hover:border-[#2B7FFF] hover:text-[#2B7FFF] dark:border-[#2a2a2d] dark:text-gray-300"
onClick={() => {
void modelsStore.load();
}}
>
{t('models.snapshot.refresh')}
</button>
</div>
{modelsState.error ? (
<div className="rounded-[14px] border border-[#F1D4D4] bg-[#FFF5F5] px-4 py-3 text-sm text-[#A53A3A] dark:border-[#4b2a2a] dark:bg-[#2a1f1f] dark:text-[#f7b8b8]">
{modelsState.error}
</div>
) : null}
<div className="grid gap-4 md:grid-cols-2">
{modelsState.models.map((model) => (
<article
key={model.id}
className="rounded-[16px] border border-[#E5E8EE] bg-[#FAFBFC] p-4 dark:border-[#2a2a2d] dark:bg-[#202024]"
>
<div className="flex items-start justify-between gap-3">
<div className="min-w-0">
<div className="truncate text-[15px] font-semibold text-[#171717] dark:text-gray-100">
{model.name}
</div>
<div className="mt-1 text-[12px] text-[#99A0AE] dark:text-gray-500">{model.id}</div>
</div>
{model.isDefault ? (
<span className="rounded-full bg-[#EFF6FF] px-2.5 py-1 text-[11px] font-medium text-[#2B7FFF] dark:bg-[#1d2633]">
{t('models.snapshot.defaultBadge')}
</span>
) : null}
</div>
<div className="mt-4 flex flex-wrap gap-2">
<span className={CHIP_CLASS_NAME}>{t('models.snapshot.provider')}: {model.providerAccountId || '--'}</span>
<span className={CHIP_CLASS_NAME}>{t('models.snapshot.model')}: {model.modelDisplay || '--'}</span>
</div>
<div className="mt-4 rounded-[12px] border border-dashed border-[#DCE5F1] bg-white px-3 py-2 text-[12px] leading-[18px] text-[#525866] dark:border-[#2a2a2d] dark:bg-[#17171a] dark:text-gray-300">
<div className="font-medium text-[#171717] dark:text-gray-100">{t('models.snapshot.mainSessionKey')}</div>
<div className="mt-1 break-all">{model.mainSessionKey}</div>
</div>
</article>
))}
{!modelsState.loading && modelsState.models.length === 0 ? (
<div className="rounded-[16px] border border-dashed border-[#DCE5F1] bg-[#FAFBFC] px-4 py-6 text-sm text-[#525866] dark:border-[#2a2a2d] dark:bg-[#202024] dark:text-gray-300">
{t('models.snapshot.empty')}
</div>
) : null}
</div>
</section>
);
}

View File

@@ -1,4 +1,3 @@
import ModelsSection from './components/ModelsSection';
import ProvidersSection from './components/ProvidersSection';
import UsageHistorySection from './components/UsageHistorySection';
import { useModelsCopy } from './copy';
@@ -21,7 +20,6 @@ export default function ModelsPage() {
</div>
<div className="min-h-0 flex-1 space-y-12 overflow-y-auto pb-10 pr-2">
<ModelsSection />
<ProvidersSection />
<UsageHistorySection />
</div>

View File

@@ -18,16 +18,6 @@ const EN_MODELS_MESSAGES: MessageTree = {
loading: 'Loading...',
ai: 'AI',
},
snapshot: {
title: 'Models Snapshot',
subtitle: 'Local `/api/models` snapshot and `mainSessionKey` mapping.',
refresh: 'Refresh Models',
defaultBadge: 'Default',
provider: 'Provider',
model: 'Model',
mainSessionKey: 'mainSessionKey',
empty: 'No models are available yet. Configure a provider account below and routable model snapshots will appear here automatically.',
},
providers: {
title: 'AI Providers',
subtitle: 'Manage your AI models and API keys.',
@@ -128,16 +118,6 @@ const ZH_MODELS_MESSAGES: MessageTree = {
loading: '加载中...',
ai: 'AI',
},
snapshot: {
title: '模型快照',
subtitle: '本地 `/api/models` 快照与 `mainSessionKey` 映射。',
refresh: '刷新模型',
defaultBadge: '默认',
provider: '服务商',
model: '模型',
mainSessionKey: '主会话 Key',
empty: '当前还没有可用模型。先在下方配置服务商账号后,这里会自动生成可路由的模型快照。',
},
providers: {
title: 'AI 服务商',
subtitle: '管理你的 AI 模型与 API Key。',
@@ -238,16 +218,6 @@ const JA_MODELS_MESSAGES: MessageTree = {
loading: '読み込み中...',
ai: 'AI',
},
snapshot: {
title: 'モデルスナップショット',
subtitle: 'ローカル `/api/models` スナップショットと `mainSessionKey` の対応です。',
refresh: 'モデルを更新',
defaultBadge: 'デフォルト',
provider: 'プロバイダー',
model: 'モデル',
mainSessionKey: 'メインセッションキー',
empty: '利用可能なモデルがまだありません。下でプロバイダーアカウントを設定すると、ルーティング可能なモデルスナップショットが自動生成されます。',
},
providers: {
title: 'AI プロバイダー',
subtitle: 'AI モデルと API キーを管理します。',

View File

@@ -1,5 +1,4 @@
export * from './settings';
export * from './models';
export * from './agents';
export * from './chat';
export * from './task';

View File

@@ -1,147 +0,0 @@
import { useSyncExternalStore } from 'react';
import {
DEFAULT_AGENT_ID,
DEFAULT_MAIN_SESSION_SUFFIX,
buildMainSessionKey,
normalizeAgentId,
type AgentSummary,
type ModelsSnapshot,
} from '@runtime/lib/models';
import { hostApiFetch } from '../lib/host-api';
export interface ModelsStoreState {
initialized: boolean;
loading: boolean;
error: string | null;
models: AgentSummary[];
defaultAgentId: string;
defaultProviderAccountId: string | null;
defaultModelRef: string | null;
mainSessionSuffix: string;
}
const listeners = new Set<() => void>();
let loadModelsInFlight: Promise<void> | null = null;
let state: ModelsStoreState = {
initialized: false,
loading: false,
error: null,
models: [],
defaultAgentId: DEFAULT_AGENT_ID,
defaultProviderAccountId: null,
defaultModelRef: null,
mainSessionSuffix: DEFAULT_MAIN_SESSION_SUFFIX,
};
function emit(): void {
for (const listener of listeners) {
listener();
}
}
function patchState(patch: Partial<ModelsStoreState>): ModelsStoreState {
state = { ...state, ...patch };
emit();
return state;
}
function sanitizeModel(model: AgentSummary): AgentSummary {
const normalizedId = normalizeAgentId(model.id);
const normalizedMainSessionKey = model.mainSessionKey || buildMainSessionKey(normalizedId);
return {
id: normalizedId,
name: model.name || normalizedId,
isDefault: Boolean(model.isDefault),
providerAccountId: model.providerAccountId ?? null,
modelRef: model.modelRef ?? null,
modelDisplay: model.modelDisplay || model.modelRef || model.name || normalizedId,
mainSessionKey: normalizedMainSessionKey,
vendorId: model.vendorId ?? null,
source: model.source,
};
}
async function loadModels(): Promise<void> {
if (loadModelsInFlight) {
await loadModelsInFlight;
return;
}
loadModelsInFlight = (async () => {
patchState({ loading: true, error: null });
try {
const snapshot = await hostApiFetch<ModelsSnapshot & { success?: boolean }>('/api/models');
const models = Array.isArray(snapshot?.models)
? snapshot.models.map((model) => sanitizeModel(model))
: Array.isArray(snapshot?.agents)
? snapshot.agents.map((model) => sanitizeModel(model))
: [];
patchState({
initialized: true,
loading: false,
error: null,
models,
defaultAgentId: snapshot?.defaultAgentId ? normalizeAgentId(snapshot.defaultAgentId) : DEFAULT_AGENT_ID,
defaultProviderAccountId: snapshot?.defaultProviderAccountId ?? null,
defaultModelRef: snapshot?.defaultModelRef ?? null,
mainSessionSuffix: snapshot?.mainSessionSuffix || DEFAULT_MAIN_SESSION_SUFFIX,
});
} catch (error) {
patchState({
initialized: true,
loading: false,
error: error instanceof Error ? error.message : String(error),
});
}
})();
try {
await loadModelsInFlight;
} finally {
loadModelsInFlight = null;
}
}
function subscribe(listener: () => void): () => void {
listeners.add(listener);
return () => listeners.delete(listener);
}
function getSnapshot(): ModelsStoreState {
return state;
}
function getModelById(modelId: string | null | undefined): AgentSummary | undefined {
const normalizedId = normalizeAgentId(modelId);
return state.models.find((model) => model.id === normalizedId);
}
function resolveMainSessionKey(agentId: string | null | undefined): string {
const normalizedId = normalizeAgentId(agentId || state.defaultAgentId);
return getModelById(normalizedId)?.mainSessionKey || buildMainSessionKey(normalizedId, state.mainSessionSuffix);
}
function resolveProviderAccountId(agentId: string | null | undefined): string | null {
const normalizedId = normalizeAgentId(agentId || state.defaultAgentId);
return getModelById(normalizedId)?.providerAccountId ?? state.defaultProviderAccountId;
}
export const modelsStore = {
subscribe,
getSnapshot,
getState: () => state,
init: loadModels,
load: loadModels,
getModelById,
resolveMainSessionKey,
resolveProviderAccountId,
};
export function useModelsStore<T = ModelsStoreState>(selector?: (state: ModelsStoreState) => T): T {
const select = selector ?? ((current: ModelsStoreState) => current as unknown as T);
return useSyncExternalStore(subscribe, () => select(getSnapshot()), () => select(getSnapshot()));
}