| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| 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: [], |
| }); |
|
|
| |
| const [uptimeData, setUptimeData] = useState([]); |
| const [uptimeLoading, setUptimeLoading] = useState(false); |
| const [activeUptimeTab, setActiveUptimeTab] = useState(''); |
|
|
| |
| const now = new Date(); |
| const isAdminUser = isAdmin(); |
|
|
| |
| 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; |
|
|
| |
| 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); |
| }, []); |
|
|
| |
| 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], |
| ); |
|
|
| |
| 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, |
|
|
| |
| 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, |
| }; |
| }; |
|
|