feat: Introduce configurable docs link and remove hardcoded chat links

- Added a new GeneralSetting struct to manage configurable docs link
- Removed hardcoded ChatLink and ChatLink2 variables across multiple files
- Updated frontend components to dynamically render docs link from status
- Simplified chat and link-related logic in various components
- Added a warning modal for quota per unit setting in operation settings
This commit is contained in:
1808837298@qq.com
2025-03-09 18:31:16 +08:00
parent 4a8bb625b8
commit 00c2d6c102
13 changed files with 130 additions and 110 deletions

View File

@@ -44,6 +44,7 @@ const HeaderBar = () => {
// Check if self-use mode is enabled
const isSelfUseMode = statusState?.status?.self_use_mode_enabled || false;
const docsLink = statusState?.status?.docs_link || '';
const isDemoSiteMode = statusState?.status?.demo_site_enabled || false;
let buttons = [
@@ -62,6 +63,13 @@ const HeaderBar = () => {
itemKey: 'pricing',
to: '/pricing',
},
// Only include the docs button if docsLink exists
...(docsLink ? [{
text: t('文档'),
itemKey: 'docs',
isExternal: true,
externalLink: docsLink,
}] : []),
{
text: t('关于'),
itemKey: 'about',
@@ -157,13 +165,25 @@ const HeaderBar = () => {
}
}
}}>
<Link
className="header-bar-text"
style={{ textDecoration: 'none' }}
to={routerMap[props.itemKey]}
>
{itemElement}
</Link>
{props.isExternal ? (
<a
className="header-bar-text"
style={{ textDecoration: 'none' }}
href={props.externalLink}
target="_blank"
rel="noopener noreferrer"
>
{itemElement}
</a>
) : (
<Link
className="header-bar-text"
style={{ textDecoration: 'none' }}
to={routerMap[props.itemKey]}
>
{itemElement}
</Link>
)}
</div>
);
}}

View File

