fix(desktop): session persistence — refresh/login/context/empty-content 4-bug fix
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
1. App.tsx: add restoreSession() call on startup to prevent redirect to login page after refresh (isRestoring guard + BootstrapScreen) 2. CloneManager: call syncAgents() after loadClones() to restore currentAgent and conversation history on app load 3. zclaw-memory: add get_or_create_session() so frontend session UUID is persisted directly — kernel no longer creates mismatched IDs 4. openai.rs: assistant message content must be non-empty for Kimi/Qwen APIs — replace empty content with meaningful placeholders Also includes admin-v2 ModelServices unified page (merge providers + models + API keys into expandable row layout)
This commit is contained in:
@@ -4,20 +4,24 @@
|
||||
|
||||
import { useState } from 'react'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { Card, Row, Col, Select, Spin, Alert, Statistic, Typography } from 'antd'
|
||||
import { ColumnWidthOutlined, ThunderboltOutlined } from '@ant-design/icons'
|
||||
import { Card, Col, Row, Select, Statistic } from 'antd'
|
||||
import { ThunderboltOutlined, ColumnWidthOutlined } from '@ant-design/icons'
|
||||
import type { ProColumns } from '@ant-design/pro-components'
|
||||
import { ProTable } from '@ant-design/pro-components'
|
||||
import { usageService } from '@/services/usage'
|
||||
import { telemetryService } from '@/services/telemetry'
|
||||
import { PageHeader } from '@/components/PageHeader'
|
||||
import { ErrorState } from '@/components/ErrorState'
|
||||
import type { DailyUsageStat, ModelUsageStat } from '@/types'
|
||||
|
||||
const { Title } = Typography
|
||||
|
||||
export default function Usage() {
|
||||
const [days, setDays] = useState(30)
|
||||
|
||||
const { data: dailyData, isLoading: dailyLoading, error: dailyError } = useQuery({
|
||||
const {
|
||||
data: dailyData,
|
||||
isLoading: dailyLoading,
|
||||
error: dailyError,
|
||||
refetch,
|
||||
} = useQuery({
|
||||
queryKey: ['usage-daily', days],
|
||||
queryFn: ({ signal }) => telemetryService.dailyStats({ days }, signal),
|
||||
})
|
||||
@@ -28,7 +32,12 @@ export default function Usage() {
|
||||
})
|
||||
|
||||
if (dailyError) {
|
||||
return <Alert type="error" message="加载用量数据失败" description={(dailyError as Error).message} showIcon />
|
||||
return (
|
||||
<>
|
||||
<PageHeader title="用量统计" description="查看模型使用情况和 Token 消耗" />
|
||||
<ErrorState message={(dailyError as Error).message} onRetry={() => refetch()} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const totalRequests = dailyData?.reduce((s, d) => s + d.request_count, 0) ?? 0
|
||||
@@ -36,22 +45,52 @@ export default function Usage() {
|
||||
|
||||
const dailyColumns: ProColumns<DailyUsageStat>[] = [
|
||||
{ title: '日期', dataIndex: 'day', width: 120 },
|
||||
{ title: '请求数', dataIndex: 'request_count', width: 100, render: (_, r) => r.request_count.toLocaleString() },
|
||||
{ title: '输入 Token', dataIndex: 'input_tokens', width: 120, render: (_, r) => r.input_tokens.toLocaleString() },
|
||||
{ title: '输出 Token', dataIndex: 'output_tokens', width: 120, render: (_, r) => r.output_tokens.toLocaleString() },
|
||||
{
|
||||
title: '请求数',
|
||||
dataIndex: 'request_count',
|
||||
width: 100,
|
||||
render: (_, r) => r.request_count.toLocaleString(),
|
||||
},
|
||||
{
|
||||
title: '输入 Token',
|
||||
dataIndex: 'input_tokens',
|
||||
width: 120,
|
||||
render: (_, r) => r.input_tokens.toLocaleString(),
|
||||
},
|
||||
{
|
||||
title: '输出 Token',
|
||||
dataIndex: 'output_tokens',
|
||||
width: 120,
|
||||
render: (_, r) => r.output_tokens.toLocaleString(),
|
||||
},
|
||||
{ title: '设备数', dataIndex: 'unique_devices', width: 80 },
|
||||
]
|
||||
|
||||
const modelColumns: ProColumns<ModelUsageStat>[] = [
|
||||
{ title: '模型', dataIndex: 'model_id', width: 200 },
|
||||
{ title: '请求数', dataIndex: 'request_count', width: 100, render: (_, r) => r.request_count.toLocaleString() },
|
||||
{ title: '输入 Token', dataIndex: 'input_tokens', width: 120, render: (_, r) => r.input_tokens.toLocaleString() },
|
||||
{ title: '输出 Token', dataIndex: 'output_tokens', width: 120, render: (_, r) => r.output_tokens.toLocaleString() },
|
||||
{
|
||||
title: '请求数',
|
||||
dataIndex: 'request_count',
|
||||
width: 100,
|
||||
render: (_, r) => r.request_count.toLocaleString(),
|
||||
},
|
||||
{
|
||||
title: '输入 Token',
|
||||
dataIndex: 'input_tokens',
|
||||
width: 120,
|
||||
render: (_, r) => r.input_tokens.toLocaleString(),
|
||||
},
|
||||
{
|
||||
title: '输出 Token',
|
||||
dataIndex: 'output_tokens',
|
||||
width: 120,
|
||||
render: (_, r) => r.output_tokens.toLocaleString(),
|
||||
},
|
||||
{
|
||||
title: '平均延迟',
|
||||
dataIndex: 'avg_latency_ms',
|
||||
width: 100,
|
||||
render: (_, r) => r.avg_latency_ms ? `${Math.round(r.avg_latency_ms)}ms` : '-',
|
||||
render: (_, r) => (r.avg_latency_ms ? `${Math.round(r.avg_latency_ms)}ms` : '-'),
|
||||
},
|
||||
{
|
||||
title: '成功率',
|
||||
@@ -63,34 +102,66 @@ export default function Usage() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24 }}>
|
||||
<Title level={4} style={{ margin: 0 }}>用量统计</Title>
|
||||
<Select
|
||||
value={days}
|
||||
onChange={setDays}
|
||||
options={[
|
||||
{ value: 7, label: '最近 7 天' },
|
||||
{ value: 30, label: '最近 30 天' },
|
||||
{ value: 90, label: '最近 90 天' },
|
||||
]}
|
||||
style={{ width: 140 }}
|
||||
/>
|
||||
</div>
|
||||
<PageHeader
|
||||
title="用量统计"
|
||||
description="查看模型使用情况和 Token 消耗"
|
||||
actions={
|
||||
<Select
|
||||
value={days}
|
||||
onChange={setDays}
|
||||
options={[
|
||||
{ value: 7, label: '最近 7 天' },
|
||||
{ value: 30, label: '最近 30 天' },
|
||||
{ value: 90, label: '最近 90 天' },
|
||||
]}
|
||||
className="w-36"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<Row gutter={[16, 16]} style={{ marginBottom: 24 }}>
|
||||
<Col span={12}>
|
||||
<Card>
|
||||
<Statistic title="总请求数" value={totalRequests} prefix={<ThunderboltOutlined />} />
|
||||
{/* Summary Cards */}
|
||||
<Row gutter={[16, 16]} className="mb-6">
|
||||
<Col xs={24} sm={12}>
|
||||
<Card className="hover:shadow-md transition-shadow duration-200">
|
||||
<Statistic
|
||||
title={
|
||||
<span className="text-neutral-500 dark:text-neutral-400 text-xs font-medium uppercase tracking-wide">
|
||||
总请求数
|
||||
</span>
|
||||
}
|
||||
value={totalRequests}
|
||||
prefix={<ThunderboltOutlined style={{ color: '#863bff' }} />}
|
||||
valueStyle={{ fontWeight: 600, color: '#863bff' }}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Card>
|
||||
<Statistic title="总 Token 数" value={totalTokens} prefix={<ColumnWidthOutlined />} />
|
||||
<Col xs={24} sm={12}>
|
||||
<Card className="hover:shadow-md transition-shadow duration-200">
|
||||
<Statistic
|
||||
title={
|
||||
<span className="text-neutral-500 dark:text-neutral-400 text-xs font-medium uppercase tracking-wide">
|
||||
总 Token 数
|
||||
</span>
|
||||
}
|
||||
value={totalTokens}
|
||||
prefix={<ColumnWidthOutlined style={{ color: '#47bfff' }} />}
|
||||
valueStyle={{ fontWeight: 600, color: '#47bfff' }}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Card title="每日统计" style={{ marginBottom: 24 }} size="small">
|
||||
{/* Daily Stats */}
|
||||
<Card
|
||||
title={
|
||||
<span className="text-sm font-semibold text-neutral-700 dark:text-neutral-300">
|
||||
每日统计
|
||||
</span>
|
||||
}
|
||||
className="mb-6"
|
||||
size="small"
|
||||
styles={{ body: { padding: 0 } }}
|
||||
>
|
||||
<ProTable<DailyUsageStat>
|
||||
columns={dailyColumns}
|
||||
dataSource={dailyData ?? []}
|
||||
@@ -103,7 +174,16 @@ export default function Usage() {
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<Card title="按模型统计" size="small">
|
||||
{/* Model Stats */}
|
||||
<Card
|
||||
title={
|
||||
<span className="text-sm font-semibold text-neutral-700 dark:text-neutral-300">
|
||||
按模型统计
|
||||
</span>
|
||||
}
|
||||
size="small"
|
||||
styles={{ body: { padding: 0 } }}
|
||||
>
|
||||
<ProTable<ModelUsageStat>
|
||||
columns={modelColumns}
|
||||
dataSource={modelData ?? []}
|
||||
|
||||
Reference in New Issue
Block a user