feat: add models management and usage history components

- Introduced RequestContentDialog for displaying request content details.
- Added UsageBarChart for visualizing token usage data.
- Implemented UsageHistorySection to manage and display usage history with filtering and pagination.
- Created provider-types for managing provider-related types.
- Developed ModelsPage to encapsulate models configuration, providers, and usage history.
- Defined usage-history types and utility functions for managing usage data.
- Updated routing to include models page and redirect agents to models.
- Refactored chat store to integrate models instead of agents.
- Established models store for managing model-related state and data fetching.
This commit is contained in:
duanshuwen
2026-04-18 09:41:59 +08:00
parent 1205a96661
commit 85d92b937f
28 changed files with 343 additions and 258 deletions

View File

@@ -7,13 +7,13 @@ import {
normalizeAgentId,
normalizeAgentSessionKey,
parseSessionKey,
} from '@runtime/lib/agents';
} from '@runtime/lib/models';
import type { ChatSession, RawMessage, ToolStatus } from '../shared/chat-model';
import { extractText, isToolOnlyMessage } from '../shared/chat-model';
import { gatewayRpc, onGatewayEvent } from '../lib/gateway-client';
import { hostApiFetch } from '../lib/host-api';
import type { GatewayEvent } from '../types/runtime';
import { agentsStore } from './agents';
import { modelsStore } from './models';
const SESSION_LOAD_MIN_INTERVAL_MS = 1200;
const HISTORY_LOAD_MIN_INTERVAL_MS = 800;
@@ -101,19 +101,19 @@ function patchState(patch: Partial<ChatStoreState>): ChatStoreState {
function getAgentIdFromSessionKey(sessionKey: string): string {
const parsed = parseSessionKey(normalizeAgentSessionKey(sessionKey));
if (parsed.isAgentSession) return parsed.agentId;
return agentsStore.getState().defaultAgentId || FALLBACK_AGENT_ID;
return modelsStore.getState().defaultAgentId || FALLBACK_AGENT_ID;
}
function getDefaultAgentId(): string {
return agentsStore.getState().defaultAgentId || FALLBACK_AGENT_ID;
return modelsStore.getState().defaultAgentId || FALLBACK_AGENT_ID;
}
function getDefaultMainSessionKey(): string {
return agentsStore.resolveMainSessionKey(getDefaultAgentId()) || FALLBACK_MAIN_SESSION_KEY;
return modelsStore.resolveMainSessionKey(getDefaultAgentId()) || FALLBACK_MAIN_SESSION_KEY;
}
function resolveMainSessionKeyForAgent(agentId: string | null | undefined): string {
return agentsStore.resolveMainSessionKey(agentId || getDefaultAgentId()) || getDefaultMainSessionKey();
return modelsStore.resolveMainSessionKey(agentId || getDefaultAgentId()) || getDefaultMainSessionKey();
}
function buildNewSessionKey(agentId: string | null | undefined): string {
@@ -208,7 +208,7 @@ async function resolveDefaultAccountId(): Promise<string | null> {
}
async function resolveProviderAccountIdForAgent(agentId: string | null | undefined): Promise<string | null> {
const mappedAccountId = agentsStore.resolveProviderAccountId(agentId);
const mappedAccountId = modelsStore.resolveProviderAccountId(agentId);
if (mappedAccountId) {
return mappedAccountId;
}
@@ -283,7 +283,7 @@ async function subscribeToGateway(): Promise<void> {
}
async function loadSessions(): Promise<void> {
await agentsStore.init();
await modelsStore.init();
const now = Date.now();
if (loadSessionsInFlight) {
await loadSessionsInFlight;
@@ -850,7 +850,7 @@ function clearError(): void {
}
async function initChatStore(): Promise<void> {
await agentsStore.init();
await modelsStore.init();
await subscribeToGateway();
await loadSessions();
}

View File

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

View File

@@ -5,15 +5,15 @@ import {
buildMainSessionKey,
normalizeAgentId,
type AgentSummary,
type AgentsSnapshot,
} from '@runtime/lib/agents';
type ModelsSnapshot,
} from '@runtime/lib/models';
import { hostApiFetch } from '../lib/host-api';
export interface AgentsStoreState {
export interface ModelsStoreState {
initialized: boolean;
loading: boolean;
error: string | null;
agents: AgentSummary[];
models: AgentSummary[];
defaultAgentId: string;
defaultProviderAccountId: string | null;
defaultModelRef: string | null;
@@ -22,12 +22,12 @@ export interface AgentsStoreState {
const listeners = new Set<() => void>();
let loadAgentsInFlight: Promise<void> | null = null;
let state: AgentsStoreState = {
let loadModelsInFlight: Promise<void> | null = null;
let state: ModelsStoreState = {
initialized: false,
loading: false,
error: null,
agents: [],
models: [],
defaultAgentId: DEFAULT_AGENT_ID,
defaultProviderAccountId: null,
defaultModelRef: null,
@@ -40,49 +40,51 @@ function emit(): void {
}
}
function patchState(patch: Partial<AgentsStoreState>): AgentsStoreState {
function patchState(patch: Partial<ModelsStoreState>): ModelsStoreState {
state = { ...state, ...patch };
emit();
return state;
}
function sanitizeAgent(agent: AgentSummary): AgentSummary {
const normalizedId = normalizeAgentId(agent.id);
const normalizedMainSessionKey = agent.mainSessionKey || buildMainSessionKey(normalizedId);
function sanitizeModel(model: AgentSummary): AgentSummary {
const normalizedId = normalizeAgentId(model.id);
const normalizedMainSessionKey = model.mainSessionKey || buildMainSessionKey(normalizedId);
return {
id: normalizedId,
name: agent.name || normalizedId,
isDefault: Boolean(agent.isDefault),
providerAccountId: agent.providerAccountId ?? null,
modelRef: agent.modelRef ?? null,
modelDisplay: agent.modelDisplay || agent.modelRef || agent.name || 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: agent.vendorId ?? null,
source: agent.source,
vendorId: model.vendorId ?? null,
source: model.source,
};
}
async function loadAgents(): Promise<void> {
if (loadAgentsInFlight) {
await loadAgentsInFlight;
async function loadModels(): Promise<void> {
if (loadModelsInFlight) {
await loadModelsInFlight;
return;
}
loadAgentsInFlight = (async () => {
loadModelsInFlight = (async () => {
patchState({ loading: true, error: null });
try {
const snapshot = await hostApiFetch<AgentsSnapshot & { success?: boolean }>('/api/agents');
const agents = Array.isArray(snapshot?.agents)
? snapshot.agents.map((agent) => sanitizeAgent(agent))
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,
agents,
models,
defaultAgentId: snapshot?.defaultAgentId ? normalizeAgentId(snapshot.defaultAgentId) : DEFAULT_AGENT_ID,
defaultProviderAccountId: snapshot?.defaultProviderAccountId ?? null,
defaultModelRef: snapshot?.defaultModelRef ?? null,
@@ -98,9 +100,9 @@ async function loadAgents(): Promise<void> {
})();
try {
await loadAgentsInFlight;
await loadModelsInFlight;
} finally {
loadAgentsInFlight = null;
loadModelsInFlight = null;
}
}
@@ -109,36 +111,36 @@ function subscribe(listener: () => void): () => void {
return () => listeners.delete(listener);
}
function getSnapshot(): AgentsStoreState {
function getSnapshot(): ModelsStoreState {
return state;
}
function getAgentById(agentId: string | null | undefined): AgentSummary | undefined {
const normalizedId = normalizeAgentId(agentId);
return state.agents.find((agent) => agent.id === normalizedId);
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 getAgentById(normalizedId)?.mainSessionKey || buildMainSessionKey(normalizedId, state.mainSessionSuffix);
return getModelById(normalizedId)?.mainSessionKey || buildMainSessionKey(normalizedId, state.mainSessionSuffix);
}
function resolveProviderAccountId(agentId: string | null | undefined): string | null {
const normalizedId = normalizeAgentId(agentId || state.defaultAgentId);
return getAgentById(normalizedId)?.providerAccountId ?? state.defaultProviderAccountId;
return getModelById(normalizedId)?.providerAccountId ?? state.defaultProviderAccountId;
}
export const agentsStore = {
export const modelsStore = {
subscribe,
getSnapshot,
getState: () => state,
init: loadAgents,
load: loadAgents,
getAgentById,
init: loadModels,
load: loadModels,
getModelById,
resolveMainSessionKey,
resolveProviderAccountId,
};
export function useAgentsStore(): AgentsStoreState {
return useSyncExternalStore(agentsStore.subscribe, agentsStore.getSnapshot, agentsStore.getSnapshot);
export function useModelsStore(): ModelsStoreState {
return useSyncExternalStore(modelsStore.subscribe, modelsStore.getSnapshot, modelsStore.getSnapshot);
}