@@ -34,8 +34,8 @@ const OperationSetting = () => {
GroupRatio: '',
UserUsableGroups: '',
TopUpLink: '',
ChatLink: '',
ChatLink2: '', // 添加的新状态变量
'general_setting.docs_link': '',
// ChatLink2: '', // 添加的新状态变量
QuotaPerUnit: 0,
AutomaticDisableChannelEnabled: false,
AutomaticEnableChannelEnabled: false,

View File

@@ -196,31 +196,28 @@ const SiderBar = () => {
}
setSelectedKeys([localKey]);
let chatLink = localStorage.getItem('chat_link');
if (!chatLink) {
let chats = localStorage.getItem('chats');
if (chats) {
// console.log(chats);
try {
chats = JSON.parse(chats);
if (Array.isArray(chats)) {
let chatItems = [];
for (let i = 0; i < chats.length; i++) {
let chat = {};
for (let key in chats[i]) {
chat.text = key;
chat.itemKey = 'chat' + i;
chat.to = '/chat/' + i;
}
// setRouterMap({ ...routerMap, chat: '/chat/' + i })
chatItems.push(chat);
let chats = localStorage.getItem('chats');
if (chats) {
// console.log(chats);
try {
chats = JSON.parse(chats);
if (Array.isArray(chats)) {
let chatItems = [];
for (let i = 0; i < chats.length; i++) {
let chat = {};
for (let key in chats[i]) {
chat.text = key;
chat.itemKey = 'chat' + i;
chat.to = '/chat/' + i;
}
setChatItems(chatItems);
// setRouterMap({ ...routerMap, chat: '/chat/' + i })
chatItems.push(chat);
}
} catch (e) {
console.error(e);
showError('聊天数据解析失败')
setChatItems(chatItems);
}
} catch (e) {
console.error(e);
showError('聊天数据解析失败')
}
}
@@ -254,24 +251,21 @@ const SiderBar = () => {
}}
selectedKeys={selectedKeys}
renderWrapper={({ itemElement, isSubNav, isInSubNav, props }) => {
let chatLink = localStorage.getItem('chat_link');
if (!chatLink) {
let chats = localStorage.getItem('chats');
if (chats) {
chats = JSON.parse(chats);
if (Array.isArray(chats) && chats.length > 0) {
for (let i = 0; i < chats.length; i++) {
routerMap['chat' + i] = '/chat/' + i;
}
if (chats.length > 1) {
// delete /chat
if (routerMap['chat']) {
delete routerMap['chat'];
}
} else {
// rename /chat to /chat/0
routerMap['chat'] = '/chat/0';
let chats = localStorage.getItem('chats');
if (chats) {
chats = JSON.parse(chats);
if (Array.isArray(chats) && chats.length > 0) {
for (let i = 0; i < chats.length; i++) {
routerMap['chat' + i] = '/chat/' + i;
}
if (chats.length > 1) {
// delete /chat
if (routerMap['chat']) {
delete routerMap['chat'];
}
} else {
// rename /chat to /chat/0
routerMap['chat'] = '/chat/0';
}
}
}

View File

@@ -144,33 +144,8 @@ const TokensTable = () => {
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);

View File

@@ -19,15 +19,20 @@ export function setStatusData(data) {
);
localStorage.setItem('mj_notify_enabled', data.mj_notify_enabled);
if (data.chat_link) {
localStorage.setItem('chat_link', data.chat_link);
// localStorage.setItem('chat_link', data.chat_link);
} else {
localStorage.removeItem('chat_link');
}
if (data.chat_link2) {
localStorage.setItem('chat_link2', data.chat_link2);
// localStorage.setItem('chat_link2', data.chat_link2);
} else {
localStorage.removeItem('chat_link2');
}
if (data.docs_link) {
localStorage.setItem('docs_link', data.docs_link);
} else {
localStorage.removeItem('docs_link');
}
}
export function setUserData(data) {

View File

@@ -10,10 +10,8 @@ const ChatPage = () => {
const comLink = (key) => {
// console.log('chatLink:', chatLink);
if (!serverAddress || !key) return '';
let link = localStorage.getItem('chat_link');
if (link) {
link = `${link}/#/?settings={"key":"sk-${key}","url":"${encodeURIComponent(serverAddress)}"}`;
} else if (id) {
let link = "";
if (id) {
let chats = localStorage.getItem('chats');
if (chats) {
chats = JSON.parse(chats);

View File

@@ -1,5 +1,5 @@
import React, { useEffect, useState, useRef } from 'react';
import { Banner, Button, Col, Form, Row, Spin } from '@douyinfe/semi-ui';
import { Banner, Button, Col, Form, Row, Spin, Collapse, Modal } from '@douyinfe/semi-ui';
import {
compareObjects,
API,
@@ -12,10 +12,10 @@ import { useTranslation } from 'react-i18next';
export default function GeneralSettings(props) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const [showQuotaWarning, setShowQuotaWarning] = useState(false);
const [inputs, setInputs] = useState({
TopUpLink: '',
ChatLink: '',
ChatLink2: '',
'general_setting.docs_link': '',
QuotaPerUnit: '',
RetryTimes: '',
DisplayInCurrencyEnabled: false,
@@ -104,20 +104,10 @@ export default function GeneralSettings(props) {
</Col>
<Col span={8}>
<Form.Input
field={'ChatLink'}
label={t('默认聊天页面链接')}
field={'general_setting.docs_link'}
label={t('文档地址')}
initValue={''}
placeholder={t('例如 ChatGPT Next Web 的部署地址')}
onChange={onChange}
showClear
/>
</Col>
<Col span={8}>
<Form.Input
field={'ChatLink2'}
label={t('聊天页面 2 链接')}
initValue={''}
placeholder={t('例如 ChatGPT Next Web 的部署地址')}
placeholder={t('例如 https://docs.newapi.pro')}
onChange={onChange}
showClear
/>
@@ -130,6 +120,7 @@ export default function GeneralSettings(props) {
placeholder={t('一单位货币能兑换的额度')}
onChange={onChange}
showClear
onClick={() => setShowQuotaWarning(true)}
/>
</Col>
<Col span={8}>
@@ -231,6 +222,23 @@ export default function GeneralSettings(props) {
</Form.Section>
</Form>
</Spin>
<Modal
title={t('警告')}
visible={showQuotaWarning}
onOk={() => setShowQuotaWarning(false)}
onCancel={() => setShowQuotaWarning(false)}
closeOnEsc={true}
width={500}
>
<Banner
type='warning'
description={t('此设置用于系统内部计算默认值500000是为了精确到6位小数点设计不推荐修改。')}
bordered
fullMode={false}
closeIcon={null}
/>
</Modal>
</>
);
}

View File

@@ -16,10 +16,7 @@ const Setting = () => {
const navigate = useNavigate();
const location = useLocation();
const [tabActiveKey, setTabActiveKey] = useState('1');
let panes = [
{
},
];
let panes = [];
if (isRoot()) {
panes.push({