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:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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":
|
||||||
|
|||||||
21
setting/operation_setting/general_setting.go
Normal file
21
setting/operation_setting/general_setting.go
Normal 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
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
Reference in New Issue
Block a user