import React, { useEffect, useState } from 'react'; import { API, copy, showError, showSuccess, timestamp2string, } from '../helpers'; import { ITEMS_PER_PAGE } from '../constants'; import {renderGroup, renderQuota} from '../helpers/render'; import { Button, Divider, Dropdown, Form, Modal, Popconfirm, Popover, Space, SplitButtonGroup, Table, Tag, } from '@douyinfe/semi-ui'; import { IconTreeTriangleDown } from '@douyinfe/semi-icons'; import EditToken from '../pages/Token/EditToken'; import { useTranslation } from 'react-i18next'; function renderTimestamp(timestamp) { return <>{timestamp2string(timestamp)}; } const TokensTable = () => { const { t } = useTranslation(); const renderStatus = (status, model_limits_enabled = false) => { switch (status) { case 1: if (model_limits_enabled) { return ( {t('已启用:限制模型')} ); } else { return ( {t('已启用')} ); } case 2: return ( {t('已禁用')} ); case 3: return ( {t('已过期')} ); case 4: return ( {t('已耗尽')} ); default: return ( {t('未知状态')} ); } }; const columns = [ { title: t('名称'), dataIndex: 'name', }, { title: t('状态'), dataIndex: 'status', key: 'status', render: (text, record, index) => { return
{renderStatus(text, record.model_limits_enabled)} {renderGroup(record.group)}
; }, }, { title: t('已用额度'), dataIndex: 'used_quota', render: (text, record, index) => { return
{renderQuota(parseInt(text))}
; }, }, { title: t('剩余额度'), dataIndex: 'remain_quota', render: (text, record, index) => { return (
{record.unlimited_quota ? ( {t('无限制')} ) : ( {renderQuota(parseInt(text))} )}
); }, }, { title: t('创建时间'), dataIndex: 'created_time', render: (text, record, index) => { return
{renderTimestamp(text)}
; }, }, { title: t('过期时间'), dataIndex: 'expired_time', render: (text, record, index) => { return (
{record.expired_time === -1 ? t('永不过期') : renderTimestamp(text)}
); }, }, { title: '', dataIndex: 'operate', render: (text, record, index) => { let chats = localStorage.getItem('chats'); let chatsArray = [] let chatLink = localStorage.getItem('chat_link'); let mjLink = localStorage.getItem('chat_link2'); let shouldUseCustom = true; if (chatLink) { shouldUseCustom = false; chatLink += `/#/?settings={"key":"{key}","url":"{address}"}`; chatsArray.push({ node: 'item', key: 'default', name: 'ChatGPT Next Web', onClick: () => { onOpenLink('default', chatLink, record); }, }); } if (mjLink) { shouldUseCustom = false; mjLink += `/#/?settings={"key":"{key}","url":"{address}"}`; chatsArray.push({ node: 'item', key: 'mj', name: 'ChatGPT Next Midjourney', onClick: () => { onOpenLink('mj', mjLink, record); }, }); } if (shouldUseCustom) { try { // console.log(chats); chats = JSON.parse(chats); // check chats is array if (Array.isArray(chats)) { for (let i = 0; i < chats.length; i++) { let chat = {} chat.node = 'item'; // c is a map // chat.key = chats[i].name; // console.log(chats[i]) for (let key in chats[i]) { if (chats[i].hasOwnProperty(key)) { chat.key = i; chat.name = key; chat.onClick = () => { onOpenLink(key, chats[i][key], record); } } } chatsArray.push(chat); } } } catch (e) { console.log(e); showError(t('聊天链接配置错误,请联系管理员')); } } return (
{ manageToken(record.id, 'delete', record).then(() => { removeRecord(record.key); }); }} > {record.status === 1 ? ( ) : ( )}
); }, }, ]; const [pageSize, setPageSize] = useState(ITEMS_PER_PAGE); const [showEdit, setShowEdit] = useState(false); const [tokens, setTokens] = useState([]); const [selectedKeys, setSelectedKeys] = useState([]); const [tokenCount, setTokenCount] = useState(pageSize); const [loading, setLoading] = useState(true); const [activePage, setActivePage] = useState(1); const [searchKeyword, setSearchKeyword] = useState(''); const [searchToken, setSearchToken] = useState(''); const [searching, setSearching] = useState(false); const [chats, setChats] = useState([]); const [editingToken, setEditingToken] = useState({ id: undefined, }); const closeEdit = () => { setShowEdit(false); setTimeout(() => { setEditingToken({ id: undefined, }); }, 500); }; const setTokensFormat = (tokens) => { setTokens(tokens); if (tokens.length >= pageSize) { setTokenCount(tokens.length + pageSize); } else { setTokenCount(tokens.length); } }; let pageData = tokens.slice( (activePage - 1) * pageSize, activePage * pageSize, ); const loadTokens = async (startIdx) => { setLoading(true); const res = await API.get(`/api/token/?p=${startIdx}&size=${pageSize}`); const { success, message, data } = res.data; if (success) { if (startIdx === 0) { setTokensFormat(data); } else { let newTokens = [...tokens]; newTokens.splice(startIdx * pageSize, data.length, ...data); setTokensFormat(newTokens); } } else { showError(message); } setLoading(false); }; const refresh = async () => { await loadTokens(activePage - 1); }; const copyText = async (text) => { if (await copy(text)) { showSuccess(t('已复制到剪贴板!')); } else { Modal.error({ title: t('无法复制到剪贴板,请手动复制'), content: text, size: 'large', }); } }; const onOpenLink = async (type, url, record) => { // console.log(type, url, key); let status = localStorage.getItem('status'); let serverAddress = ''; if (status) { status = JSON.parse(status); serverAddress = status.server_address; } if (serverAddress === '') { serverAddress = window.location.origin; } let encodedServerAddress = encodeURIComponent(serverAddress); url = url.replaceAll('{address}', encodedServerAddress); url = url.replaceAll('{key}', 'sk-' + record.key); window.open(url, '_blank'); }; useEffect(() => { loadTokens(0) .then() .catch((reason) => { showError(reason); }); }, [pageSize]); const removeRecord = (key) => { let newDataSource = [...tokens]; if (key != null) { let idx = newDataSource.findIndex((data) => data.key === key); if (idx > -1) { newDataSource.splice(idx, 1); setTokensFormat(newDataSource); } } }; 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]; // let realIdx = (activePage - 1) * ITEMS_PER_PAGE + idx; if (action === 'delete') { } else { record.status = token.status; // newTokens[realIdx].status = token.status; } setTokensFormat(newTokens); } else { showError(message); } setLoading(false); }; const searchTokens = async () => { if (searchKeyword === '' && searchToken === '') { // if keyword is blank, load files instead. await loadTokens(0); setActivePage(1); return; } setSearching(true); const res = await API.get( `/api/token/search?keyword=${searchKeyword}&token=${searchToken}`, ); const { success, message, data } = res.data; if (success) { setTokensFormat(data); setActivePage(1); } else { showError(message); } setSearching(false); }; const handleKeywordChange = async (value) => { setSearchKeyword(value.trim()); }; const handleSearchTokenChange = async (value) => { setSearchToken(value.trim()); }; 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); }; const handlePageChange = (page) => { setActivePage(page); if (page === Math.ceil(tokens.length / pageSize) + 1) { // In this case we have to load more data and then append them. loadTokens(page - 1).then((r) => {}); } }; const rowSelection = { onSelect: (record, selected) => {}, onSelectAll: (selected, selectedRows) => {}, onChange: (selectedRowKeys, selectedRows) => { setSelectedKeys(selectedRows); }, }; const handleRow = (record, index) => { if (record.status !== 1) { return { style: { background: 'var(--semi-color-disabled-border)', }, }; } else { return {}; } }; return ( <>
t('第 {{start}} - {{end}} 条,共 {{total}} 条', { start: page.currentStart, end: page.currentEnd, total: tokens.length }), onPageSizeChange: (size) => { setPageSize(size); setActivePage(1); }, onPageChange: handlePageChange, }} loading={loading} rowSelection={rowSelection} onRow={handleRow} >
); }; export default TokensTable;