chore(settings): 删除用量统计和积分详情页面 — 与订阅计费重复
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
UsageStats 和 Credits 功能已被 PricingPage (订阅与计费) 覆盖, 移除冗余页面简化设置导航。
This commit is contained in:
@@ -1,53 +0,0 @@
|
|||||||
import { useState } from 'react';
|
|
||||||
|
|
||||||
export function Credits() {
|
|
||||||
const [filter, setFilter] = useState<'all' | 'consume' | 'earn'>('all');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="max-w-3xl">
|
|
||||||
<div className="flex justify-between items-center mb-6">
|
|
||||||
<h1 className="text-xl font-bold text-gray-900">积分</h1>
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<button className="text-xs text-gray-500 hover:text-gray-700 px-3 py-1.5 border border-gray-200 rounded-lg transition-colors">
|
|
||||||
刷新
|
|
||||||
</button>
|
|
||||||
<button className="text-xs text-white bg-orange-500 hover:bg-orange-600 px-3 py-1.5 rounded-lg transition-colors">
|
|
||||||
去充值
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="text-center mb-8 py-12">
|
|
||||||
<div className="text-xs text-gray-500 mb-1">总积分</div>
|
|
||||||
<div className="text-3xl font-bold text-gray-900">--</div>
|
|
||||||
<div className="text-xs text-gray-400 mt-2">积分系统开发中</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="p-1 mb-6 flex rounded-lg bg-gray-50 border border-gray-100 shadow-sm">
|
|
||||||
<button
|
|
||||||
onClick={() => setFilter('all')}
|
|
||||||
className={`flex-1 py-2 rounded-md text-xs transition-colors ${filter === 'all' ? 'bg-white shadow-sm font-medium text-gray-900' : 'text-gray-500 hover:text-gray-700'}`}
|
|
||||||
>
|
|
||||||
全部
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => setFilter('consume')}
|
|
||||||
className={`flex-1 py-2 rounded-md text-xs transition-colors ${filter === 'consume' ? 'bg-white shadow-sm font-medium text-gray-900' : 'text-gray-500 hover:text-gray-700'}`}
|
|
||||||
>
|
|
||||||
消耗
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => setFilter('earn')}
|
|
||||||
className={`flex-1 py-2 rounded-md text-xs transition-colors ${filter === 'earn' ? 'bg-white shadow-sm font-medium text-gray-900' : 'text-gray-500 hover:text-gray-700'}`}
|
|
||||||
>
|
|
||||||
获得
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="bg-white rounded-xl border border-gray-200 p-8 text-center">
|
|
||||||
<div className="text-sm text-gray-400">暂无积分记录</div>
|
|
||||||
<div className="text-xs text-gray-300 mt-1">连接后端服务后即可查看积分使用记录</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -2,14 +2,12 @@ import { useState } from 'react';
|
|||||||
import { useSecurityStore } from '../../store/securityStore';
|
import { useSecurityStore } from '../../store/securityStore';
|
||||||
import {
|
import {
|
||||||
Settings as SettingsIcon,
|
Settings as SettingsIcon,
|
||||||
BarChart3,
|
|
||||||
Puzzle,
|
Puzzle,
|
||||||
MessageSquare,
|
MessageSquare,
|
||||||
FolderOpen,
|
FolderOpen,
|
||||||
Shield,
|
Shield,
|
||||||
Info,
|
Info,
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
Coins,
|
|
||||||
Cpu,
|
Cpu,
|
||||||
Zap,
|
Zap,
|
||||||
HelpCircle,
|
HelpCircle,
|
||||||
@@ -24,7 +22,6 @@ import {
|
|||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { silentErrorHandler } from '../../lib/error-utils';
|
import { silentErrorHandler } from '../../lib/error-utils';
|
||||||
import { General } from './General';
|
import { General } from './General';
|
||||||
import { UsageStats } from './UsageStats';
|
|
||||||
import { ModelsAPI } from './ModelsAPI';
|
import { ModelsAPI } from './ModelsAPI';
|
||||||
import { MCPServices } from './MCPServices';
|
import { MCPServices } from './MCPServices';
|
||||||
import { Skills } from './Skills';
|
import { Skills } from './Skills';
|
||||||
@@ -32,7 +29,6 @@ import { IMChannels } from './IMChannels';
|
|||||||
import { Workspace } from './Workspace';
|
import { Workspace } from './Workspace';
|
||||||
import { Privacy } from './Privacy';
|
import { Privacy } from './Privacy';
|
||||||
import { About } from './About';
|
import { About } from './About';
|
||||||
import { Credits } from './Credits';
|
|
||||||
import { AuditLogsPanel } from '../AuditLogsPanel';
|
import { AuditLogsPanel } from '../AuditLogsPanel';
|
||||||
import { SecurityStatus } from '../SecurityStatus';
|
import { SecurityStatus } from '../SecurityStatus';
|
||||||
import { SecurityLayersPanel } from '../SecurityLayersPanel';
|
import { SecurityLayersPanel } from '../SecurityLayersPanel';
|
||||||
@@ -51,8 +47,6 @@ interface SettingsLayoutProps {
|
|||||||
|
|
||||||
type SettingsPage =
|
type SettingsPage =
|
||||||
| 'general'
|
| 'general'
|
||||||
| 'usage'
|
|
||||||
| 'credits'
|
|
||||||
| 'models'
|
| 'models'
|
||||||
| 'mcp'
|
| 'mcp'
|
||||||
| 'skills'
|
| 'skills'
|
||||||
@@ -74,8 +68,6 @@ type SettingsPage =
|
|||||||
const menuItems: { id: SettingsPage; label: string; icon: React.ReactNode; group?: 'advanced' }[] = [
|
const menuItems: { id: SettingsPage; label: string; icon: React.ReactNode; group?: 'advanced' }[] = [
|
||||||
// --- Core settings ---
|
// --- Core settings ---
|
||||||
{ id: 'general', label: '通用', icon: <SettingsIcon className="w-4 h-4" /> },
|
{ id: 'general', label: '通用', icon: <SettingsIcon className="w-4 h-4" /> },
|
||||||
{ id: 'usage', label: '用量统计', icon: <BarChart3 className="w-4 h-4" /> },
|
|
||||||
{ id: 'credits', label: '积分详情', icon: <Coins className="w-4 h-4" /> },
|
|
||||||
{ id: 'models', label: '模型与 API', icon: <Cpu className="w-4 h-4" /> },
|
{ id: 'models', label: '模型与 API', icon: <Cpu className="w-4 h-4" /> },
|
||||||
{ id: 'mcp', label: 'MCP 服务', icon: <Puzzle className="w-4 h-4" /> },
|
{ id: 'mcp', label: 'MCP 服务', icon: <Puzzle className="w-4 h-4" /> },
|
||||||
{ id: 'im', label: 'IM 频道', icon: <MessageSquare className="w-4 h-4" /> },
|
{ id: 'im', label: 'IM 频道', icon: <MessageSquare className="w-4 h-4" /> },
|
||||||
@@ -105,8 +97,6 @@ export function SettingsLayout({ onBack }: SettingsLayoutProps) {
|
|||||||
const renderPage = () => {
|
const renderPage = () => {
|
||||||
switch (activePage) {
|
switch (activePage) {
|
||||||
case 'general': return <General />;
|
case 'general': return <General />;
|
||||||
case 'usage': return <UsageStats />;
|
|
||||||
case 'credits': return <Credits />;
|
|
||||||
case 'models': return <ModelsAPI />;
|
case 'models': return <ModelsAPI />;
|
||||||
case 'mcp': return <MCPServices />;
|
case 'mcp': return <MCPServices />;
|
||||||
case 'skills': return <Skills />;
|
case 'skills': return <Skills />;
|
||||||
|
|||||||
@@ -1,177 +0,0 @@
|
|||||||
import { useEffect, useState } from 'react';
|
|
||||||
import { useAgentStore } from '../../store/agentStore';
|
|
||||||
import { BarChart3, TrendingUp, Clock, Zap } from 'lucide-react';
|
|
||||||
|
|
||||||
export function UsageStats() {
|
|
||||||
const usageStats = useAgentStore((s) => s.usageStats);
|
|
||||||
const loadUsageStats = useAgentStore((s) => s.loadUsageStats);
|
|
||||||
const [timeRange, setTimeRange] = useState<'7d' | '30d' | 'all'>('7d');
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
loadUsageStats();
|
|
||||||
}, [loadUsageStats]);
|
|
||||||
|
|
||||||
const stats = usageStats || { totalSessions: 0, totalMessages: 0, totalTokens: 0, byModel: {} };
|
|
||||||
const models = Object.entries(stats.byModel || {});
|
|
||||||
|
|
||||||
const formatTokens = (n: number) => {
|
|
||||||
if (n >= 1_000_000) return `~${(n / 1_000_000).toFixed(1)} M`;
|
|
||||||
if (n >= 1_000) return `~${(n / 1_000).toFixed(1)} k`;
|
|
||||||
return `${n}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 计算总输入和输出 Token
|
|
||||||
const totalInputTokens = models.reduce((sum, [_, data]) => sum + data.inputTokens, 0);
|
|
||||||
const totalOutputTokens = models.reduce((sum, [_, data]) => sum + data.outputTokens, 0);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="max-w-3xl">
|
|
||||||
<div className="flex justify-between items-center mb-6">
|
|
||||||
<h1 className="text-xl font-bold text-gray-900">用量统计</h1>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<div className="flex items-center bg-gray-100 rounded-lg p-0.5">
|
|
||||||
{(['7d', '30d', 'all'] as const).map((range) => (
|
|
||||||
<button
|
|
||||||
key={range}
|
|
||||||
onClick={() => setTimeRange(range)}
|
|
||||||
className={`px-3 py-1 text-xs rounded-md transition-colors ${
|
|
||||||
timeRange === range
|
|
||||||
? 'bg-white text-gray-900 shadow-sm'
|
|
||||||
: 'text-gray-500 hover:text-gray-700'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{range === '7d' ? '近 7 天' : range === '30d' ? '近 30 天' : '全部'}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
onClick={() => loadUsageStats()}
|
|
||||||
className="text-xs text-gray-500 hover:text-gray-700 px-3 py-1.5 border border-gray-200 rounded-lg transition-colors"
|
|
||||||
>
|
|
||||||
刷新
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="text-xs text-gray-500 mb-4">本设备所有已保存对话的使用统计。</div>
|
|
||||||
|
|
||||||
{/* 主要统计卡片 */}
|
|
||||||
<div className="grid grid-cols-4 gap-4 mb-8">
|
|
||||||
<StatCard
|
|
||||||
icon={BarChart3}
|
|
||||||
label="会话数"
|
|
||||||
value={stats.totalSessions}
|
|
||||||
color="text-blue-500"
|
|
||||||
/>
|
|
||||||
<StatCard
|
|
||||||
icon={Zap}
|
|
||||||
label="消息数"
|
|
||||||
value={stats.totalMessages}
|
|
||||||
color="text-purple-500"
|
|
||||||
/>
|
|
||||||
<StatCard
|
|
||||||
icon={TrendingUp}
|
|
||||||
label="输入 Token"
|
|
||||||
value={formatTokens(totalInputTokens)}
|
|
||||||
color="text-green-500"
|
|
||||||
/>
|
|
||||||
<StatCard
|
|
||||||
icon={Clock}
|
|
||||||
label="输出 Token"
|
|
||||||
value={formatTokens(totalOutputTokens)}
|
|
||||||
color="text-orange-500"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 总 Token 使用量概览 */}
|
|
||||||
<div className="bg-white rounded-xl border border-gray-200 p-5 shadow-sm mb-6">
|
|
||||||
<h3 className="text-sm font-semibold mb-4 text-gray-900">Token 使用概览</h3>
|
|
||||||
{stats.totalTokens === 0 ? (
|
|
||||||
<p className="text-xs text-gray-400">Token 用量将在后续版本中支持</p>
|
|
||||||
) : (
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<div className="flex-1">
|
|
||||||
<div className="flex justify-between text-xs text-gray-500 mb-1">
|
|
||||||
<span>输入</span>
|
|
||||||
<span>输出</span>
|
|
||||||
</div>
|
|
||||||
<div className="h-3 bg-gray-100 rounded-full overflow-hidden flex">
|
|
||||||
<div
|
|
||||||
className="bg-gradient-to-r from-green-400 to-green-500 h-full transition-all"
|
|
||||||
style={{ width: `${(totalInputTokens / Math.max(totalInputTokens + totalOutputTokens, 1)) * 100}%` }}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className="bg-gradient-to-r from-orange-400 to-orange-500 h-full transition-all"
|
|
||||||
style={{ width: `${(totalOutputTokens / Math.max(totalInputTokens + totalOutputTokens, 1)) * 100}%` }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="text-right flex-shrink-0">
|
|
||||||
<div className="text-lg font-bold text-gray-900">{formatTokens(stats.totalTokens)}</div>
|
|
||||||
<div className="text-xs text-gray-500">总计</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 按模型分组 */}
|
|
||||||
<h2 className="text-sm font-semibold mb-4 text-gray-900">按模型</h2>
|
|
||||||
<div className="bg-white rounded-xl border border-gray-200 divide-y divide-gray-100 shadow-sm">
|
|
||||||
{models.length === 0 ? (
|
|
||||||
<div className="p-8 text-center">
|
|
||||||
<div className="w-12 h-12 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-3">
|
|
||||||
<BarChart3 className="w-6 h-6 text-gray-400" />
|
|
||||||
</div>
|
|
||||||
<p className="text-sm text-gray-400">暂无使用数据</p>
|
|
||||||
<p className="text-xs text-gray-300 mt-1">开始对话后将自动记录用量统计</p>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
models.map(([model, data]) => {
|
|
||||||
const total = data.inputTokens + data.outputTokens;
|
|
||||||
const inputPct = (data.inputTokens / Math.max(total, 1)) * 100;
|
|
||||||
const outputPct = (data.outputTokens / Math.max(total, 1)) * 100;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div key={model} className="p-4">
|
|
||||||
<div className="flex justify-between items-center mb-2">
|
|
||||||
<span className="font-medium text-gray-900">{model}</span>
|
|
||||||
<span className="text-xs text-gray-500">{data.messages} 条消息</span>
|
|
||||||
</div>
|
|
||||||
<div className="h-2 bg-gray-100 rounded-full overflow-hidden mb-2 flex">
|
|
||||||
<div className="bg-orange-500 h-full" style={{ width: `${inputPct}%` }} />
|
|
||||||
<div className="bg-orange-200 h-full" style={{ width: `${outputPct}%` }} />
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between text-xs text-gray-500">
|
|
||||||
<span>输入: {formatTokens(data.inputTokens)}</span>
|
|
||||||
<span>输出: {formatTokens(data.outputTokens)}</span>
|
|
||||||
<span>总计: {formatTokens(total)}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function StatCard({
|
|
||||||
icon: Icon,
|
|
||||||
label,
|
|
||||||
value,
|
|
||||||
color,
|
|
||||||
}: {
|
|
||||||
icon: typeof BarChart3;
|
|
||||||
label: string;
|
|
||||||
value: string | number;
|
|
||||||
color: string;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<div className="bg-white rounded-xl border border-gray-200 p-4 shadow-sm">
|
|
||||||
<div className="flex items-center gap-2 mb-2">
|
|
||||||
<Icon className={`w-4 h-4 ${color}`} />
|
|
||||||
<span className="text-xs text-gray-500">{label}</span>
|
|
||||||
</div>
|
|
||||||
<div className="text-2xl font-bold text-gray-900">{value}</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user