mirror of
https://github.com/songquanpeng/one-api.git
synced 2025-10-24 02:13:42 +08:00
Compare commits
9 Commits
v0.4.5-alp
...
v0.4.5-alp
Author | SHA1 | Date | |
---|---|---|---|
|
dc94765d32 | ||
|
1cb1f727c0 | ||
|
d97640374c | ||
|
ba89abedf0 | ||
|
a680b1b8b7 | ||
|
b3b7d0a0ea | ||
|
8e805e23bc | ||
|
bcbfacc04a | ||
|
5531e21526 |
1
.github/ISSUE_TEMPLATE/bug_report.md
vendored
1
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -10,6 +10,7 @@ assignees: ''
|
|||||||
**例行检查**
|
**例行检查**
|
||||||
+ [ ] 我已确认目前没有类似 issue
|
+ [ ] 我已确认目前没有类似 issue
|
||||||
+ [ ] 我已确认我已升级到最新版本
|
+ [ ] 我已确认我已升级到最新版本
|
||||||
|
+ [ ] 我已完整查看过项目 README,尤其是常见问题部分
|
||||||
+ [ ] 我理解并愿意跟进此 issue,协助测试和提供反馈
|
+ [ ] 我理解并愿意跟进此 issue,协助测试和提供反馈
|
||||||
+ [ ] 我理解并认可上述内容,并理解项目维护者精力有限,不遵循规则的 issue 可能会被无视或直接关闭
|
+ [ ] 我理解并认可上述内容,并理解项目维护者精力有限,不遵循规则的 issue 可能会被无视或直接关闭
|
||||||
|
|
||||||
|
16
README.md
16
README.md
@@ -155,6 +155,12 @@ sudo service nginx restart
|
|||||||
|
|
||||||
环境变量的具体使用方法详见[此处](#环境变量)。
|
环境变量的具体使用方法详见[此处](#环境变量)。
|
||||||
|
|
||||||
|
### 宝塔部署教程
|
||||||
|
|
||||||
|
详见[#175](https://github.com/songquanpeng/one-api/issues/175)。
|
||||||
|
|
||||||
|
如果部署后访问出现空白页面,详见[#97](https://github.com/songquanpeng/one-api/issues/97)。
|
||||||
|
|
||||||
### 部署第三方服务配合 One API 使用
|
### 部署第三方服务配合 One API 使用
|
||||||
> 欢迎 PR 添加更多示例。
|
> 欢迎 PR 添加更多示例。
|
||||||
|
|
||||||
@@ -256,14 +262,18 @@ https://openai.justsong.cn
|
|||||||

|

|
||||||
|
|
||||||
## 常见问题
|
## 常见问题
|
||||||
1. 账户额度足够为什么提示额度不足?
|
1. 额度是什么?怎么计算的?
|
||||||
|
+ 额度 = token * 倍率
|
||||||
|
+ 倍率包括分组的倍率,以及补全的倍率。
|
||||||
|
2. 账户额度足够为什么提示额度不足?
|
||||||
+ 请检查你的令牌额度是否足够,这个和账户额度是分开的。
|
+ 请检查你的令牌额度是否足够,这个和账户额度是分开的。
|
||||||
+ 令牌额度仅供用户设置最大使用量,用户可自由设置。
|
+ 令牌额度仅供用户设置最大使用量,用户可自由设置。
|
||||||
2. 宝塔部署后访问出现空白页面?
|
|
||||||
+ 自动配置的问题,详见[#97](https://github.com/songquanpeng/one-api/issues/97)。
|
|
||||||
3. 提示无可用渠道?
|
3. 提示无可用渠道?
|
||||||
+ 请检查的用户分组和渠道分组设置。
|
+ 请检查的用户分组和渠道分组设置。
|
||||||
+ 以及渠道的模型设置。
|
+ 以及渠道的模型设置。
|
||||||
|
4. 渠道测试报错:`invalid character '<' looking for beginning of value`
|
||||||
|
+ 这是因为返回值不是合法的 JSON,而是一个 HTML 页面。
|
||||||
|
+ 大概率是你的部署站的 IP 或代理的节点被 CloudFlare 封禁了。
|
||||||
|
|
||||||
## 注意
|
## 注意
|
||||||
本项目为开源项目,请在遵循 OpenAI 的[使用条款](https://openai.com/policies/terms-of-use)以及法律法规的情况下使用,不得用于非法用途。
|
本项目为开源项目,请在遵循 OpenAI 的[使用条款](https://openai.com/policies/terms-of-use)以及法律法规的情况下使用,不得用于非法用途。
|
||||||
|
@@ -14,6 +14,7 @@ var ServerAddress = "http://localhost:3000"
|
|||||||
var Footer = ""
|
var Footer = ""
|
||||||
var Logo = ""
|
var Logo = ""
|
||||||
var TopUpLink = ""
|
var TopUpLink = ""
|
||||||
|
var ChatLink = ""
|
||||||
|
|
||||||
var UsingSQLite = false
|
var UsingSQLite = false
|
||||||
|
|
||||||
|
@@ -143,7 +143,6 @@ func updateChannelAPI2GPTBalance(channel *model.Channel) (float64, error) {
|
|||||||
}
|
}
|
||||||
response := API2GPTUsageResponse{}
|
response := API2GPTUsageResponse{}
|
||||||
err = json.Unmarshal(body, &response)
|
err = json.Unmarshal(body, &response)
|
||||||
fmt.Print(response)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
@@ -14,12 +14,12 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testChannel(channel *model.Channel, request *ChatRequest) error {
|
func testChannel(channel *model.Channel, request ChatRequest) error {
|
||||||
if request.Model == "" {
|
switch channel.Type {
|
||||||
|
case common.ChannelTypeAzure:
|
||||||
|
request.Model = "gpt-35-turbo"
|
||||||
|
default:
|
||||||
request.Model = "gpt-3.5-turbo"
|
request.Model = "gpt-3.5-turbo"
|
||||||
if channel.Type == common.ChannelTypeAzure {
|
|
||||||
request.Model = "gpt-35-turbo"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
requestURL := common.ChannelBaseURLs[channel.Type]
|
requestURL := common.ChannelBaseURLs[channel.Type]
|
||||||
if channel.Type == common.ChannelTypeAzure {
|
if channel.Type == common.ChannelTypeAzure {
|
||||||
@@ -97,7 +97,7 @@ func TestChannel(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
testRequest := buildTestRequest(c)
|
testRequest := buildTestRequest(c)
|
||||||
tik := time.Now()
|
tik := time.Now()
|
||||||
err = testChannel(channel, testRequest)
|
err = testChannel(channel, *testRequest)
|
||||||
tok := time.Now()
|
tok := time.Now()
|
||||||
milliseconds := tok.Sub(tik).Milliseconds()
|
milliseconds := tok.Sub(tik).Milliseconds()
|
||||||
go channel.UpdateResponseTime(milliseconds)
|
go channel.UpdateResponseTime(milliseconds)
|
||||||
@@ -165,7 +165,7 @@ func testAllChannels(c *gin.Context) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
tik := time.Now()
|
tik := time.Now()
|
||||||
err := testChannel(channel, testRequest)
|
err := testChannel(channel, *testRequest)
|
||||||
tok := time.Now()
|
tok := time.Now()
|
||||||
milliseconds := tok.Sub(tik).Milliseconds()
|
milliseconds := tok.Sub(tik).Milliseconds()
|
||||||
if err != nil || milliseconds > disableThreshold {
|
if err != nil || milliseconds > disableThreshold {
|
||||||
|
@@ -28,6 +28,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,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
|
@@ -63,6 +63,7 @@ func InitOptionMap() {
|
|||||||
common.OptionMap["ModelRatio"] = common.ModelRatio2JSONString()
|
common.OptionMap["ModelRatio"] = common.ModelRatio2JSONString()
|
||||||
common.OptionMap["GroupRatio"] = common.GroupRatio2JSONString()
|
common.OptionMap["GroupRatio"] = common.GroupRatio2JSONString()
|
||||||
common.OptionMap["TopUpLink"] = common.TopUpLink
|
common.OptionMap["TopUpLink"] = common.TopUpLink
|
||||||
|
common.OptionMap["ChatLink"] = common.ChatLink
|
||||||
common.OptionMapRWMutex.Unlock()
|
common.OptionMapRWMutex.Unlock()
|
||||||
loadOptionsFromDatabase()
|
loadOptionsFromDatabase()
|
||||||
}
|
}
|
||||||
@@ -191,6 +192,8 @@ func updateOptionMap(key string, value string) (err error) {
|
|||||||
err = common.UpdateGroupRatioByJSONString(value)
|
err = common.UpdateGroupRatioByJSONString(value)
|
||||||
case "TopUpLink":
|
case "TopUpLink":
|
||||||
common.TopUpLink = value
|
common.TopUpLink = value
|
||||||
|
case "ChatLink":
|
||||||
|
common.ChatLink = value
|
||||||
case "ChannelDisableThreshold":
|
case "ChannelDisableThreshold":
|
||||||
common.ChannelDisableThreshold, _ = strconv.ParseFloat(value, 64)
|
common.ChannelDisableThreshold, _ = strconv.ParseFloat(value, 64)
|
||||||
}
|
}
|
||||||
|
@@ -23,6 +23,7 @@ import Redemption from './pages/Redemption';
|
|||||||
import EditRedemption from './pages/Redemption/EditRedemption';
|
import EditRedemption from './pages/Redemption/EditRedemption';
|
||||||
import TopUp from './pages/TopUp';
|
import TopUp from './pages/TopUp';
|
||||||
import Log from './pages/Log';
|
import Log from './pages/Log';
|
||||||
|
import Chat from './pages/Chat';
|
||||||
|
|
||||||
const Home = lazy(() => import('./pages/Home'));
|
const Home = lazy(() => import('./pages/Home'));
|
||||||
const About = lazy(() => import('./pages/About'));
|
const About = lazy(() => import('./pages/About'));
|
||||||
@@ -47,6 +48,11 @@ function App() {
|
|||||||
localStorage.setItem('system_name', data.system_name);
|
localStorage.setItem('system_name', data.system_name);
|
||||||
localStorage.setItem('logo', data.logo);
|
localStorage.setItem('logo', data.logo);
|
||||||
localStorage.setItem('footer_html', data.footer_html);
|
localStorage.setItem('footer_html', data.footer_html);
|
||||||
|
if (data.chat_link) {
|
||||||
|
localStorage.setItem('chat_link', data.chat_link);
|
||||||
|
} else {
|
||||||
|
localStorage.removeItem('chat_link');
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
data.version !== process.env.REACT_APP_VERSION &&
|
data.version !== process.env.REACT_APP_VERSION &&
|
||||||
data.version !== 'v0.0.0' &&
|
data.version !== 'v0.0.0' &&
|
||||||
@@ -267,6 +273,14 @@ function App() {
|
|||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
path='/chat'
|
||||||
|
element={
|
||||||
|
<Suspense fallback={<Loading></Loading>}>
|
||||||
|
<Chat />
|
||||||
|
</Suspense>
|
||||||
|
}
|
||||||
|
/>
|
||||||
<Route path='*' element={NotFound} />
|
<Route path='*' element={NotFound} />
|
||||||
</Routes>
|
</Routes>
|
||||||
);
|
);
|
||||||
|
@@ -262,7 +262,7 @@ const ChannelsTable = () => {
|
|||||||
/>
|
/>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
<Table basic>
|
<Table basic compact size='small'>
|
||||||
<Table.Header>
|
<Table.Header>
|
||||||
<Table.Row>
|
<Table.Row>
|
||||||
<Table.HeaderCell
|
<Table.HeaderCell
|
||||||
|
@@ -7,57 +7,65 @@ import { API, getLogo, getSystemName, isAdmin, isMobile, showSuccess } from '../
|
|||||||
import '../index.css';
|
import '../index.css';
|
||||||
|
|
||||||
// Header Buttons
|
// Header Buttons
|
||||||
const headerButtons = [
|
let headerButtons = [
|
||||||
{
|
{
|
||||||
name: '首页',
|
name: '首页',
|
||||||
to: '/',
|
to: '/',
|
||||||
icon: 'home',
|
icon: 'home'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '渠道',
|
name: '渠道',
|
||||||
to: '/channel',
|
to: '/channel',
|
||||||
icon: 'sitemap',
|
icon: 'sitemap',
|
||||||
admin: true,
|
admin: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '令牌',
|
name: '令牌',
|
||||||
to: '/token',
|
to: '/token',
|
||||||
icon: 'key',
|
icon: 'key'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '兑换',
|
name: '兑换',
|
||||||
to: '/redemption',
|
to: '/redemption',
|
||||||
icon: 'dollar sign',
|
icon: 'dollar sign',
|
||||||
admin: true,
|
admin: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '充值',
|
name: '充值',
|
||||||
to: '/topup',
|
to: '/topup',
|
||||||
icon: 'cart',
|
icon: 'cart'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '用户',
|
name: '用户',
|
||||||
to: '/user',
|
to: '/user',
|
||||||
icon: 'user',
|
icon: 'user',
|
||||||
admin: true,
|
admin: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '日志',
|
name: '日志',
|
||||||
to: '/log',
|
to: '/log',
|
||||||
icon: 'book',
|
icon: 'book'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '设置',
|
name: '设置',
|
||||||
to: '/setting',
|
to: '/setting',
|
||||||
icon: 'setting',
|
icon: 'setting'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '关于',
|
name: '关于',
|
||||||
to: '/about',
|
to: '/about',
|
||||||
icon: 'info circle',
|
icon: 'info circle'
|
||||||
},
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if (localStorage.getItem('chat_link')) {
|
||||||
|
headerButtons.splice(1, 0, {
|
||||||
|
name: '聊天',
|
||||||
|
to: '/chat',
|
||||||
|
icon: 'comments'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
const [userState, userDispatch] = useContext(UserContext);
|
const [userState, userDispatch] = useContext(UserContext);
|
||||||
let navigate = useNavigate();
|
let navigate = useNavigate();
|
||||||
@@ -112,11 +120,11 @@ const Header = () => {
|
|||||||
style={
|
style={
|
||||||
showSidebar
|
showSidebar
|
||||||
? {
|
? {
|
||||||
borderBottom: 'none',
|
borderBottom: 'none',
|
||||||
marginBottom: '0',
|
marginBottom: '0',
|
||||||
borderTop: 'none',
|
borderTop: 'none',
|
||||||
height: '51px',
|
height: '51px'
|
||||||
}
|
}
|
||||||
: { borderTop: 'none', height: '52px' }
|
: { borderTop: 'none', height: '52px' }
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@@ -165,7 +165,7 @@ const OtherSetting = () => {
|
|||||||
/>
|
/>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
<Form.Button onClick={submitAbout}>保存关于</Form.Button>
|
<Form.Button onClick={submitAbout}>保存关于</Form.Button>
|
||||||
<Message>移除 One API 的版权标识必须首先获得授权,后续版本将通过授权码强制执行。</Message>
|
<Message>移除 One API 的版权标识必须首先获得授权,项目维护需要花费大量精力,如果本项目对你有意义,请主动支持本项目。</Message>
|
||||||
<Form.Group widths='equal'>
|
<Form.Group widths='equal'>
|
||||||
<Form.Input
|
<Form.Input
|
||||||
label='页脚'
|
label='页脚'
|
||||||
|
@@ -152,7 +152,7 @@ const RedemptionsTable = () => {
|
|||||||
/>
|
/>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
<Table basic>
|
<Table basic compact size='small'>
|
||||||
<Table.Header>
|
<Table.Header>
|
||||||
<Table.Row>
|
<Table.Row>
|
||||||
<Table.HeaderCell
|
<Table.HeaderCell
|
||||||
|
@@ -34,6 +34,7 @@ const SystemSetting = () => {
|
|||||||
ModelRatio: '',
|
ModelRatio: '',
|
||||||
GroupRatio: '',
|
GroupRatio: '',
|
||||||
TopUpLink: '',
|
TopUpLink: '',
|
||||||
|
ChatLink: '',
|
||||||
AutomaticDisableChannelEnabled: '',
|
AutomaticDisableChannelEnabled: '',
|
||||||
ChannelDisableThreshold: 0,
|
ChannelDisableThreshold: 0,
|
||||||
LogConsumeEnabled: ''
|
LogConsumeEnabled: ''
|
||||||
@@ -109,7 +110,8 @@ const SystemSetting = () => {
|
|||||||
name === 'PreConsumedQuota' ||
|
name === 'PreConsumedQuota' ||
|
||||||
name === 'ModelRatio' ||
|
name === 'ModelRatio' ||
|
||||||
name === 'GroupRatio' ||
|
name === 'GroupRatio' ||
|
||||||
name === 'TopUpLink'
|
name === 'TopUpLink' ||
|
||||||
|
name === 'ChatLink'
|
||||||
) {
|
) {
|
||||||
setInputs((inputs) => ({ ...inputs, [name]: value }));
|
setInputs((inputs) => ({ ...inputs, [name]: value }));
|
||||||
} else {
|
} else {
|
||||||
@@ -155,6 +157,9 @@ const SystemSetting = () => {
|
|||||||
if (originInputs['TopUpLink'] !== inputs.TopUpLink) {
|
if (originInputs['TopUpLink'] !== inputs.TopUpLink) {
|
||||||
await updateOption('TopUpLink', inputs.TopUpLink);
|
await updateOption('TopUpLink', inputs.TopUpLink);
|
||||||
}
|
}
|
||||||
|
if (originInputs['ChatLink'] !== inputs.ChatLink) {
|
||||||
|
await updateOption('ChatLink', inputs.ChatLink);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const submitSMTP = async () => {
|
const submitSMTP = async () => {
|
||||||
@@ -360,6 +365,15 @@ const SystemSetting = () => {
|
|||||||
min='0'
|
min='0'
|
||||||
placeholder='例如:100'
|
placeholder='例如:100'
|
||||||
/>
|
/>
|
||||||
|
<Form.Input
|
||||||
|
label='聊天页面链接'
|
||||||
|
name='ChatLink'
|
||||||
|
onChange={handleInputChange}
|
||||||
|
autoComplete='new-password'
|
||||||
|
value={inputs.ChatLink}
|
||||||
|
type='link'
|
||||||
|
placeholder='例如 ChatGPT Next Web 的部署地址'
|
||||||
|
/>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
<Form.Group widths='equal'>
|
<Form.Group widths='equal'>
|
||||||
<Form.TextArea
|
<Form.TextArea
|
||||||
|
@@ -161,7 +161,7 @@ const TokensTable = () => {
|
|||||||
/>
|
/>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
<Table basic>
|
<Table basic compact size='small'>
|
||||||
<Table.Header>
|
<Table.Header>
|
||||||
<Table.Row>
|
<Table.Row>
|
||||||
<Table.HeaderCell
|
<Table.HeaderCell
|
||||||
|
@@ -156,7 +156,7 @@ const UsersTable = () => {
|
|||||||
/>
|
/>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
<Table basic>
|
<Table basic compact size='small'>
|
||||||
<Table.Header>
|
<Table.Header>
|
||||||
<Table.Row>
|
<Table.Row>
|
||||||
<Table.HeaderCell
|
<Table.HeaderCell
|
||||||
@@ -240,7 +240,9 @@ const UsersTable = () => {
|
|||||||
/>
|
/>
|
||||||
</Table.Cell>
|
</Table.Cell>
|
||||||
<Table.Cell>{renderGroup(user.group)}</Table.Cell>
|
<Table.Cell>{renderGroup(user.group)}</Table.Cell>
|
||||||
<Table.Cell>{user.email ? renderText(user.email, 20) : '无'}</Table.Cell>
|
<Table.Cell>
|
||||||
|
{user.email ? <Popup hoverable content={user.email} trigger={<span>{renderText(user.email, 24)}</span>} /> : '无'}
|
||||||
|
</Table.Cell>
|
||||||
<Table.Cell>
|
<Table.Cell>
|
||||||
<Popup content='剩余额度' trigger={<Label>{renderNumber(user.quota)}</Label>} />
|
<Popup content='剩余额度' trigger={<Label>{renderNumber(user.quota)}</Label>} />
|
||||||
<Popup content='已用额度' trigger={<Label>{renderNumber(user.used_quota)}</Label>} />
|
<Popup content='已用额度' trigger={<Label>{renderNumber(user.used_quota)}</Label>} />
|
||||||
|
15
web/src/pages/Chat/index.js
Normal file
15
web/src/pages/Chat/index.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const Chat = () => {
|
||||||
|
const chatLink = localStorage.getItem('chat_link');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<iframe
|
||||||
|
src={chatLink}
|
||||||
|
style={{ width: '100%', height: '85vh', border: 'none' }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export default Chat;
|
Reference in New Issue
Block a user