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

@@ -15,8 +15,9 @@ var SystemName = "New API"
var Footer = ""
var Logo = ""
var TopUpLink = ""
var ChatLink = ""
var ChatLink2 = ""
// var ChatLink = ""
// var ChatLink2 = ""
var QuotaPerUnit = 500 * 1000.0 // $0.002 / 1K tokens
var DisplayInCurrencyEnabled = true
var DisplayTokenStatEnabled = true

View File

@@ -186,6 +186,8 @@ func buildTestRequest(model string) *dto.GeneralOpenAIRequest {
// 并非Embedding 模型
if strings.HasPrefix(model, "o1") || strings.HasPrefix(model, "o3") {
testRequest.MaxCompletionTokens = 10
} else if strings.Contains(model, "thinking") {
testRequest.MaxTokens = 50
} else {
testRequest.MaxTokens = 10
}

View File

@@ -54,8 +54,7 @@ func GetStatus(c *gin.Context) {
"turnstile_check": common.TurnstileCheckEnabled,
"turnstile_site_key": common.TurnstileSiteKey,
"top_up_link": common.TopUpLink,
"chat_link": common.ChatLink,
"chat_link2": common.ChatLink2,
"docs_link": operation_setting.GetGeneralSetting().DocsLink,
"quota_per_unit": common.QuotaPerUnit,
"display_in_currency": common.DisplayInCurrencyEnabled,
"enable_batch_update": common.BatchUpdateEnabled,

View File

@@ -99,8 +99,8 @@ func InitOptionMap() {
common.OptionMap["UserUsableGroups"] = setting.UserUsableGroups2JSONString()
common.OptionMap["CompletionRatio"] = operation_setting.CompletionRatio2JSONString()
common.OptionMap["TopUpLink"] = common.TopUpLink
common.OptionMap["ChatLink"] = common.ChatLink
common.OptionMap["ChatLink2"] = common.ChatLink2
//common.OptionMap["ChatLink"] = common.ChatLink
//common.OptionMap["ChatLink2"] = common.ChatLink2
common.OptionMap["QuotaPerUnit"] = strconv.FormatFloat(common.QuotaPerUnit, 'f', -1, 64)
common.OptionMap["RetryTimes"] = strconv.Itoa(common.RetryTimes)
common.OptionMap["DataExportInterval"] = strconv.Itoa(common.DataExportInterval)
@@ -358,10 +358,10 @@ func updateOptionMap(key string, value string) (err error) {
err = operation_setting.UpdateCacheRatioByJSONString(value)
case "TopUpLink":
common.TopUpLink = value
case "ChatLink":
common.ChatLink = value
case "ChatLink2":
common.ChatLink2 = value
//case "ChatLink":
// common.ChatLink = value
//case "ChatLink2":
// common.ChatLink2 = value
case "ChannelDisableThreshold":
common.ChannelDisableThreshold, _ = strconv.ParseFloat(value, 64)
case "QuotaPerUnit":

View File

@@ -0,0 +1,21 @@
package operation_setting
import "one-api/setting/config"
type GeneralSetting struct {
DocsLink string `json:"docs_link"`
}
// 默认配置
var generalSetting = GeneralSetting{
DocsLink: "https://docs.newapi.pro",
}
func init() {
// 注册到全局配置管理器
config.GlobalConfig.Register("general_setting", &generalSetting)
}
func GetGeneralSetting() *GeneralSetting {
return &generalSetting
}

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({