import React, { useEffect, useState } from 'react'; import { API, copy, showError, showSuccess, timestamp2string } from '../helpers'; import { ITEMS_PER_PAGE } from '../constants'; import { renderQuota } from '../helpers/render'; import { Button, Dropdown, Form, Modal, Popconfirm, Popover, SplitButtonGroup, Table, Tag } from '@douyinfe/semi-ui'; import { IconTreeTriangleDown } from '@douyinfe/semi-icons'; import EditToken from '../pages/Token/EditToken'; const COPY_OPTIONS = [ { key: 'next', text: 'ChatGPT Next Web', value: 'next' }, { key: 'ama', text: 'ChatGPT Web & Midjourney', value: 'ama' }, { key: 'opencat', text: 'OpenCat', value: 'opencat' } ]; const OPEN_LINK_OPTIONS = [ { key: 'ama', text: 'ChatGPT Web & Midjourney', value: 'ama' }, { key: 'opencat', text: 'OpenCat', value: 'opencat' } ]; function renderTimestamp(timestamp) { return ( <> {timestamp2string(timestamp)} ); } function renderStatus(status, model_limits_enabled = false) { switch (status) { case 1: if (model_limits_enabled) { return 已启用:限制模型; } else { return 已启用; } case 2: return 已禁用 ; case 3: return 已过期 ; case 4: return 已耗尽 ; default: return 未知状态 ; } } const TokensTable = () => { const link_menu = [ { node: 'item', key: 'next', name: 'ChatGPT Next Web', onClick: () => { onOpenLink('next'); } }, { node: 'item', key: 'ama', name: 'AMA 问天', value: 'ama' }, { node: 'item', key: 'next-mj', name: 'ChatGPT Web & Midjourney', value: 'next-mj', onClick: () => { onOpenLink('next-mj'); } }, { node: 'item', key: 'opencat', name: 'OpenCat', value: 'opencat' } ]; const columns = [ { title: '名称', dataIndex: 'name' }, { title: '状态', dataIndex: 'status', key: 'status', render: (text, record, index) => { return (
{renderStatus(text, record.model_limits_enabled)}
); } }, { title: '已用额度', dataIndex: 'used_quota', render: (text, record, index) => { return (
{renderQuota(parseInt(text))}
); } }, { title: '剩余额度', dataIndex: 'remain_quota', render: (text, record, index) => { return (
{record.unlimited_quota ? 无限制 : {renderQuota(parseInt(text))}}
); } }, { title: '创建时间', dataIndex: 'created_time', render: (text, record, index) => { return (
{renderTimestamp(text)}
); } }, { title: '过期时间', dataIndex: 'expired_time', render: (text, record, index) => { return (
{record.expired_time === -1 ? '永不过期' : renderTimestamp(text)}
); } }, { title: '', dataIndex: 'operate', render: (text, record, index) => (
{ onOpenLink('next', record.key); } }, { node: 'item', key: 'next-mj', disabled: !localStorage.getItem('chat_link2'), name: 'ChatGPT Web & Midjourney', onClick: () => { onOpenLink('next-mj', record.key); } }, { node: 'item', key: 'ama', name: 'AMA 问天(BotGem)', onClick: () => { onOpenLink('ama', record.key); } }, { node: 'item', key: 'opencat', name: 'OpenCat', onClick: () => { onOpenLink('opencat', record.key); } } ] } > { 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 [showTopUpModal, setShowTopUpModal] = useState(false); const [targetTokenIdx, setTargetTokenIdx] = useState(0); 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 onPaginationChange = (e, { activePage }) => { (async () => { if (activePage === Math.ceil(tokens.length / pageSize) + 1) { // In this case we have to load more data and then append them. await loadTokens(activePage - 1); } setActivePage(activePage); })(); }; const refresh = async () => { 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'); const mjLink = localStorage.getItem('chat_link2'); let nextUrl; if (nextLink) { nextUrl = nextLink + `/#/?settings={"key":"sk-${key}","url":"${serverAddress}"}`; } else { nextUrl = `https://chat.oneapi.pro/#/?settings={"key":"sk-${key}","url":"${serverAddress}"}`; } let url; switch (type) { case 'ama': url = mjLink + `/#/?settings={"key":"sk-${key}","url":"${serverAddress}"}`; 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 copyText = async (text) => { if (await copy(text)) { showSuccess('已复制到剪贴板!'); } else { // setSearchKeyword(text); Modal.error({ title: '无法复制到剪贴板,请手动复制', content: text }); } }; 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'); const mjLink = localStorage.getItem('chat_link2'); let defaultUrl; if (chatLink) { defaultUrl = chatLink + `/#/?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-mj': url = mjLink + `/#/?settings={"key":"sk-${key}","url":"${serverAddress}"}`; break; default: if (!chatLink) { showError('管理员未设置聊天链接'); return; } url = defaultUrl; } 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 ( <>
{/* */} `第 ${page.currentStart} - ${page.currentEnd} 条,共 ${tokens.length} 条`, onPageSizeChange: (size) => { setPageSize(size); setActivePage(1); }, onPageChange: handlePageChange }} loading={loading} rowSelection={rowSelection} onRow={handleRow}>
); }; export default TokensTable;