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:
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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 キーを管理します。',
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export * from './settings';
|
||||
export * from './models';
|
||||
export * from './agents';
|
||||
export * from './chat';
|
||||
export * from './task';
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
Reference in New Issue
Block a user