/* 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 . For commercial licensing, please contact support@quantumnous.com */ import { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { Modal } from '@douyinfe/semi-ui'; import { API, copy, showError, showSuccess, encodeToBase64, } from '../../helpers'; import { ITEMS_PER_PAGE } from '../../constants'; import { useTableCompactMode } from '../common/useTableCompactMode'; export const useTokensData = (openFluentNotification) => { const { t } = useTranslation(); // Basic state const [tokens, setTokens] = useState([]); const [loading, setLoading] = useState(true); const [activePage, setActivePage] = useState(1); const [tokenCount, setTokenCount] = useState(0); const [pageSize, setPageSize] = useState(ITEMS_PER_PAGE); const [searching, setSearching] = useState(false); // Selection state const [selectedKeys, setSelectedKeys] = useState([]); // Edit state const [showEdit, setShowEdit] = useState(false); const [editingToken, setEditingToken] = useState({ id: undefined, }); // UI state const [compactMode, setCompactMode] = useTableCompactMode('tokens'); const [showKeys, setShowKeys] = useState({}); // Form state const [formApi, setFormApi] = useState(null); const formInitValues = { searchKeyword: '', searchToken: '', }; // Get form values helper function const getFormValues = () => { const formValues = formApi ? formApi.getValues() : {}; return { searchKeyword: formValues.searchKeyword || '', searchToken: formValues.searchToken || '', }; }; // Close edit modal const closeEdit = () => { setShowEdit(false); setTimeout(() => { setEditingToken({ id: undefined, }); }, 500); }; // Sync page data from API response const syncPageData = (payload) => { setTokens(payload.items || []); setTokenCount(payload.total || 0); setActivePage(payload.page || 1); setPageSize(payload.page_size || pageSize); }; // Load tokens function const loadTokens = async (page = 1, size = pageSize) => { setLoading(true); const res = await API.get(`/api/token/?p=${page}&size=${size}`); const { success, message, data } = res.data; if (success) { syncPageData(data); } else { showError(message); } setLoading(false); }; // Refresh function const refresh = async (page = activePage) => { await loadTokens(page); setSelectedKeys([]); }; // Copy text function const copyText = async (text) => { if (await copy(text)) { showSuccess(t('已复制到剪贴板!')); } else { Modal.error({ title: t('无法复制到剪贴板,请手动复制'), content: text, size: 'large', }); } }; // Open link function for chat integrations const onOpenLink = async (type, url, record) => { if (url && url.startsWith('fluent')) { openFluentNotification(record.key); return; } let status = localStorage.getItem('status'); let serverAddress = ''; if (status) { status = JSON.parse(status); serverAddress = status.server_address; } if (serverAddress === '') { serverAddress = window.location.origin; } if (url.includes('{cherryConfig}') === true) { let cherryConfig = { id: 'new-api', baseUrl: serverAddress, apiKey: 'sk-' + record.key, }; let encodedConfig = encodeURIComponent( encodeToBase64(JSON.stringify(cherryConfig)), ); url = url.replaceAll('{cherryConfig}', encodedConfig); } else { let encodedServerAddress = encodeURIComponent(serverAddress); url = url.replaceAll('{address}', encodedServerAddress); url = url.replaceAll('{key}', 'sk-' + record.key); } window.open(url, '_blank'); }; // Manage token function (delete, enable, disable) const manageToken = async (id, action, record) => { setLoading(true); let data = { id }; let res; switch (action) { case 'delete': res = await API.delete(`/api/token/${id}/`); break; case 'enable': data.status = 1; res = await API.put('/api/token/?status_only=true', data); break; case 'disable': data.status = 2; res = await API.put('/api/token/?status_only=true', data); break; } const { success, message } = res.data; if (success) { showSuccess('操作成功完成!'); let token = res.data.data; let newTokens = [...tokens]; if (action !== 'delete') { record.status = token.status; } setTokens(newTokens); } else { showError(message); } setLoading(false); }; // Search tokens function const searchTokens = async () => { const { searchKeyword, searchToken } = getFormValues(); if (searchKeyword === '' && searchToken === '') { await loadTokens(1); return; } setSearching(true); const res = await API.get( `/api/token/search?keyword=${searchKeyword}&token=${searchToken}`, ); const { success, message, data } = res.data; if (success) { setTokens(data); setTokenCount(data.length); setActivePage(1); } else { showError(message); } setSearching(false); }; // Sort tokens function const sortToken = (key) => { if (tokens.length === 0) return; setLoading(true); let sortedTokens = [...tokens]; sortedTokens.sort((a, b) => { return ('' + a[key]).localeCompare(b[key]); }); if (sortedTokens[0].id === tokens[0].id) { sortedTokens.reverse(); } setTokens(sortedTokens); setLoading(false); }; // Page handlers const handlePageChange = (page) => { loadTokens(page, pageSize).then(); }; const handlePageSizeChange = async (size) => { setPageSize(size); await loadTokens(1, size); }; // Row selection handlers const rowSelection = { onSelect: (record, selected) => {}, onSelectAll: (selected, selectedRows) => {}, onChange: (selectedRowKeys, selectedRows) => { setSelectedKeys(selectedRows); }, }; // Handle row styling const handleRow = (record, index) => { if (record.status !== 1) { return { style: { background: 'var(--semi-color-disabled-border)', }, }; } else { return {}; } }; // Batch delete tokens const batchDeleteTokens = async () => { if (selectedKeys.length === 0) { showError(t('请先选择要删除的令牌!')); return; } setLoading(true); try { const ids = selectedKeys.map((token) => token.id); const res = await API.post('/api/token/batch', { ids }); if (res?.data?.success) { const count = res.data.data || 0; showSuccess(t('已删除 {{count}} 个令牌!', { count })); await refresh(); setTimeout(() => { if (tokens.length === 0 && activePage > 1) { refresh(activePage - 1); } }, 100); } else { showError(res?.data?.message || t('删除失败')); } } catch (error) { showError(error.message); } finally { setLoading(false); } }; // Batch copy tokens const batchCopyTokens = (copyType) => { if (selectedKeys.length === 0) { showError(t('请至少选择一个令牌!')); return; } Modal.info({ title: t('复制令牌'), icon: null, content: t('请选择你的复制方式'), footer: (
), }); }; // Initialize data useEffect(() => { loadTokens(1) .then() .catch((reason) => { showError(reason); }); }, [pageSize]); return { // Basic state tokens, loading, activePage, tokenCount, pageSize, searching, // Selection state selectedKeys, setSelectedKeys, // Edit state showEdit, setShowEdit, editingToken, setEditingToken, closeEdit, // UI state compactMode, setCompactMode, showKeys, setShowKeys, // Form state formApi, setFormApi, formInitValues, getFormValues, // Functions loadTokens, refresh, copyText, onOpenLink, manageToken, searchTokens, sortToken, handlePageChange, handlePageSizeChange, rowSelection, handleRow, batchDeleteTokens, batchCopyTokens, syncPageData, // Translation t, }; };