| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310 |
- /*
- Copyright (C) 2025 QuantumNous
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
- For commercial licensing, please contact support@quantumnous.com
- */
- import { useState, useEffect, useRef, useCallback, useMemo } from 'react';
- import { useNavigate } from 'react-router-dom';
- import { useTranslation } from 'react-i18next';
- import { API, isAdmin, showError, timestamp2string } from '../../helpers';
- import { getDefaultTime, getInitialTimestamp } from '../../helpers/dashboard';
- import { TIME_OPTIONS } from '../../constants/dashboard.constants';
- import { useIsMobile } from '../common/useIsMobile';
- import { useMinimumLoadingTime } from '../common/useMinimumLoadingTime';
- export const useDashboardData = (userState, userDispatch, statusState) => {
- const { t } = useTranslation();
- const navigate = useNavigate();
- const isMobile = useIsMobile();
- const initialized = useRef(false);
- // ========== 基础状态 ==========
- const [loading, setLoading] = useState(false);
- const [greetingVisible, setGreetingVisible] = useState(false);
- const [searchModalVisible, setSearchModalVisible] = useState(false);
- const showLoading = useMinimumLoadingTime(loading);
- // ========== 输入状态 ==========
- const [inputs, setInputs] = useState({
- username: '',
- token_name: '',
- model_name: '',
- start_timestamp: getInitialTimestamp(),
- end_timestamp: timestamp2string(new Date().getTime() / 1000 + 3600),
- channel: '',
- data_export_default_time: '',
- });
- const [dataExportDefaultTime, setDataExportDefaultTime] = useState(getDefaultTime());
- // ========== 数据状态 ==========
- const [quotaData, setQuotaData] = useState([]);
- const [consumeQuota, setConsumeQuota] = useState(0);
- const [consumeTokens, setConsumeTokens] = useState(0);
- const [times, setTimes] = useState(0);
- const [pieData, setPieData] = useState([{ type: 'null', value: '0' }]);
- const [lineData, setLineData] = useState([]);
- const [modelColors, setModelColors] = useState({});
- // ========== 图表状态 ==========
- const [activeChartTab, setActiveChartTab] = useState('1');
- // ========== 趋势数据 ==========
- const [trendData, setTrendData] = useState({
- balance: [],
- usedQuota: [],
- requestCount: [],
- times: [],
- consumeQuota: [],
- tokens: [],
- rpm: [],
- tpm: []
- });
- // ========== Uptime 数据 ==========
- const [uptimeData, setUptimeData] = useState([]);
- const [uptimeLoading, setUptimeLoading] = useState(false);
- const [activeUptimeTab, setActiveUptimeTab] = useState('');
- // ========== 常量 ==========
- const now = new Date();
- const isAdminUser = isAdmin();
- // ========== Panel enable flags ==========
- const apiInfoEnabled = statusState?.status?.api_info_enabled ?? true;
- const announcementsEnabled = statusState?.status?.announcements_enabled ?? true;
- const faqEnabled = statusState?.status?.faq_enabled ?? true;
- const uptimeEnabled = statusState?.status?.uptime_kuma_enabled ?? true;
- const hasApiInfoPanel = apiInfoEnabled;
- const hasInfoPanels = announcementsEnabled || faqEnabled || uptimeEnabled;
- // ========== Memoized Values ==========
- const timeOptions = useMemo(() => TIME_OPTIONS.map(option => ({
- ...option,
- label: t(option.label)
- })), [t]);
- const performanceMetrics = useMemo(() => {
- const { start_timestamp, end_timestamp } = inputs;
- const timeDiff = (Date.parse(end_timestamp) - Date.parse(start_timestamp)) / 60000;
- const avgRPM = isNaN(times / timeDiff) ? '0' : (times / timeDiff).toFixed(3);
- const avgTPM = isNaN(consumeTokens / timeDiff) ? '0' : (consumeTokens / timeDiff).toFixed(3);
- return { avgRPM, avgTPM, timeDiff };
- }, [times, consumeTokens, inputs.start_timestamp, inputs.end_timestamp]);
- const getGreeting = useMemo(() => {
- const hours = new Date().getHours();
- let greeting = '';
- if (hours >= 5 && hours < 12) {
- greeting = t('早上好');
- } else if (hours >= 12 && hours < 14) {
- greeting = t('中午好');
- } else if (hours >= 14 && hours < 18) {
- greeting = t('下午好');
- } else {
- greeting = t('晚上好');
- }
- const username = userState?.user?.username || '';
- return `👋${greeting},${username}`;
- }, [t, userState?.user?.username]);
- // ========== 回调函数 ==========
- const handleInputChange = useCallback((value, name) => {
- if (name === 'data_export_default_time') {
- setDataExportDefaultTime(value);
- localStorage.setItem('data_export_default_time', value);
- return;
- }
- setInputs((inputs) => ({ ...inputs, [name]: value }));
- }, []);
- const showSearchModal = useCallback(() => {
- setSearchModalVisible(true);
- }, []);
- const handleCloseModal = useCallback(() => {
- setSearchModalVisible(false);
- }, []);
- // ========== API 调用函数 ==========
- const loadQuotaData = useCallback(async () => {
- setLoading(true);
- try {
- let url = '';
- const { start_timestamp, end_timestamp, username } = inputs;
- let localStartTimestamp = Date.parse(start_timestamp) / 1000;
- let localEndTimestamp = Date.parse(end_timestamp) / 1000;
- if (isAdminUser) {
- url = `/api/data/?username=${username}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}&default_time=${dataExportDefaultTime}`;
- } else {
- url = `/api/data/self/?start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}&default_time=${dataExportDefaultTime}`;
- }
- const res = await API.get(url);
- const { success, message, data } = res.data;
- if (success) {
- setQuotaData(data);
- if (data.length === 0) {
- data.push({
- count: 0,
- model_name: '无数据',
- quota: 0,
- created_at: now.getTime() / 1000,
- });
- }
- data.sort((a, b) => a.created_at - b.created_at);
- return data;
- } else {
- showError(message);
- return [];
- }
- } finally {
- setLoading(false);
- }
- }, [inputs, dataExportDefaultTime, isAdminUser, now]);
- const loadUptimeData = useCallback(async () => {
- setUptimeLoading(true);
- try {
- const res = await API.get('/api/uptime/status');
- const { success, message, data } = res.data;
- if (success) {
- setUptimeData(data || []);
- if (data && data.length > 0 && !activeUptimeTab) {
- setActiveUptimeTab(data[0].categoryName);
- }
- } else {
- showError(message);
- }
- } catch (err) {
- console.error(err);
- } finally {
- setUptimeLoading(false);
- }
- }, [activeUptimeTab]);
- const getUserData = useCallback(async () => {
- let res = await API.get(`/api/user/self`);
- const { success, message, data } = res.data;
- if (success) {
- userDispatch({ type: 'login', payload: data });
- } else {
- showError(message);
- }
- }, [userDispatch]);
- const refresh = useCallback(async () => {
- const data = await loadQuotaData();
- await loadUptimeData();
- return data;
- }, [loadQuotaData, loadUptimeData]);
- const handleSearchConfirm = useCallback(async (updateChartDataCallback) => {
- const data = await refresh();
- if (data && data.length > 0 && updateChartDataCallback) {
- updateChartDataCallback(data);
- }
- setSearchModalVisible(false);
- }, [refresh]);
- // ========== Effects ==========
- useEffect(() => {
- const timer = setTimeout(() => {
- setGreetingVisible(true);
- }, 100);
- return () => clearTimeout(timer);
- }, []);
- useEffect(() => {
- if (!initialized.current) {
- getUserData();
- initialized.current = true;
- }
- }, [getUserData]);
- return {
- // 基础状态
- loading: showLoading,
- greetingVisible,
- searchModalVisible,
- // 输入状态
- inputs,
- dataExportDefaultTime,
- // 数据状态
- quotaData,
- consumeQuota,
- setConsumeQuota,
- consumeTokens,
- setConsumeTokens,
- times,
- setTimes,
- pieData,
- setPieData,
- lineData,
- setLineData,
- modelColors,
- setModelColors,
- // 图表状态
- activeChartTab,
- setActiveChartTab,
- // 趋势数据
- trendData,
- setTrendData,
- // Uptime 数据
- uptimeData,
- uptimeLoading,
- activeUptimeTab,
- setActiveUptimeTab,
- // 计算值
- timeOptions,
- performanceMetrics,
- getGreeting,
- isAdminUser,
- hasApiInfoPanel,
- hasInfoPanels,
- apiInfoEnabled,
- announcementsEnabled,
- faqEnabled,
- uptimeEnabled,
- // 函数
- handleInputChange,
- showSearchModal,
- handleCloseModal,
- loadQuotaData,
- loadUptimeData,
- getUserData,
- refresh,
- handleSearchConfirm,
- // 导航和翻译
- navigate,
- t,
- isMobile
- };
- };
|