mirror of
				https://github.com/songquanpeng/one-api.git
				synced 2025-11-04 07:43:41 +08:00 
			
		
		
		
	feat: basic overview is done
This commit is contained in:
		@@ -13,6 +13,7 @@
 | 
			
		||||
    "react-scripts": "5.0.1",
 | 
			
		||||
    "react-toastify": "^9.0.8",
 | 
			
		||||
    "react-turnstile": "^1.0.5",
 | 
			
		||||
    "recharts": "^2.15.1",
 | 
			
		||||
    "semantic-ui-css": "^2.5.0",
 | 
			
		||||
    "semantic-ui-react": "^2.1.3"
 | 
			
		||||
  },
 | 
			
		||||
 
 | 
			
		||||
@@ -25,6 +25,7 @@ import TopUp from './pages/TopUp';
 | 
			
		||||
import Log from './pages/Log';
 | 
			
		||||
import Chat from './pages/Chat';
 | 
			
		||||
import LarkOAuth from './components/LarkOAuth';
 | 
			
		||||
import Dashboard from './pages/Dashboard';
 | 
			
		||||
 | 
			
		||||
const Home = lazy(() => import('./pages/Home'));
 | 
			
		||||
const About = lazy(() => import('./pages/About'));
 | 
			
		||||
@@ -261,11 +262,11 @@ function App() {
 | 
			
		||||
      <Route
 | 
			
		||||
        path='/topup'
 | 
			
		||||
        element={
 | 
			
		||||
        <PrivateRoute>
 | 
			
		||||
          <Suspense fallback={<Loading></Loading>}>
 | 
			
		||||
            <TopUp />
 | 
			
		||||
          </Suspense>
 | 
			
		||||
        </PrivateRoute>
 | 
			
		||||
          <PrivateRoute>
 | 
			
		||||
            <Suspense fallback={<Loading></Loading>}>
 | 
			
		||||
              <TopUp />
 | 
			
		||||
            </Suspense>
 | 
			
		||||
          </PrivateRoute>
 | 
			
		||||
        }
 | 
			
		||||
      />
 | 
			
		||||
      <Route
 | 
			
		||||
@@ -292,9 +293,15 @@ function App() {
 | 
			
		||||
          </Suspense>
 | 
			
		||||
        }
 | 
			
		||||
      />
 | 
			
		||||
      <Route path='*' element={
 | 
			
		||||
          <NotFound />
 | 
			
		||||
      } />
 | 
			
		||||
      <Route
 | 
			
		||||
        path='/dashboard'
 | 
			
		||||
        element={
 | 
			
		||||
          <PrivateRoute>
 | 
			
		||||
            <Dashboard />
 | 
			
		||||
          </PrivateRoute>
 | 
			
		||||
        }
 | 
			
		||||
      />
 | 
			
		||||
      <Route path='*' element={<NotFound />} />
 | 
			
		||||
    </Routes>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,8 +2,22 @@ import React, { useContext, useState } from 'react';
 | 
			
		||||
import { Link, useNavigate } from 'react-router-dom';
 | 
			
		||||
import { UserContext } from '../context/User';
 | 
			
		||||
 | 
			
		||||
import { Button, Container, Dropdown, Icon, Menu, Segment } from 'semantic-ui-react';
 | 
			
		||||
import { API, getLogo, getSystemName, isAdmin, isMobile, showSuccess } from '../helpers';
 | 
			
		||||
import {
 | 
			
		||||
  Button,
 | 
			
		||||
  Container,
 | 
			
		||||
  Dropdown,
 | 
			
		||||
  Icon,
 | 
			
		||||
  Menu,
 | 
			
		||||
  Segment,
 | 
			
		||||
} from 'semantic-ui-react';
 | 
			
		||||
import {
 | 
			
		||||
  API,
 | 
			
		||||
  getLogo,
 | 
			
		||||
  getSystemName,
 | 
			
		||||
  isAdmin,
 | 
			
		||||
  isMobile,
 | 
			
		||||
  showSuccess,
 | 
			
		||||
} from '../helpers';
 | 
			
		||||
import '../index.css';
 | 
			
		||||
 | 
			
		||||
// Header Buttons
 | 
			
		||||
@@ -11,58 +25,63 @@ let headerButtons = [
 | 
			
		||||
  {
 | 
			
		||||
    name: '首页',
 | 
			
		||||
    to: '/',
 | 
			
		||||
    icon: 'home'
 | 
			
		||||
    icon: 'home',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    name: '渠道',
 | 
			
		||||
    to: '/channel',
 | 
			
		||||
    icon: 'sitemap',
 | 
			
		||||
    admin: true
 | 
			
		||||
    admin: true,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    name: '令牌',
 | 
			
		||||
    to: '/token',
 | 
			
		||||
    icon: 'key'
 | 
			
		||||
    icon: 'key',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    name: '兑换',
 | 
			
		||||
    to: '/redemption',
 | 
			
		||||
    icon: 'dollar sign',
 | 
			
		||||
    admin: true
 | 
			
		||||
    admin: true,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    name: '充值',
 | 
			
		||||
    to: '/topup',
 | 
			
		||||
    icon: 'cart'
 | 
			
		||||
    icon: 'cart',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    name: '用户',
 | 
			
		||||
    to: '/user',
 | 
			
		||||
    icon: 'user',
 | 
			
		||||
    admin: true
 | 
			
		||||
    admin: true,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    name: '总览',
 | 
			
		||||
    to: '/dashboard',
 | 
			
		||||
    icon: 'chart bar',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    name: '日志',
 | 
			
		||||
    to: '/log',
 | 
			
		||||
    icon: 'book'
 | 
			
		||||
    icon: 'book',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    name: '设置',
 | 
			
		||||
    to: '/setting',
 | 
			
		||||
    icon: 'setting'
 | 
			
		||||
    icon: 'setting',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    name: '关于',
 | 
			
		||||
    to: '/about',
 | 
			
		||||
    icon: 'info circle'
 | 
			
		||||
  }
 | 
			
		||||
    icon: 'info circle',
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
if (localStorage.getItem('chat_link')) {
 | 
			
		||||
  headerButtons.splice(1, 0, {
 | 
			
		||||
    name: '聊天',
 | 
			
		||||
    to: '/chat',
 | 
			
		||||
    icon: 'comments'
 | 
			
		||||
    icon: 'comments',
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -120,21 +139,17 @@ const Header = () => {
 | 
			
		||||
          style={
 | 
			
		||||
            showSidebar
 | 
			
		||||
              ? {
 | 
			
		||||
                borderBottom: 'none',
 | 
			
		||||
                marginBottom: '0',
 | 
			
		||||
                borderTop: 'none',
 | 
			
		||||
                height: '51px'
 | 
			
		||||
              }
 | 
			
		||||
                  borderBottom: 'none',
 | 
			
		||||
                  marginBottom: '0',
 | 
			
		||||
                  borderTop: 'none',
 | 
			
		||||
                  height: '51px',
 | 
			
		||||
                }
 | 
			
		||||
              : { borderTop: 'none', height: '52px' }
 | 
			
		||||
          }
 | 
			
		||||
        >
 | 
			
		||||
          <Container>
 | 
			
		||||
            <Menu.Item as={Link} to='/'>
 | 
			
		||||
              <img
 | 
			
		||||
                src={logo}
 | 
			
		||||
                alt='logo'
 | 
			
		||||
                style={{ marginRight: '0.75em' }}
 | 
			
		||||
              />
 | 
			
		||||
              <img src={logo} alt='logo' style={{ marginRight: '0.75em' }} />
 | 
			
		||||
              <div style={{ fontSize: '20px' }}>
 | 
			
		||||
                <b>{systemName}</b>
 | 
			
		||||
              </div>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										63
									
								
								web/default/src/pages/Dashboard/Dashboard.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								web/default/src/pages/Dashboard/Dashboard.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,63 @@
 | 
			
		||||
.dashboard-container {
 | 
			
		||||
    padding: 20px;
 | 
			
		||||
    background-color: #f7f9fc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.stat-card {
 | 
			
		||||
    background: linear-gradient(135deg, #2185d0 0%, #1678c2 100%) !important;
 | 
			
		||||
    color: white !important;
 | 
			
		||||
    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1) !important;
 | 
			
		||||
    transition: transform 0.2s ease !important;
 | 
			
		||||
    margin-bottom: 1rem !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.stat-card:hover {
 | 
			
		||||
    transform: translateY(-5px);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.stat-card .statistic {
 | 
			
		||||
    color: white !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.charts-grid {
 | 
			
		||||
    margin-top: 1rem !important;
 | 
			
		||||
    margin-bottom: 1rem !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.charts-grid .column {
 | 
			
		||||
    padding: 0.5rem !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.chart-card {
 | 
			
		||||
    margin: 0 !important;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05) !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.chart-container {
 | 
			
		||||
    margin-top: 20px;
 | 
			
		||||
    padding: 10px;
 | 
			
		||||
    background-color: white;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.ui.card > .content > .header {
 | 
			
		||||
    color: #1a1a1a;
 | 
			
		||||
    font-size: 1.2em;
 | 
			
		||||
    margin-bottom: 15px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 优化图表响应式布局 */
 | 
			
		||||
@media (max-width: 768px) {
 | 
			
		||||
    .dashboard-container {
 | 
			
		||||
        padding: 10px;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    .chart-container {
 | 
			
		||||
        padding: 5px;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    .charts-grid .column {
 | 
			
		||||
        padding: 0.25rem !important;
 | 
			
		||||
    }
 | 
			
		||||
} 
 | 
			
		||||
							
								
								
									
										295
									
								
								web/default/src/pages/Dashboard/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										295
									
								
								web/default/src/pages/Dashboard/index.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,295 @@
 | 
			
		||||
import React, { useEffect, useState } from 'react';
 | 
			
		||||
import { Card, Grid, Statistic } from 'semantic-ui-react';
 | 
			
		||||
import {
 | 
			
		||||
  LineChart,
 | 
			
		||||
  Line,
 | 
			
		||||
  XAxis,
 | 
			
		||||
  YAxis,
 | 
			
		||||
  CartesianGrid,
 | 
			
		||||
  Tooltip,
 | 
			
		||||
  ResponsiveContainer,
 | 
			
		||||
  BarChart,
 | 
			
		||||
  Bar,
 | 
			
		||||
  Legend,
 | 
			
		||||
} from 'recharts';
 | 
			
		||||
import axios from 'axios';
 | 
			
		||||
import './Dashboard.css';
 | 
			
		||||
 | 
			
		||||
const Dashboard = () => {
 | 
			
		||||
  const [data, setData] = useState([]);
 | 
			
		||||
  const [summaryData, setSummaryData] = useState({
 | 
			
		||||
    todayRequests: 0,
 | 
			
		||||
    todayQuota: 0,
 | 
			
		||||
    todayTokens: 0,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    fetchDashboardData();
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  const fetchDashboardData = async () => {
 | 
			
		||||
    try {
 | 
			
		||||
      const response = await axios.get('/api/user/dashboard');
 | 
			
		||||
      if (response.data.success) {
 | 
			
		||||
        const dashboardData = response.data.data;
 | 
			
		||||
        setData(dashboardData);
 | 
			
		||||
        calculateSummary(dashboardData);
 | 
			
		||||
      }
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      console.error('Failed to fetch dashboard data:', error);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const calculateSummary = (dashboardData) => {
 | 
			
		||||
    const today = new Date().toISOString().split('T')[0];
 | 
			
		||||
    const todayData = dashboardData.filter((item) => item.Day === today);
 | 
			
		||||
 | 
			
		||||
    const summary = {
 | 
			
		||||
      todayRequests: todayData.reduce(
 | 
			
		||||
        (sum, item) => sum + item.RequestCount,
 | 
			
		||||
        0
 | 
			
		||||
      ),
 | 
			
		||||
      todayQuota:
 | 
			
		||||
        todayData.reduce((sum, item) => sum + item.Quota, 0) / 1000000, // 转换为美元
 | 
			
		||||
      todayTokens: todayData.reduce(
 | 
			
		||||
        (sum, item) => sum + item.PromptTokens + item.CompletionTokens,
 | 
			
		||||
        0
 | 
			
		||||
      ),
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    setSummaryData(summary);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  // 处理数据以供折线图使用,补充缺失的日期
 | 
			
		||||
  const processTimeSeriesData = () => {
 | 
			
		||||
    const dailyData = {};
 | 
			
		||||
 | 
			
		||||
    // 获取日期范围
 | 
			
		||||
    const dates = data.map((item) => item.Day);
 | 
			
		||||
    const minDate = new Date(Math.min(...dates.map((d) => new Date(d))));
 | 
			
		||||
    const maxDate = new Date(Math.max(...dates.map((d) => new Date(d))));
 | 
			
		||||
 | 
			
		||||
    // 生成所有日期
 | 
			
		||||
    for (let d = new Date(minDate); d <= maxDate; d.setDate(d.getDate() + 1)) {
 | 
			
		||||
      const dateStr = d.toISOString().split('T')[0];
 | 
			
		||||
      dailyData[dateStr] = {
 | 
			
		||||
        date: dateStr,
 | 
			
		||||
        requests: 0,
 | 
			
		||||
        quota: 0,
 | 
			
		||||
        tokens: 0,
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 填充实际数据
 | 
			
		||||
    data.forEach((item) => {
 | 
			
		||||
      dailyData[item.Day].requests += item.RequestCount;
 | 
			
		||||
      dailyData[item.Day].quota += item.Quota / 1000000;
 | 
			
		||||
      dailyData[item.Day].tokens += item.PromptTokens + item.CompletionTokens;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return Object.values(dailyData).sort((a, b) =>
 | 
			
		||||
      a.date.localeCompare(b.date)
 | 
			
		||||
    );
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  // 处理数据以供堆叠柱状图使用
 | 
			
		||||
  const processModelData = () => {
 | 
			
		||||
    const timeData = {};
 | 
			
		||||
 | 
			
		||||
    // 获取日期范围
 | 
			
		||||
    const dates = data.map((item) => item.Day);
 | 
			
		||||
    const minDate = new Date(Math.min(...dates.map((d) => new Date(d))));
 | 
			
		||||
    const maxDate = new Date(Math.max(...dates.map((d) => new Date(d))));
 | 
			
		||||
 | 
			
		||||
    // 生成所有日期
 | 
			
		||||
    for (let d = new Date(minDate); d <= maxDate; d.setDate(d.getDate() + 1)) {
 | 
			
		||||
      const dateStr = d.toISOString().split('T')[0];
 | 
			
		||||
      timeData[dateStr] = {
 | 
			
		||||
        date: dateStr,
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      // 初始化所有模型的数据为0
 | 
			
		||||
      const models = [...new Set(data.map((item) => item.ModelName))];
 | 
			
		||||
      models.forEach((model) => {
 | 
			
		||||
        timeData[dateStr][model] = 0;
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 填充实际数据
 | 
			
		||||
    data.forEach((item) => {
 | 
			
		||||
      timeData[item.Day][item.ModelName] =
 | 
			
		||||
        item.PromptTokens + item.CompletionTokens;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return Object.values(timeData).sort((a, b) => a.date.localeCompare(b.date));
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  // 获取所有唯一的模型名称
 | 
			
		||||
  const getUniqueModels = () => {
 | 
			
		||||
    return [...new Set(data.map((item) => item.ModelName))];
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const timeSeriesData = processTimeSeriesData();
 | 
			
		||||
  const modelData = processModelData();
 | 
			
		||||
  const models = getUniqueModels();
 | 
			
		||||
 | 
			
		||||
  // 生成随机颜色
 | 
			
		||||
  const getRandomColor = (index) => {
 | 
			
		||||
    const colors = [
 | 
			
		||||
      '#1f77b4',
 | 
			
		||||
      '#ff7f0e',
 | 
			
		||||
      '#2ca02c',
 | 
			
		||||
      '#d62728',
 | 
			
		||||
      '#9467bd',
 | 
			
		||||
      '#8c564b',
 | 
			
		||||
      '#e377c2',
 | 
			
		||||
      '#7f7f7f',
 | 
			
		||||
      '#bcbd22',
 | 
			
		||||
      '#17becf',
 | 
			
		||||
    ];
 | 
			
		||||
    return colors[index % colors.length];
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className='dashboard-container'>
 | 
			
		||||
      <Grid columns={3} stackable>
 | 
			
		||||
        <Grid.Column>
 | 
			
		||||
          <Card fluid className='stat-card'>
 | 
			
		||||
            <Card.Content>
 | 
			
		||||
              <Statistic>
 | 
			
		||||
                <Statistic.Value>{summaryData.todayRequests}</Statistic.Value>
 | 
			
		||||
                <Statistic.Label>今日请求量</Statistic.Label>
 | 
			
		||||
              </Statistic>
 | 
			
		||||
            </Card.Content>
 | 
			
		||||
          </Card>
 | 
			
		||||
        </Grid.Column>
 | 
			
		||||
        <Grid.Column>
 | 
			
		||||
          <Card fluid className='stat-card'>
 | 
			
		||||
            <Card.Content>
 | 
			
		||||
              <Statistic>
 | 
			
		||||
                <Statistic.Value>
 | 
			
		||||
                  ${summaryData.todayQuota.toFixed(3)}
 | 
			
		||||
                </Statistic.Value>
 | 
			
		||||
                <Statistic.Label>今日消费</Statistic.Label>
 | 
			
		||||
              </Statistic>
 | 
			
		||||
            </Card.Content>
 | 
			
		||||
          </Card>
 | 
			
		||||
        </Grid.Column>
 | 
			
		||||
        <Grid.Column>
 | 
			
		||||
          <Card fluid className='stat-card'>
 | 
			
		||||
            <Card.Content>
 | 
			
		||||
              <Statistic>
 | 
			
		||||
                <Statistic.Value>{summaryData.todayTokens}</Statistic.Value>
 | 
			
		||||
                <Statistic.Label>今日 token</Statistic.Label>
 | 
			
		||||
              </Statistic>
 | 
			
		||||
            </Card.Content>
 | 
			
		||||
          </Card>
 | 
			
		||||
        </Grid.Column>
 | 
			
		||||
      </Grid>
 | 
			
		||||
 | 
			
		||||
      {/* 三个并排的折线图 */}
 | 
			
		||||
      <Grid columns={3} stackable className='charts-grid'>
 | 
			
		||||
        <Grid.Column>
 | 
			
		||||
          <Card fluid className='chart-card'>
 | 
			
		||||
            <Card.Content>
 | 
			
		||||
              <Card.Header>今日请求量</Card.Header>
 | 
			
		||||
              <div className='chart-container'>
 | 
			
		||||
                <ResponsiveContainer width='100%' height={120}>
 | 
			
		||||
                  <LineChart data={timeSeriesData}>
 | 
			
		||||
                    <CartesianGrid strokeDasharray='3 3' />
 | 
			
		||||
                    <XAxis dataKey='date' />
 | 
			
		||||
                    <YAxis />
 | 
			
		||||
                    <Tooltip />
 | 
			
		||||
                    <Line
 | 
			
		||||
                      type='monotone'
 | 
			
		||||
                      dataKey='requests'
 | 
			
		||||
                      stroke='#2185d0'
 | 
			
		||||
                      dot={false}
 | 
			
		||||
                    />
 | 
			
		||||
                  </LineChart>
 | 
			
		||||
                </ResponsiveContainer>
 | 
			
		||||
              </div>
 | 
			
		||||
            </Card.Content>
 | 
			
		||||
          </Card>
 | 
			
		||||
        </Grid.Column>
 | 
			
		||||
 | 
			
		||||
        <Grid.Column>
 | 
			
		||||
          <Card fluid className='chart-card'>
 | 
			
		||||
            <Card.Content>
 | 
			
		||||
              <Card.Header>今日消费</Card.Header>
 | 
			
		||||
              <div className='chart-container'>
 | 
			
		||||
                <ResponsiveContainer width='100%' height={120}>
 | 
			
		||||
                  <LineChart data={timeSeriesData}>
 | 
			
		||||
                    <CartesianGrid strokeDasharray='3 3' />
 | 
			
		||||
                    <XAxis dataKey='date' />
 | 
			
		||||
                    <YAxis />
 | 
			
		||||
                    <Tooltip />
 | 
			
		||||
                    <Line
 | 
			
		||||
                      type='monotone'
 | 
			
		||||
                      dataKey='quota'
 | 
			
		||||
                      stroke='#21ba45'
 | 
			
		||||
                      dot={false}
 | 
			
		||||
                    />
 | 
			
		||||
                  </LineChart>
 | 
			
		||||
                </ResponsiveContainer>
 | 
			
		||||
              </div>
 | 
			
		||||
            </Card.Content>
 | 
			
		||||
          </Card>
 | 
			
		||||
        </Grid.Column>
 | 
			
		||||
 | 
			
		||||
        <Grid.Column>
 | 
			
		||||
          <Card fluid className='chart-card'>
 | 
			
		||||
            <Card.Content>
 | 
			
		||||
              <Card.Header>今日 token</Card.Header>
 | 
			
		||||
              <div className='chart-container'>
 | 
			
		||||
                <ResponsiveContainer width='100%' height={120}>
 | 
			
		||||
                  <LineChart data={timeSeriesData}>
 | 
			
		||||
                    <CartesianGrid strokeDasharray='3 3' />
 | 
			
		||||
                    <XAxis dataKey='date' />
 | 
			
		||||
                    <YAxis />
 | 
			
		||||
                    <Tooltip />
 | 
			
		||||
                    <Line
 | 
			
		||||
                      type='monotone'
 | 
			
		||||
                      dataKey='tokens'
 | 
			
		||||
                      stroke='#f2711c'
 | 
			
		||||
                      dot={false}
 | 
			
		||||
                    />
 | 
			
		||||
                  </LineChart>
 | 
			
		||||
                </ResponsiveContainer>
 | 
			
		||||
              </div>
 | 
			
		||||
            </Card.Content>
 | 
			
		||||
          </Card>
 | 
			
		||||
        </Grid.Column>
 | 
			
		||||
      </Grid>
 | 
			
		||||
 | 
			
		||||
      {/* 模型使用统计 */}
 | 
			
		||||
      <Card fluid className='chart-card'>
 | 
			
		||||
        <Card.Content>
 | 
			
		||||
          <Card.Header>统计</Card.Header>
 | 
			
		||||
          <div className='chart-container'>
 | 
			
		||||
            <ResponsiveContainer width='100%' height={300}>
 | 
			
		||||
              <BarChart data={modelData}>
 | 
			
		||||
                <CartesianGrid strokeDasharray='3 3' />
 | 
			
		||||
                <XAxis dataKey='date' />
 | 
			
		||||
                <YAxis />
 | 
			
		||||
                <Tooltip />
 | 
			
		||||
                <Legend />
 | 
			
		||||
                {models.map((model, index) => (
 | 
			
		||||
                  <Bar
 | 
			
		||||
                    key={model}
 | 
			
		||||
                    dataKey={model}
 | 
			
		||||
                    stackId='a'
 | 
			
		||||
                    fill={getRandomColor(index)}
 | 
			
		||||
                    name={model}
 | 
			
		||||
                  />
 | 
			
		||||
                ))}
 | 
			
		||||
              </BarChart>
 | 
			
		||||
            </ResponsiveContainer>
 | 
			
		||||
          </div>
 | 
			
		||||
        </Card.Content>
 | 
			
		||||
      </Card>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default Dashboard;
 | 
			
		||||
		Reference in New Issue
	
	Block a user