Compare commits

..

6 Commits

Author SHA1 Message Date
JustSong
2cdc718fde feat: able to use any link as about page (#60) 2023-05-14 18:58:54 +08:00
JustSong
57cb150177 perf: load cached about content first (#60) 2023-05-14 16:13:42 +08:00
JustSong
6167e20b34 style: hide scroll bar 2023-05-14 16:02:40 +08:00
JustSong
8835d8302e chore: fix typo 2023-05-14 16:01:04 +08:00
JustSong
224bebe67a feat: able to customize home page with link (close #60) 2023-05-14 15:34:14 +08:00
JustSong
cf6883778e perf: use slice to improve efficiency (#57) 2023-05-14 12:53:03 +08:00
6 changed files with 124 additions and 93 deletions

View File

@@ -43,8 +43,8 @@ _✨ All in one 的 OpenAI 接口,整合各种 API 访问方式,开箱即用
## 功能 ## 功能
1. 支持多种 API 访问渠道,欢迎 PR 或提 issue 添加更多渠道: 1. 支持多种 API 访问渠道,欢迎 PR 或提 issue 添加更多渠道:
+ [x] OpenAI 官方通道 + [x] OpenAI 官方通道
+ [x] [API2D](https://api2d.com/r/197971)
+ [x] Azure OpenAI API + [x] Azure OpenAI API
+ [x] [API2D](https://api2d.com/r/197971)
+ [x] [CloseAI](https://console.openai-asia.com) + [x] [CloseAI](https://console.openai-asia.com)
+ [x] [OpenAI-SB](https://openai-sb.com) + [x] [OpenAI-SB](https://openai-sb.com)
+ [x] [OpenAI Max](https://openaimax.com) + [x] [OpenAI Max](https://openaimax.com)
@@ -55,15 +55,16 @@ _✨ All in one 的 OpenAI 接口,整合各种 API 访问方式,开箱即用
4. 支持 HTTP SSE可以通过流式传输实现打字机效果。 4. 支持 HTTP SSE可以通过流式传输实现打字机效果。
5. 支持设置令牌的过期时间和使用次数。 5. 支持设置令牌的过期时间和使用次数。
6. 支持批量生成和导出兑换码,可使用兑换码为令牌进行充值。 6. 支持批量生成和导出兑换码,可使用兑换码为令牌进行充值。
7. 支持为新用户设置初始配额 7. 支持批量创建通道
8. 支持自定义首页,发布公告,自定义关于页面,设置充值链接,自定义页脚。 8. 支持发布公告,自定义关于页面,设置充值链接,自定义页脚。
9. 支持通过系统访问令牌访问管理 API 9. 支持自定义首页,可以选择使用 HTML & Markdown 代码进行自定义,或者使用一个单独的网页通过 iframe 嵌入
10. 多种用户登录注册方式: 10. 支持通过系统访问令牌访问管理 API。
11. 多种用户登录注册方式:
+ 邮箱登录注册以及通过邮箱进行密码重置。 + 邮箱登录注册以及通过邮箱进行密码重置。
+ [GitHub 开放授权](https://github.com/settings/applications/new)。 + [GitHub 开放授权](https://github.com/settings/applications/new)。
+ 微信公众号授权(需要额外部署 [WeChat Server](https://github.com/songquanpeng/wechat-server))。 + 微信公众号授权(需要额外部署 [WeChat Server](https://github.com/songquanpeng/wechat-server))。
11. 支持用户管理。 12. 支持用户管理,支持为新用户设置初始配额。
12. 未来其他大模型开放 API 后,将第一时间支持,并将其封装成同样的 API 访问方式。 13. 未来其他大模型开放 API 后,将第一时间支持,并将其封装成同样的 API 访问方式。
## 部署 ## 部署
### 基于 Docker 进行部署 ### 基于 Docker 进行部署

View File

@@ -210,7 +210,7 @@ func relayHelper(c *gin.Context) error {
select { select {
case data := <-dataChan: case data := <-dataChan:
if strings.HasPrefix(data, "data: [DONE]") { if strings.HasPrefix(data, "data: [DONE]") {
data = "data: [DONE]" data = data[:12]
} }
c.Render(-1, common.CustomEvent{Data: data}) c.Render(-1, common.CustomEvent{Data: data})
return true return true

View File

@@ -117,18 +117,18 @@ const OtherSetting = () => {
<Form.Group widths='equal'> <Form.Group widths='equal'>
<Form.TextArea <Form.TextArea
label='首页内容' label='首页内容'
placeholder='在此输入首页内容,支持 Markdown & HTML 代码,设置后首页的状态信息将不再显示' placeholder='在此输入首页内容,支持 Markdown & HTML 代码,设置后首页的状态信息将不再显示。如果输入的是一个链接,则会使用该链接作为 iframe 的 src 属性,这允许你设置任意网页作为首页。'
value={inputs.HomePageContent} value={inputs.HomePageContent}
name='HomePageContent' name='HomePageContent'
onChange={handleInputChange} onChange={handleInputChange}
style={{ minHeight: 300, fontFamily: 'JetBrains Mono, Consolas' }} style={{ minHeight: 150, fontFamily: 'JetBrains Mono, Consolas' }}
/> />
</Form.Group> </Form.Group>
<Form.Button onClick={()=>submitOption('HomePageContent')}>保存首页内容</Form.Button> <Form.Button onClick={()=>submitOption('HomePageContent')}>保存首页内容</Form.Button>
<Form.Group widths='equal'> <Form.Group widths='equal'>
<Form.TextArea <Form.TextArea
label='关于' label='关于'
placeholder='在此输入新的关于内容,支持 Markdown & HTML 代码' placeholder='在此输入新的关于内容,支持 Markdown & HTML 代码。如果输入的是一个链接,则会使用该链接作为 iframe 的 src 属性,这允许你设置任意网页作为关于页面。'
value={inputs.About} value={inputs.About}
name='About' name='About'
onChange={handleInputChange} onChange={handleInputChange}

View File

@@ -5,6 +5,11 @@ body {
font-family: Lato, 'Helvetica Neue', Arial, Helvetica, "Microsoft YaHei", sans-serif; font-family: Lato, 'Helvetica Neue', Arial, Helvetica, "Microsoft YaHei", sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
scrollbar-width: none;
}
body::-webkit-scrollbar {
display: none;
} }
code { code {

View File

@@ -5,18 +5,24 @@ import { marked } from 'marked';
const About = () => { const About = () => {
const [about, setAbout] = useState(''); const [about, setAbout] = useState('');
const [aboutLoaded, setAboutLoaded] = useState(false);
const displayAbout = async () => { const displayAbout = async () => {
setAbout(localStorage.getItem('about') || '');
const res = await API.get('/api/about'); const res = await API.get('/api/about');
const { success, message, data } = res.data; const { success, message, data } = res.data;
if (success) { if (success) {
let HTMLAbout = marked.parse(data); let aboutContent = data;
localStorage.setItem('about', HTMLAbout); if (!data.startsWith('https://')) {
setAbout(HTMLAbout); aboutContent = marked.parse(data);
}
setAbout(aboutContent);
localStorage.setItem('about', aboutContent);
} else { } else {
showError(message); showError(message);
setAbout('加载关于内容失败...'); setAbout('加载关于内容失败...');
} }
setAboutLoaded(true);
}; };
useEffect(() => { useEffect(() => {
@@ -25,20 +31,27 @@ const About = () => {
return ( return (
<> <>
<Segment> {
{ aboutLoaded && about === '' ? <>
about === '' ? <> <Segment>
<Header as='h3'>关于</Header> <Header as='h3'>关于</Header>
<p>可在设置页面设置关于内容支持 HTML & Markdown</p> <p>可在设置页面设置关于内容支持 HTML & Markdown</p>
项目仓库地址 项目仓库地址
<a href="https://github.com/songquanpeng/one-api"> <a href='https://github.com/songquanpeng/one-api'>
https://github.com/songquanpeng/one-api https://github.com/songquanpeng/one-api
</a> </a>
</> : <> </Segment>
<div dangerouslySetInnerHTML={{ __html: about}}></div> </> : <>
</> {
} about.startsWith('https://') ? <iframe
</Segment> src={about}
style={{ width: '100%', height: '100vh', border: 'none' }}
/> : <Segment>
<div style={{ fontSize: 'larger' }} dangerouslySetInnerHTML={{ __html: about }}></div>
</Segment>
}
</>
}
</> </>
); );
}; };

View File

@@ -6,6 +6,7 @@ import { marked } from 'marked';
const Home = () => { const Home = () => {
const [statusState, statusDispatch] = useContext(StatusContext); const [statusState, statusDispatch] = useContext(StatusContext);
const [homePageContentLoaded, setHomePageContentLoaded] = useState(false);
const [homePageContent, setHomePageContent] = useState(''); const [homePageContent, setHomePageContent] = useState('');
const displayNotice = async () => { const displayNotice = async () => {
@@ -23,16 +24,21 @@ const Home = () => {
}; };
const displayHomePageContent = async () => { const displayHomePageContent = async () => {
setHomePageContent(localStorage.getItem('home_page_content') || '');
const res = await API.get('/api/home_page_content'); const res = await API.get('/api/home_page_content');
const { success, message, data } = res.data; const { success, message, data } = res.data;
if (success) { if (success) {
let HTMLContent = marked.parse(data); let content = data;
localStorage.setItem('home_page_content', HTMLContent); if (!data.startsWith('https://')) {
setHomePageContent(HTMLContent); content = marked.parse(data);
}
setHomePageContent(content);
localStorage.setItem('home_page_content', content);
} else { } else {
showError(message); showError(message);
setHomePageContent('加载首页内容失败...'); setHomePageContent('加载首页内容失败...');
} }
setHomePageContentLoaded(true);
}; };
const getStartTimeString = () => { const getStartTimeString = () => {
@@ -46,73 +52,79 @@ const Home = () => {
}, []); }, []);
return ( return (
<> <>
<Segment> {
{ homePageContentLoaded && homePageContent === '' ? <>
homePageContent === '' ? <> <Segment>
<Header as='h3'>系统状况</Header> <Header as='h3'>系统状况</Header>
<Grid columns={2} stackable> <Grid columns={2} stackable>
<Grid.Column> <Grid.Column>
<Card fluid> <Card fluid>
<Card.Content> <Card.Content>
<Card.Header>系统信息</Card.Header> <Card.Header>系统信息</Card.Header>
<Card.Meta>系统信息总览</Card.Meta> <Card.Meta>系统信息总览</Card.Meta>
<Card.Description> <Card.Description>
<p>名称{statusState?.status?.system_name}</p> <p>名称{statusState?.status?.system_name}</p>
<p>版本{statusState?.status?.version}</p> <p>版本{statusState?.status?.version}</p>
<p> <p>
源码 源码
<a <a
href='https://github.com/songquanpeng/one-api' href='https://github.com/songquanpeng/one-api'
target='_blank' target='_blank'
> >
https://github.com/songquanpeng/one-api https://github.com/songquanpeng/one-api
</a> </a>
</p> </p>
<p>启动时间{getStartTimeString()}</p> <p>启动时间{getStartTimeString()}</p>
</Card.Description> </Card.Description>
</Card.Content> </Card.Content>
</Card> </Card>
</Grid.Column> </Grid.Column>
<Grid.Column> <Grid.Column>
<Card fluid> <Card fluid>
<Card.Content> <Card.Content>
<Card.Header>系统配置</Card.Header> <Card.Header>系统配置</Card.Header>
<Card.Meta>系统配置总览</Card.Meta> <Card.Meta>系统配置总览</Card.Meta>
<Card.Description> <Card.Description>
<p> <p>
邮箱验证 邮箱验证
{statusState?.status?.email_verification === true {statusState?.status?.email_verification === true
? '已启用' ? '已启用'
: '未启用'} : '未启用'}
</p> </p>
<p> <p>
GitHub 身份验证 GitHub 身份验证
{statusState?.status?.github_oauth === true {statusState?.status?.github_oauth === true
? '已启用' ? '已启用'
: '未启用'} : '未启用'}
</p> </p>
<p> <p>
微信身份验证 微信身份验证
{statusState?.status?.wechat_login === true {statusState?.status?.wechat_login === true
? '已启用' ? '已启用'
: '未启用'} : '未启用'}
</p> </p>
<p> <p>
Turnstile 用户校验 Turnstile 用户校验
{statusState?.status?.turnstile_check === true {statusState?.status?.turnstile_check === true
? '已启用' ? '已启用'
: '未启用'} : '未启用'}
</p> </p>
</Card.Description> </Card.Description>
</Card.Content> </Card.Content>
</Card> </Card>
</Grid.Column> </Grid.Column>
</Grid> </Grid>
</> : <> </Segment>
<div dangerouslySetInnerHTML={{ __html: homePageContent}}></div> </> : <>
</> {
} homePageContent.startsWith('https://') ? <iframe
</Segment> src={homePageContent}
style={{ width: '100%', height: '100vh', border: 'none' }}
/> : <div style={{ fontSize: 'larger' }} dangerouslySetInnerHTML={{ __html: homePageContent }}></div>
}
</>
}
</> </>
); );
}; };