import React, { useEffect, useState } from 'react'; import { Button, Dropdown, Form, Label, Pagination, Popup, Table } from 'semantic-ui-react'; import { Link } from 'react-router-dom'; import { API, copy, showError, showSuccess, showWarning, timestamp2string } from '../helpers'; import { ITEMS_PER_PAGE } from '../constants'; import { renderQuota } from '../helpers/render'; const COPY_OPTIONS = [ { key: 'next', text: 'ChatGPT Next Web', value: 'next' }, { key: 'ama', text: 'BotGem', value: 'ama' }, { key: 'opencat', text: 'OpenCat', value: 'opencat' }, ]; const OPEN_LINK_OPTIONS = [ { key: 'next', text: 'ChatGPT Next Web', value: 'next' }, { key: 'ama', text: 'BotGem', value: 'ama' }, { key: 'opencat', text: 'OpenCat', value: 'opencat' }, ]; function renderTimestamp(timestamp) { return ( <> {timestamp2string(timestamp)} ); } function renderStatus(status) { switch (status) { case 1: return ; case 2: return ; case 3: return ; case 4: return ; default: return ; } } const TokensTable = () => { const [tokens, setTokens] = useState([]); const [loading, setLoading] = useState(true); const [activePage, setActivePage] = useState(1); const [searchKeyword, setSearchKeyword] = useState(''); const [searching, setSearching] = useState(false); const [showTopUpModal, setShowTopUpModal] = useState(false); const [targetTokenIdx, setTargetTokenIdx] = useState(0); const [orderBy, setOrderBy] = useState(''); const loadTokens = async (startIdx) => { const res = await API.get(`/api/token/?p=${startIdx}&order=${orderBy}`); const { success, message, data } = res.data; if (success) { if (startIdx === 0) { setTokens(data); } else { let newTokens = [...tokens]; newTokens.splice(startIdx * ITEMS_PER_PAGE, data.length, ...data); setTokens(newTokens); } } else { showError(message); } setLoading(false); }; const onPaginationChange = (e, { activePage }) => { (async () => { if (activePage === Math.ceil(tokens.length / ITEMS_PER_PAGE) + 1) { // In this case we have to load more data and then append them. await loadTokens(activePage - 1, orderBy); } setActivePage(activePage); })(); }; const refresh = async () => { setLoading(true); await loadTokens(activePage - 1); }; const onCopy = async (type, 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); const nextLink = localStorage.getItem('chat_link'); let nextUrl; if (nextLink) { nextUrl = nextLink + `/#/?settings={"key":"sk-${key}","url":"${serverAddress}"}`; } else { nextUrl = `https://app.nextchat.dev/#/?settings={"key":"sk-${key}","url":"${serverAddress}"}`; } let url; switch (type) { case 'ama': url = `ama://set-api-key?server=${encodedServerAddress}&key=sk-${key}`; break; case 'opencat': url = `opencat://team/join?domain=${encodedServerAddress}&token=sk-${key}`; break; case 'next': url = nextUrl; break; default: url = `sk-${key}`; } if (await copy(url)) { showSuccess('已复制到剪贴板!'); } else { showWarning('无法复制到剪贴板,请手动复制,已将令牌填入搜索框。'); setSearchKeyword(url); } }; const onOpenLink = async (type, 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); const chatLink = localStorage.getItem('chat_link'); let defaultUrl; if (chatLink) { defaultUrl = chatLink + `/#/?settings={"key":"sk-${key}","url":"${serverAddress}"}`; } else { defaultUrl = `https://app.nextchat.dev/#/?settings={"key":"sk-${key}","url":"${serverAddress}"}`; } let url; switch (type) { case 'ama': url = `ama://set-api-key?server=${encodedServerAddress}&key=sk-${key}`; break; case 'opencat': url = `opencat://team/join?domain=${encodedServerAddress}&token=sk-${key}`; break; default: url = defaultUrl; } window.open(url, '_blank'); } useEffect(() => { loadTokens(0, orderBy) .then() .catch((reason) => { showError(reason); }); }, [orderBy]); const manageToken = async (id, action, idx) => { 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') { newTokens[realIdx].deleted = true; } else { newTokens[realIdx].status = token.status; } setTokens(newTokens); } else { showError(message); } }; const searchTokens = async () => { if (searchKeyword === '') { // if keyword is blank, load files instead. await loadTokens(0); setActivePage(1); setOrderBy(''); return; } setSearching(true); const res = await API.get(`/api/token/search?keyword=${searchKeyword}`); const { success, message, data } = res.data; if (success) { setTokens(data); setActivePage(1); } else { showError(message); } setSearching(false); }; const handleKeywordChange = async (e, { value }) => { setSearchKeyword(value.trim()); }; const sortToken = (key) => { if (tokens.length === 0) return; setLoading(true); let sortedTokens = [...tokens]; sortedTokens.sort((a, b) => { if (!isNaN(a[key])) { // If the value is numeric, subtract to sort return a[key] - b[key]; } else { // If the value is not numeric, sort as strings return ('' + a[key]).localeCompare(b[key]); } }); if (sortedTokens[0].id === tokens[0].id) { sortedTokens.reverse(); } setTokens(sortedTokens); setLoading(false); }; const handleOrderByChange = (e, { value }) => { setOrderBy(value); setActivePage(1); }; return ( <>
{ sortToken('name'); }} > 名称 { sortToken('status'); }} > 状态 { sortToken('used_quota'); }} > 已用额度 { sortToken('remain_quota'); }} > 剩余额度 { sortToken('created_time'); }} > 创建时间 { sortToken('expired_time'); }} > 过期时间 操作 {tokens .slice( (activePage - 1) * ITEMS_PER_PAGE, activePage * ITEMS_PER_PAGE ) .map((token, idx) => { if (token.deleted) return <>; return ( {token.name ? token.name : '无'} {renderStatus(token.status)} {renderQuota(token.used_quota)} {token.unlimited_quota ? '无限制' : renderQuota(token.remain_quota, 2)} {renderTimestamp(token.created_time)} {token.expired_time === -1 ? '永不过期' : renderTimestamp(token.expired_time)}
({ ...option, onClick: async () => { await onCopy(option.value, token.key); } }))} trigger={<>} /> {' '} ({ ...option, onClick: async () => { await onOpenLink(option.value, token.key); } }))} trigger={<>} /> {' '} 删除 } on='click' flowing hoverable >
); })}
); }; export default TokensTable;