feat: add chat button for each token (#363)
* fork * fork * chore: update style --------- Co-authored-by: JustSong <songquanpeng@foxmail.com>
This commit is contained in:
parent
b464e2907a
commit
5a62357c93
@ -3,6 +3,11 @@
|
||||
"%d 点额度": "%d point quota",
|
||||
"尚未实现": "Not yet implemented",
|
||||
"余额不足": "Insufficient balance",
|
||||
"危险操作": "Hazardous operations",
|
||||
"输入你的账户名": "Enter your account name",
|
||||
"确认删除": "Confirm Delete",
|
||||
"确认绑定": "Confirm Binding",
|
||||
"您正在删除自己的帐户,将清空所有数据且不可恢复": "You are deleting your account, all data will be cleared and unrecoverable.",
|
||||
"\"通道「%s」(#%d)已被禁用\"": "\"Channel %s (#%d) has been disabled\"",
|
||||
"通道「%s」(#%d)已被禁用,原因:%s": "Channel %s (#%d) has been disabled, reason: %s",
|
||||
"测试已在运行中": "Test is already running",
|
||||
@ -427,7 +432,7 @@
|
||||
"一分钟后过期": "Expires after one minute",
|
||||
"创建新的令牌": "Create New Token",
|
||||
"注意,令牌的额度仅用于限制令牌本身的最大额度使用量,实际的使用受到账户的剩余额度限制。": "Note that the quota of the token is only used to limit the maximum quota usage of the token itself, and the actual usage is limited by the remaining quota of the account.",
|
||||
"设置为无限额度": "Set to unlimited quota",
|
||||
"设为无限额度": "Set to unlimited quota",
|
||||
"更新令牌信息": "Update Token Information",
|
||||
"请输入充值码!": "Please enter the recharge code!",
|
||||
"请输入名称": "Please enter a name",
|
||||
@ -493,6 +498,7 @@
|
||||
"参数替换为你的部署名称(模型名称中的点会被剔除)": "Replace the parameter with your deployment name (dots in the model name will be removed)",
|
||||
"模型映射必须是合法的 JSON 格式!": "Model mapping must be in valid JSON format!",
|
||||
"取消无限额度": "Cancel unlimited quota",
|
||||
"取消": "Cancel",
|
||||
"请输入新的剩余额度": "Please enter the new remaining quota",
|
||||
"请输入单个兑换码中包含的额度": "Please enter the quota included in a single redemption code",
|
||||
"请输入用户名": "Please enter username",
|
||||
|
@ -25,6 +25,8 @@ const PersonalSetting = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [disableButton, setDisableButton] = useState(false);
|
||||
const [countdown, setCountdown] = useState(30);
|
||||
const [affLink, setAffLink] = useState("");
|
||||
const [systemToken, setSystemToken] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
let status = localStorage.getItem('status');
|
||||
@ -59,8 +61,10 @@ const PersonalSetting = () => {
|
||||
const res = await API.get('/api/user/token');
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
setSystemToken(data);
|
||||
setAffLink("");
|
||||
await copy(data);
|
||||
showSuccess(`令牌已重置并已复制到剪贴板:${data}`);
|
||||
showSuccess(`令牌已重置并已复制到剪贴板`);
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
@ -71,13 +75,27 @@ const PersonalSetting = () => {
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
let link = `${window.location.origin}/register?aff=${data}`;
|
||||
setAffLink(link);
|
||||
setSystemToken("");
|
||||
await copy(link);
|
||||
showNotice(`邀请链接已复制到剪切板:${link}`);
|
||||
showSuccess(`邀请链接已复制到剪切板`);
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAffLinkClick = async (e) => {
|
||||
e.target.select();
|
||||
await copy(e.target.value);
|
||||
showSuccess(`邀请链接已复制到剪切板`);
|
||||
};
|
||||
|
||||
const handleSystemTokenClick = async (e) => {
|
||||
e.target.select();
|
||||
await copy(e.target.value);
|
||||
showSuccess(`系统令牌已复制到剪切板`);
|
||||
};
|
||||
|
||||
const deleteAccount = async () => {
|
||||
if (inputs.self_account_deletion_confirmation !== userState.user.username) {
|
||||
showError('请输入你的账户名以确认删除!');
|
||||
@ -168,6 +186,25 @@ const PersonalSetting = () => {
|
||||
<Button onClick={() => {
|
||||
setShowAccountDeleteModal(true);
|
||||
}}>删除个人账户</Button>
|
||||
|
||||
{systemToken && (
|
||||
<Form.Input
|
||||
fluid
|
||||
readOnly
|
||||
value={systemToken}
|
||||
onClick={handleSystemTokenClick}
|
||||
style={{ marginTop: '10px' }}
|
||||
/>
|
||||
)}
|
||||
{affLink && (
|
||||
<Form.Input
|
||||
fluid
|
||||
readOnly
|
||||
value={affLink}
|
||||
onClick={handleAffLinkClick}
|
||||
style={{ marginTop: '10px' }}
|
||||
/>
|
||||
)}
|
||||
<Divider />
|
||||
<Header as='h3'>账号绑定</Header>
|
||||
{
|
||||
@ -262,6 +299,7 @@ const PersonalSetting = () => {
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginTop: '1rem' }}>
|
||||
<Button
|
||||
color=''
|
||||
fluid
|
||||
@ -269,8 +307,17 @@ const PersonalSetting = () => {
|
||||
onClick={bindEmail}
|
||||
loading={loading}
|
||||
>
|
||||
绑定
|
||||
确认绑定
|
||||
</Button>
|
||||
<div style={{ width: '1rem' }}></div>
|
||||
<Button
|
||||
fluid
|
||||
size='large'
|
||||
onClick={() => setShowEmailBindModal(false)}
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</Modal.Description>
|
||||
</Modal.Content>
|
||||
@ -282,8 +329,9 @@ const PersonalSetting = () => {
|
||||
size={'tiny'}
|
||||
style={{ maxWidth: '450px' }}
|
||||
>
|
||||
<Modal.Header>确认删除自己的帐户</Modal.Header>
|
||||
<Modal.Header>危险操作</Modal.Header>
|
||||
<Modal.Content>
|
||||
<Message>您正在删除自己的帐户,将清空所有数据且不可恢复</Message>
|
||||
<Modal.Description>
|
||||
<Form size='large'>
|
||||
<Form.Input
|
||||
@ -303,15 +351,25 @@ const PersonalSetting = () => {
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<Button
|
||||
color='red'
|
||||
fluid
|
||||
size='large'
|
||||
onClick={deleteAccount}
|
||||
loading={loading}
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginTop: '1rem' }}>
|
||||
<Button
|
||||
color='red'
|
||||
fluid
|
||||
size='large'
|
||||
onClick={deleteAccount}
|
||||
loading={loading}
|
||||
>
|
||||
确认删除
|
||||
</Button>
|
||||
<div style={{ width: '1rem' }}></div>
|
||||
<Button
|
||||
fluid
|
||||
size='large'
|
||||
onClick={() => setShowAccountDeleteModal(false)}
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</Modal.Description>
|
||||
</Modal.Content>
|
||||
|
@ -12,6 +12,11 @@ const COPY_OPTIONS = [
|
||||
{ key: 'opencat', text: 'OpenCat', value: 'opencat' },
|
||||
];
|
||||
|
||||
const OPEN_LINK_OPTIONS = [
|
||||
{ key: 'ama', text: 'AMA 问天', value: 'ama' },
|
||||
{ key: 'opencat', text: 'OpenCat', value: 'opencat' },
|
||||
];
|
||||
|
||||
function renderTimestamp(timestamp) {
|
||||
return (
|
||||
<>
|
||||
@ -87,6 +92,15 @@ const TokensTable = () => {
|
||||
serverAddress = window.location.origin;
|
||||
}
|
||||
let encodedServerAddress = encodeURIComponent(serverAddress);
|
||||
const nextLink = localStorage.getItem('chat_link');
|
||||
let nextUrl;
|
||||
|
||||
if (nextLink) {
|
||||
nextUrl = nextLink + `/#/?settings={"key":"sk-${key}"}`;
|
||||
} else {
|
||||
nextUrl = `https://chat.oneapi.pro/#/?settings={"key":"sk-${key}","url":"${serverAddress}"}`;
|
||||
}
|
||||
|
||||
let url;
|
||||
switch (type) {
|
||||
case 'ama':
|
||||
@ -96,7 +110,7 @@ const TokensTable = () => {
|
||||
url = `opencat://team/join?domain=${encodedServerAddress}&token=sk-${key}`;
|
||||
break;
|
||||
case 'next':
|
||||
url = `https://chat.oneapi.pro/#/?settings=%7B%22key%22:%22sk-${key}%22,%22url%22:%22${serverAddress}%22%7D`;
|
||||
url = nextUrl;
|
||||
break;
|
||||
default:
|
||||
url = `sk-${key}`;
|
||||
@ -109,6 +123,42 @@ const TokensTable = () => {
|
||||
}
|
||||
};
|
||||
|
||||
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}"}`;
|
||||
} else {
|
||||
defaultUrl = `https://chat.oneapi.pro/#/?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)
|
||||
.then()
|
||||
@ -274,28 +324,51 @@ const TokensTable = () => {
|
||||
<Table.Cell>{token.expired_time === -1 ? '永不过期' : renderTimestamp(token.expired_time)}</Table.Cell>
|
||||
<Table.Cell>
|
||||
<div>
|
||||
<Button.Group color='green' size={'small'}>
|
||||
<Button.Group color='green' size={'small'}>
|
||||
<Button
|
||||
size={'small'}
|
||||
positive
|
||||
onClick={async () => {
|
||||
await onCopy('', token.key);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
复制
|
||||
</Button>
|
||||
<Dropdown
|
||||
className='button icon'
|
||||
floating
|
||||
options={COPY_OPTIONS}
|
||||
onChange={async (e, { value } = {}) => {
|
||||
await onCopy(value, token.key);
|
||||
}}
|
||||
options={COPY_OPTIONS.map(option => ({
|
||||
...option,
|
||||
onClick: async () => {
|
||||
await onCopy(option.value, token.key);
|
||||
}
|
||||
}))}
|
||||
trigger={<></>}
|
||||
/>
|
||||
</Button.Group>
|
||||
{' '}
|
||||
<Button.Group color='blue' size={'small'}>
|
||||
<Button
|
||||
size={'small'}
|
||||
positive
|
||||
onClick={() => {
|
||||
onOpenLink('', token.key);
|
||||
}}>
|
||||
聊天
|
||||
</Button>
|
||||
<Dropdown
|
||||
className="button icon"
|
||||
floating
|
||||
options={OPEN_LINK_OPTIONS.map(option => ({
|
||||
...option,
|
||||
onClick: async () => {
|
||||
await onOpenLink(option.value, token.key);
|
||||
}
|
||||
}))}
|
||||
trigger={<></>}
|
||||
/>
|
||||
</Button.Group>
|
||||
{' '}
|
||||
<Popup
|
||||
trigger={
|
||||
<Button size='small' negative>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Button, Form, Header, Message, Segment } from 'semantic-ui-react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { API, showError, showSuccess, timestamp2string } from '../../helpers';
|
||||
import { renderQuota, renderQuotaWithPrompt } from '../../helpers/render';
|
||||
|
||||
@ -17,11 +17,13 @@ const EditToken = () => {
|
||||
};
|
||||
const [inputs, setInputs] = useState(originInputs);
|
||||
const { name, remain_quota, expired_time, unlimited_quota } = inputs;
|
||||
|
||||
const navigate = useNavigate();
|
||||
const handleInputChange = (e, { name, value }) => {
|
||||
setInputs((inputs) => ({ ...inputs, [name]: value }));
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
navigate("/token");
|
||||
}
|
||||
const setExpiredTime = (month, day, hour, minute) => {
|
||||
let now = new Date();
|
||||
let timestamp = now.getTime() / 1000;
|
||||
@ -150,8 +152,9 @@ const EditToken = () => {
|
||||
</Form.Field>
|
||||
<Button type={'button'} onClick={() => {
|
||||
setUnlimitedQuota();
|
||||
}}>{unlimited_quota ? '取消无限额度' : '设置为无限额度'}</Button>
|
||||
<Button positive onClick={submit}>提交</Button>
|
||||
}}>{unlimited_quota ? '取消无限额度' : '设为无限额度'}</Button>
|
||||
<Button floated='right' positive onClick={submit}>提交</Button>
|
||||
<Button floated='right' onClick={handleCancel}>取消</Button>
|
||||
</Form>
|
||||
</Segment>
|
||||
</>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Button, Form, Header, Segment } from 'semantic-ui-react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { API, showError, showSuccess } from '../../helpers';
|
||||
import { renderQuota, renderQuotaWithPrompt } from '../../helpers/render';
|
||||
|
||||
@ -36,7 +36,10 @@ const EditUser = () => {
|
||||
showError(error.message);
|
||||
}
|
||||
};
|
||||
|
||||
const navigate = useNavigate();
|
||||
const handleCancel = () => {
|
||||
navigate("/setting");
|
||||
}
|
||||
const loadUser = async () => {
|
||||
let res = undefined;
|
||||
if (userId) {
|
||||
@ -176,6 +179,7 @@ const EditUser = () => {
|
||||
readOnly
|
||||
/>
|
||||
</Form.Field>
|
||||
<Button onClick={handleCancel}>取消</Button>
|
||||
<Button positive onClick={submit}>提交</Button>
|
||||
</Form>
|
||||
</Segment>
|
||||
|
Loading…
Reference in New Issue
Block a user