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 Footer = ""
var Logo = "" var Logo = ""
var TopUpLink = "" var TopUpLink = ""
var ChatLink = ""
var ChatLink2 = "" // var ChatLink = ""
// var ChatLink2 = ""
var QuotaPerUnit = 500 * 1000.0 // $0.002 / 1K tokens var QuotaPerUnit = 500 * 1000.0 // $0.002 / 1K tokens
var DisplayInCurrencyEnabled = true var DisplayInCurrencyEnabled = true
var DisplayTokenStatEnabled = true var DisplayTokenStatEnabled = true

View File

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

View File

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

View File

@@ -99,8 +99,8 @@ func InitOptionMap() {
common.OptionMap["UserUsableGroups"] = setting.UserUsableGroups2JSONString() common.OptionMap["UserUsableGroups"] = setting.UserUsableGroups2JSONString()
common.OptionMap["CompletionRatio"] = operation_setting.CompletionRatio2JSONString() common.OptionMap["CompletionRatio"] = operation_setting.CompletionRatio2JSONString()
common.OptionMap["TopUpLink"] = common.TopUpLink common.OptionMap["TopUpLink"] = common.TopUpLink
common.OptionMap["ChatLink"] = common.ChatLink //common.OptionMap["ChatLink"] = common.ChatLink
common.OptionMap["ChatLink2"] = common.ChatLink2 //common.OptionMap["ChatLink2"] = common.ChatLink2
common.OptionMap["QuotaPerUnit"] = strconv.FormatFloat(common.QuotaPerUnit, 'f', -1, 64) common.OptionMap["QuotaPerUnit"] = strconv.FormatFloat(common.QuotaPerUnit, 'f', -1, 64)
common.OptionMap["RetryTimes"] = strconv.Itoa(common.RetryTimes) common.OptionMap["RetryTimes"] = strconv.Itoa(common.RetryTimes)
common.OptionMap["DataExportInterval"] = strconv.Itoa(common.DataExportInterval) common.OptionMap["DataExportInterval"] = strconv.Itoa(common.DataExportInterval)
@@ -358,10 +358,10 @@ func updateOptionMap(key string, value string) (err error) {
err = operation_setting.UpdateCacheRatioByJSONString(value) err = operation_setting.UpdateCacheRatioByJSONString(value)
case "TopUpLink": case "TopUpLink":
common.TopUpLink = value common.TopUpLink = value
case "ChatLink": //case "ChatLink":
common.ChatLink = value // common.ChatLink = value
case "ChatLink2": //case "ChatLink2":
common.ChatLink2 = value // common.ChatLink2 = value
case "ChannelDisableThreshold": case "ChannelDisableThreshold":
common.ChannelDisableThreshold, _ = strconv.ParseFloat(value, 64) common.ChannelDisableThreshold, _ = strconv.ParseFloat(value, 64)
case "QuotaPerUnit": 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 // Check if self-use mode is enabled
const isSelfUseMode = statusState?.status?.self_use_mode_enabled || false; const isSelfUseMode = statusState?.status?.self_use_mode_enabled || false;
const docsLink = statusState?.status?.docs_link || '';
const isDemoSiteMode = statusState?.status?.demo_site_enabled || false; const isDemoSiteMode = statusState?.status?.demo_site_enabled || false;
let buttons = [ let buttons = [
@@ -62,6 +63,13 @@ const HeaderBar = () => {
itemKey: 'pricing', itemKey: 'pricing',
to: '/pricing', to: '/pricing',
}, },
// Only include the docs button if docsLink exists
...(docsLink ? [{
text: t('文档'),
itemKey: 'docs',
isExternal: true,
externalLink: docsLink,
}] : []),
{ {
text: t('关于'), text: t('关于'),
itemKey: 'about', itemKey: 'about',
@@ -157,13 +165,25 @@ const HeaderBar = () => {
} }
} }
}}> }}>
<Link {props.isExternal ? (
className="header-bar-text" <a
style={{ textDecoration: 'none' }} className="header-bar-text"
to={routerMap[props.itemKey]} style={{ textDecoration: 'none' }}
> href={props.externalLink}
{itemElement} target="_blank"
</Link> rel="noopener noreferrer"
>
{itemElement}
</a>
) : (
<Link
className="header-bar-text"
style={{ textDecoration: 'none' }}
to={routerMap[props.itemKey]}
>
{itemElement}
</Link>
)}
</div> </div>
); );
}} }}

View File

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

View File

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

View File

@@ -144,33 +144,8 @@ const TokensTable = () => {
render: (text, record, index) => { render: (text, record, index) => {
let chats = localStorage.getItem('chats'); let chats = localStorage.getItem('chats');
let chatsArray = [] let chatsArray = []
let chatLink = localStorage.getItem('chat_link');
let mjLink = localStorage.getItem('chat_link2');
let shouldUseCustom = true; 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) { if (shouldUseCustom) {
try { try {
// console.log(chats); // console.log(chats);

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
import React, { useEffect, useState, useRef } from 'react'; 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 { import {
compareObjects, compareObjects,
API, API,
@@ -12,10 +12,10 @@ import { useTranslation } from 'react-i18next';
export default function GeneralSettings(props) { export default function GeneralSettings(props) {
const { t } = useTranslation(); const { t } = useTranslation();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [showQuotaWarning, setShowQuotaWarning] = useState(false);
const [inputs, setInputs] = useState({ const [inputs, setInputs] = useState({
TopUpLink: '', TopUpLink: '',
ChatLink: '', 'general_setting.docs_link': '',
ChatLink2: '',
QuotaPerUnit: '', QuotaPerUnit: '',
RetryTimes: '', RetryTimes: '',
DisplayInCurrencyEnabled: false, DisplayInCurrencyEnabled: false,
@@ -104,20 +104,10 @@ export default function GeneralSettings(props) {
</Col> </Col>
<Col span={8}> <Col span={8}>
<Form.Input <Form.Input
field={'ChatLink'} field={'general_setting.docs_link'}
label={t('默认聊天页面链接')} label={t('文档地址')}
initValue={''} initValue={''}
placeholder={t('例如 ChatGPT Next Web 的部署地址')} placeholder={t('例如 https://docs.newapi.pro')}
onChange={onChange}
showClear
/>
</Col>
<Col span={8}>
<Form.Input
field={'ChatLink2'}
label={t('聊天页面 2 链接')}
initValue={''}
placeholder={t('例如 ChatGPT Next Web 的部署地址')}
onChange={onChange} onChange={onChange}
showClear showClear
/> />
@@ -130,6 +120,7 @@ export default function GeneralSettings(props) {
placeholder={t('一单位货币能兑换的额度')} placeholder={t('一单位货币能兑换的额度')}
onChange={onChange} onChange={onChange}
showClear showClear
onClick={() => setShowQuotaWarning(true)}
/> />
</Col> </Col>
<Col span={8}> <Col span={8}>
@@ -231,6 +222,23 @@ export default function GeneralSettings(props) {
</Form.Section> </Form.Section>
</Form> </Form>
</Spin> </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 navigate = useNavigate();
const location = useLocation(); const location = useLocation();
const [tabActiveKey, setTabActiveKey] = useState('1'); const [tabActiveKey, setTabActiveKey] = useState('1');
let panes = [ let panes = [];
{
},
];
if (isRoot()) { if (isRoot()) {
panes.push({ panes.push({