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:
@@ -15,15 +15,15 @@ import {
|
||||
import { IPC_EVENTS } from '../../lib/constants';
|
||||
import { invokeIpc } from '../../lib/host-api';
|
||||
import {
|
||||
agentsStore,
|
||||
channelStore,
|
||||
chatStore,
|
||||
getCompletedTasks,
|
||||
getPendingTasks,
|
||||
modelsStore,
|
||||
taskStore,
|
||||
useAgentsStore,
|
||||
useChannelStore,
|
||||
useChatStore,
|
||||
useModelsStore,
|
||||
useTaskStore,
|
||||
type StagedAttachment,
|
||||
} from '../../stores';
|
||||
@@ -149,7 +149,7 @@ function mapMessages(messages: RawMessage[], streamingMessage: RawMessage | null
|
||||
}
|
||||
|
||||
export default function HomePage() {
|
||||
const agentsState = useAgentsStore();
|
||||
const modelsState = useModelsStore();
|
||||
const chat = useChatStore();
|
||||
const taskState = useTaskStore();
|
||||
const channelState = useChannelStore();
|
||||
@@ -161,7 +161,7 @@ export default function HomePage() {
|
||||
const [addChannelDialogOpen, setAddChannelDialogOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
void agentsStore.init();
|
||||
void modelsStore.init();
|
||||
void chatStore.init();
|
||||
void taskStore.init();
|
||||
void channelStore.init();
|
||||
@@ -212,10 +212,10 @@ export default function HomePage() {
|
||||
const latestTask = currentTaskSource[0];
|
||||
|
||||
const visibleMessages = mapMessages(chat.messages, chat.streamingMessage);
|
||||
const selectedAgentId = agentsState.agents.some((agent) => agent.id === chat.currentAgentId)
|
||||
const selectedModelId = modelsState.models.some((model) => model.id === chat.currentAgentId)
|
||||
? chat.currentAgentId
|
||||
: agentsState.defaultAgentId;
|
||||
const currentAgent = agentsState.agents.find((agent) => agent.id === selectedAgentId) || null;
|
||||
: modelsState.defaultAgentId;
|
||||
const currentModel = modelsState.models.find((model) => model.id === selectedModelId) || null;
|
||||
|
||||
async function handleSendMessage(): Promise<void> {
|
||||
const sent = await chatStore.sendMessage(inputMessage, attachments);
|
||||
@@ -288,7 +288,7 @@ export default function HomePage() {
|
||||
loading={!chat.initialized}
|
||||
selectedConversationId={chat.currentSessionKey}
|
||||
onNewChat={() => {
|
||||
void chatStore.newSession(selectedAgentId || undefined);
|
||||
void chatStore.newSession(selectedModelId || undefined);
|
||||
}}
|
||||
onSelectConversation={(conversationId) => {
|
||||
chatStore.switchSession(conversationId);
|
||||
@@ -316,23 +316,23 @@ export default function HomePage() {
|
||||
<h2 className="text-base font-semibold text-[#171717] dark:text-gray-100">智能对话</h2>
|
||||
<div className="mt-1 text-xs text-[#99A0AE] dark:text-gray-500">
|
||||
网关状态:{chat.gatewayStatus === 'connected' ? '已连接' : chat.gatewayStatus === 'reconnecting' ? '重连中' : '未连接'}
|
||||
{currentAgent ? ` · 当前代理:${currentAgent.name}` : ''}
|
||||
{currentModel ? ` · 当前模型:${currentModel.name}` : ''}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<label className="flex items-center gap-2 text-xs text-[#525866] dark:text-gray-300">
|
||||
<span>代理</span>
|
||||
<span>模型</span>
|
||||
<select
|
||||
className="rounded-full border border-[#E5E8EE] bg-white px-3 py-1.5 text-xs text-[#525866] outline-none transition-colors hover:border-[#2B7FFF] dark:border-[#2a2a2d] dark:bg-[#232327] dark:text-gray-300"
|
||||
disabled={agentsState.loading || agentsState.agents.length === 0}
|
||||
value={selectedAgentId}
|
||||
disabled={modelsState.loading || modelsState.models.length === 0}
|
||||
value={selectedModelId}
|
||||
onChange={(event) => {
|
||||
chatStore.selectAgent(event.target.value);
|
||||
}}
|
||||
>
|
||||
{agentsState.agents.map((agent) => (
|
||||
<option key={agent.id} value={agent.id}>
|
||||
{agent.name}
|
||||
{modelsState.models.map((model) => (
|
||||
<option key={model.id} value={model.id}>
|
||||
{model.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
@@ -341,7 +341,7 @@ export default function HomePage() {
|
||||
type="button"
|
||||
className="rounded-full border border-[#E5E8EE] px-3 py-1.5 text-xs text-[#525866] transition-colors hover:border-[#2B7FFF] hover:text-[#2B7FFF] dark:border-[#2a2a2d] dark:text-gray-300"
|
||||
onClick={() => {
|
||||
void agentsStore.load();
|
||||
void modelsStore.load();
|
||||
void chatStore.loadSessions();
|
||||
void chatStore.loadHistory();
|
||||
}}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { useEffect } from 'react';
|
||||
import { agentsStore, useAgentsStore } from '../../../stores';
|
||||
import { modelsStore, useModelsStore } from '../../../stores';
|
||||
|
||||
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 AgentsSection() {
|
||||
const agentState = useAgentsStore();
|
||||
export default function ModelsSection() {
|
||||
const modelsState = useModelsStore();
|
||||
|
||||
useEffect(() => {
|
||||
void agentsStore.init();
|
||||
void modelsStore.init();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
@@ -18,43 +18,43 @@ export default function AgentsSection() {
|
||||
<div className="flex items-end justify-between gap-4">
|
||||
<div>
|
||||
<h3 className="text-[18px] font-semibold leading-[24px] text-[#171717] dark:text-gray-100">
|
||||
Agents Snapshot
|
||||
Models Snapshot
|
||||
</h3>
|
||||
<p className="mt-1 text-[13px] leading-[20px] text-[#99A0AE] dark:text-gray-500">
|
||||
当前本地 `agents` 契约与 `mainSessionKey` 映射。
|
||||
当前本地 `/api/models` 快照与 `mainSessionKey` 映射。
|
||||
</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 agentsStore.load();
|
||||
void modelsStore.load();
|
||||
}}
|
||||
>
|
||||
刷新 Agents
|
||||
刷新 Models
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{agentState.error ? (
|
||||
{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]">
|
||||
{agentState.error}
|
||||
{modelsState.error}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
{agentState.agents.map((agent) => (
|
||||
{modelsState.models.map((model) => (
|
||||
<article
|
||||
key={agent.id}
|
||||
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">
|
||||
{agent.name}
|
||||
{model.name}
|
||||
</div>
|
||||
<div className="mt-1 text-[12px] text-[#99A0AE] dark:text-gray-500">{agent.id}</div>
|
||||
<div className="mt-1 text-[12px] text-[#99A0AE] dark:text-gray-500">{model.id}</div>
|
||||
</div>
|
||||
{agent.isDefault ? (
|
||||
{model.isDefault ? (
|
||||
<span className="rounded-full bg-[#EFF6FF] px-2.5 py-1 text-[11px] font-medium text-[#2B7FFF] dark:bg-[#1d2633]">
|
||||
默认
|
||||
</span>
|
||||
@@ -62,20 +62,20 @@ export default function AgentsSection() {
|
||||
</div>
|
||||
|
||||
<div className="mt-4 flex flex-wrap gap-2">
|
||||
<span className={CHIP_CLASS_NAME}>Provider: {agent.providerAccountId || '--'}</span>
|
||||
<span className={CHIP_CLASS_NAME}>Model: {agent.modelDisplay || '--'}</span>
|
||||
<span className={CHIP_CLASS_NAME}>Provider: {model.providerAccountId || '--'}</span>
|
||||
<span className={CHIP_CLASS_NAME}>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">mainSessionKey</div>
|
||||
<div className="mt-1 break-all">{agent.mainSessionKey}</div>
|
||||
<div className="mt-1 break-all">{model.mainSessionKey}</div>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
|
||||
{!agentState.loading && agentState.agents.length === 0 ? (
|
||||
{!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">
|
||||
当前还没有可用 agent。先在下方配置 provider 账号后,这里会自动生成可路由的 agent snapshot。
|
||||
当前还没有可用模型。先在下方配置 provider 账号后,这里会自动生成可路由的 model snapshot。
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
@@ -1,8 +1,8 @@
|
||||
import AgentsSection from './components/AgentsSection';
|
||||
import ModelsSection from './components/ModelsSection';
|
||||
import ProvidersSection from './components/ProvidersSection';
|
||||
import UsageHistorySection from './components/UsageHistorySection';
|
||||
|
||||
export default function AgentsPage() {
|
||||
export default function ModelsPage() {
|
||||
return (
|
||||
<section className="h-full w-full min-h-0">
|
||||
<div className="flex h-full w-full min-h-0 flex-col rounded-[16px] bg-white p-[20px] dark:bg-[#1b1b1d]">
|
||||
@@ -18,7 +18,7 @@ export default function AgentsPage() {
|
||||
</div>
|
||||
|
||||
<div className="min-h-0 flex-1 space-y-12 overflow-y-auto pb-10 pr-2">
|
||||
<AgentsSection />
|
||||
<ModelsSection />
|
||||
<ProvidersSection />
|
||||
<UsageHistorySection />
|
||||
</div>
|
||||
Reference in New Issue
Block a user