mirror of
https://github.com/yangjian102621/geekai.git
synced 2025-09-17 16:56:38 +08:00
优化 ChatGPT API 重试逻辑
This commit is contained in:
parent
0ca104bac8
commit
3bb6814493
@ -6,5 +6,9 @@
|
|||||||
|
|
||||||
* [ ] 使用 level DB 保存用户聊天的上下文
|
* [ ] 使用 level DB 保存用户聊天的上下文
|
||||||
* [ ] 使用 MySQL 保存用户的聊天的历史记录
|
* [ ] 使用 MySQL 保存用户的聊天的历史记录
|
||||||
* [ ] 用户聊天鉴权
|
* [ ] 用户聊天鉴权,设置口令模式
|
||||||
|
* [ ] 每次连接自动加载历史记录
|
||||||
|
* [ ] 角色设定,预设一些角色,比如程序员,产品经理,医生,作家,老师...
|
||||||
|
* [ ] markdown 语法解析
|
||||||
|
* [ ] 用户配置界面
|
||||||
|
|
||||||
|
@ -10,7 +10,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"openai/types"
|
"openai/types"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -74,34 +73,32 @@ func (s *Server) sendMessage(userId string, text string, ws Client) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: API KEY 负载均衡
|
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
index := rand.Intn(len(s.Config.Chat.ApiKeys))
|
|
||||||
logger.Infof("Use API KEY: %s", s.Config.Chat.ApiKeys[index])
|
|
||||||
request.Header.Add("Content-Type", "application/json")
|
request.Header.Add("Content-Type", "application/json")
|
||||||
request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", s.Config.Chat.ApiKeys[index]))
|
// 随机获取一个 API Key,如果请求失败,则更换 API Key 重试
|
||||||
|
// TODO: 需要将失败的 Key 移除列表
|
||||||
uri := url.URL{}
|
rand.Seed(time.Now().UnixNano())
|
||||||
proxy, _ := uri.Parse(s.Config.ProxyURL)
|
|
||||||
client := &http.Client{
|
|
||||||
Transport: &http.Transport{
|
|
||||||
Proxy: http.ProxyURL(proxy),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
response, err := client.Do(request)
|
|
||||||
var retryCount = 3
|
var retryCount = 3
|
||||||
for err != nil {
|
var response *http.Response
|
||||||
if retryCount <= 0 {
|
for retryCount > 0 {
|
||||||
return err
|
index := rand.Intn(len(s.Config.Chat.ApiKeys))
|
||||||
|
apiKey := s.Config.Chat.ApiKeys[index]
|
||||||
|
logger.Infof("Use API KEY: %s", apiKey)
|
||||||
|
request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey))
|
||||||
|
response, err = s.Client.Do(request)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
logger.Error(err)
|
||||||
}
|
}
|
||||||
response, err = client.Do(request)
|
|
||||||
retryCount--
|
retryCount--
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
var message = types.Message{}
|
var message = types.Message{}
|
||||||
var contents = make([]string, 0)
|
var contents = make([]string, 0)
|
||||||
var responseBody = types.ApiResponse{}
|
var responseBody = types.ApiResponse{}
|
||||||
|
|
||||||
reader := bufio.NewReader(response.Body)
|
reader := bufio.NewReader(response.Body)
|
||||||
for {
|
for {
|
||||||
line, err := reader.ReadString('\n')
|
line, err := reader.ReadString('\n')
|
||||||
@ -119,7 +116,7 @@ func (s *Server) sendMessage(userId string, text string, ws Client) error {
|
|||||||
|
|
||||||
err = json.Unmarshal([]byte(line[6:]), &responseBody)
|
err = json.Unmarshal([]byte(line[6:]), &responseBody)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(err)
|
logger.Error(line)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// 初始化 role
|
// 初始化 role
|
||||||
|
@ -73,6 +73,11 @@ func (s *Server) ConfigSetHandle(c *gin.Context) {
|
|||||||
|
|
||||||
// 保存配置文件
|
// 保存配置文件
|
||||||
logger.Infof("Config: %+v", s.Config)
|
logger.Infof("Config: %+v", s.Config)
|
||||||
types.SaveConfig(s.Config, s.ConfigPath)
|
err = types.SaveConfig(s.Config, s.ConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusOK, types.BizVo{Code: types.Failed, Message: "Failed to save config file"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, types.BizVo{Code: types.Success, Message: types.OkMsg})
|
c.JSON(http.StatusOK, types.BizVo{Code: types.Success, Message: types.OkMsg})
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"io/fs"
|
"io/fs"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
logger2 "openai/logger"
|
logger2 "openai/logger"
|
||||||
"openai/types"
|
"openai/types"
|
||||||
"os"
|
"os"
|
||||||
@ -32,6 +33,7 @@ func (s StaticFile) Open(name string) (fs.File, error) {
|
|||||||
type Server struct {
|
type Server struct {
|
||||||
Config *types.Config
|
Config *types.Config
|
||||||
ConfigPath string
|
ConfigPath string
|
||||||
|
Client *http.Client
|
||||||
History map[string][]types.Message
|
History map[string][]types.Message
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,8 +43,17 @@ func NewServer(configPath string) (*Server, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uri := url.URL{}
|
||||||
|
proxy, _ := uri.Parse(config.ProxyURL)
|
||||||
|
client := &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
Proxy: http.ProxyURL(proxy),
|
||||||
|
},
|
||||||
|
}
|
||||||
return &Server{
|
return &Server{
|
||||||
Config: config,
|
Config: config,
|
||||||
|
Client: client,
|
||||||
ConfigPath: configPath,
|
ConfigPath: configPath,
|
||||||
History: make(map[string][]types.Message, 16)}, nil
|
History: make(map[string][]types.Message, 16)}, nil
|
||||||
}
|
}
|
||||||
|
@ -1 +1 @@
|
|||||||
VUE_APP_WS_HOST=ws://127.0.0.1:5678
|
VUE_APP_WS_HOST=ws://172.22.11.200:5678
|
@ -34,7 +34,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
<style lang="stylus">
|
<style lang="stylus">
|
||||||
.chat-line-right {
|
.chat-line-right {
|
||||||
justify-content: end;
|
justify-content: flex-end;
|
||||||
|
|
||||||
.chat-icon {
|
.chat-icon {
|
||||||
margin-left 5px;
|
margin-left 5px;
|
||||||
|
@ -6,10 +6,7 @@
|
|||||||
|
|
||||||
<div class="chat-item">
|
<div class="chat-item">
|
||||||
<div class="triangle"></div>
|
<div class="triangle"></div>
|
||||||
<div class="content">
|
<div class="content" v-html="content"></div>
|
||||||
<span v-html="content"></span>
|
|
||||||
<span class="cursor" v-show="cursor"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -41,7 +38,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
<style lang="stylus">
|
<style lang="stylus">
|
||||||
.chat-line-left {
|
.chat-line-left {
|
||||||
justify-content: start;
|
justify-content: flex-start;
|
||||||
|
|
||||||
.chat-icon {
|
.chat-icon {
|
||||||
margin-right 5px;
|
margin-right 5px;
|
||||||
@ -76,20 +73,20 @@ export default defineComponent({
|
|||||||
font-size: var(--content-font-size);
|
font-size: var(--content-font-size);
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
|
|
||||||
.cursor {
|
//.cursor {
|
||||||
height 24px;
|
// height 24px;
|
||||||
border-left 1px solid black;
|
// border-left 1px solid black;
|
||||||
|
//
|
||||||
animation: cursorImg 1s infinite steps(1, start);
|
// animation: cursorImg 1s infinite steps(1, start);
|
||||||
@keyframes cursorImg {
|
// @keyframes cursorImg {
|
||||||
0%, 100% {
|
// 0%, 100% {
|
||||||
opacity: 0;
|
// opacity: 0;
|
||||||
}
|
// }
|
||||||
50% {
|
// 50% {
|
||||||
opacity: 1;
|
// opacity: 1;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="$props.show"
|
v-model="$props.show"
|
||||||
title="聊天配置"
|
title="聊天配置"
|
||||||
width="30%"
|
|
||||||
:before-close="beforeClose"
|
:before-close="beforeClose"
|
||||||
>
|
>
|
||||||
<span>正在努力开发中...</span>
|
<span>正在努力开发中...</span>
|
||||||
@ -42,5 +41,8 @@ export default defineComponent({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus">
|
<style lang="stylus">
|
||||||
|
.el-dialog {
|
||||||
|
--el-dialog-width 90%;
|
||||||
|
max-width 800px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
@ -17,16 +17,16 @@
|
|||||||
|
|
||||||
<div class="input-box">
|
<div class="input-box">
|
||||||
<div class="input-container">
|
<div class="input-container">
|
||||||
<el-form ref="form">
|
|
||||||
<el-input
|
<el-input
|
||||||
|
ref="text-input"
|
||||||
v-model="inputValue"
|
v-model="inputValue"
|
||||||
:autosize="{ minRows: 1, maxRows: 10 }"
|
:autosize="{ minRows: 1, maxRows: 10 }"
|
||||||
v-on:keydown="inputKeyDown"
|
v-on:keydown="inputKeyDown"
|
||||||
v-on:focus="focus"
|
v-on:focus="focus"
|
||||||
|
autofocus
|
||||||
type="textarea"
|
type="textarea"
|
||||||
placeholder="Input any thing here..."
|
placeholder="Input any thing here..."
|
||||||
/>
|
/>
|
||||||
</el-form>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="btn-container">
|
<div class="btn-container">
|
||||||
@ -41,6 +41,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div><!-- end input box -->
|
</div><!-- end input box -->
|
||||||
|
|
||||||
</div><!-- end container -->
|
</div><!-- end container -->
|
||||||
|
|
||||||
<config-dialog v-model:show="showDialog"></config-dialog>
|
<config-dialog v-model:show="showDialog"></config-dialog>
|
||||||
@ -52,7 +53,7 @@ import {defineComponent, nextTick} from 'vue'
|
|||||||
import ChatPrompt from "@/components/ChatPrompt.vue";
|
import ChatPrompt from "@/components/ChatPrompt.vue";
|
||||||
import ChatReply from "@/components/ChatReply.vue";
|
import ChatReply from "@/components/ChatReply.vue";
|
||||||
import {randString} from "@/utils/libs";
|
import {randString} from "@/utils/libs";
|
||||||
import {ElMessage} from 'element-plus'
|
import {ElMessage, ElMessageBox} from 'element-plus'
|
||||||
import {Tools} from '@element-plus/icons-vue'
|
import {Tools} from '@element-plus/icons-vue'
|
||||||
import ConfigDialog from '@/components/ConfigDialog.vue'
|
import ConfigDialog from '@/components/ConfigDialog.vue'
|
||||||
|
|
||||||
@ -67,6 +68,7 @@ export default defineComponent({
|
|||||||
chatBoxHeight: 0,
|
chatBoxHeight: 0,
|
||||||
showDialog: false,
|
showDialog: false,
|
||||||
|
|
||||||
|
connectingMessageBox: null,
|
||||||
socket: null,
|
socket: null,
|
||||||
sending: false
|
sending: false
|
||||||
}
|
}
|
||||||
@ -78,12 +80,27 @@ export default defineComponent({
|
|||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
this.chatBoxHeight = window.innerHeight - 61;
|
this.chatBoxHeight = window.innerHeight - 61;
|
||||||
})
|
})
|
||||||
|
this.connect();
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
connect: function () {
|
||||||
|
if (this.online) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 初始化 WebSocket 对象
|
// 初始化 WebSocket 对象
|
||||||
const socket = new WebSocket(process.env.VUE_APP_WS_HOST + '/api/chat');
|
const socket = new WebSocket(process.env.VUE_APP_WS_HOST + '/api/chat');
|
||||||
socket.addEventListener('open', () => {
|
socket.addEventListener('open', () => {
|
||||||
ElMessage.success('创建会话成功!');
|
ElMessage.success('创建会话成功!');
|
||||||
|
|
||||||
|
if (this.connectingMessageBox != null) {
|
||||||
|
this.connectingMessageBox.close();
|
||||||
|
this.connectingMessageBox = null;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.addEventListener('message', event => {
|
socket.addEventListener('message', event => {
|
||||||
if (event.data instanceof Blob) {
|
if (event.data instanceof Blob) {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
@ -103,6 +120,7 @@ export default defineComponent({
|
|||||||
this.chatData[this.chatData.length - 1]["cursor"] = false;
|
this.chatData[this.chatData.length - 1]["cursor"] = false;
|
||||||
} else {
|
} else {
|
||||||
let content = data.content;
|
let content = data.content;
|
||||||
|
// 替换换行符
|
||||||
if (content.indexOf("\n\n") >= 0) {
|
if (content.indexOf("\n\n") >= 0) {
|
||||||
content = content.replace("\n\n", "<br />");
|
content = content.replace("\n\n", "<br />");
|
||||||
}
|
}
|
||||||
@ -116,15 +134,30 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
socket.addEventListener('error', () => {
|
socket.addEventListener('close', () => {
|
||||||
ElMessage.error('会话发生异常,请刷新页面后重试');
|
ElMessageBox.confirm(
|
||||||
|
'^_^ 会话发生异常,您已经从服务器断开连接!',
|
||||||
|
'注意:',
|
||||||
|
{
|
||||||
|
confirmButtonText: '重连会话',
|
||||||
|
cancelButtonText: '不聊了',
|
||||||
|
type: 'warning',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
this.connect();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
ElMessage({
|
||||||
|
type: 'info',
|
||||||
|
message: '您关闭了会话',
|
||||||
|
})
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
this.socket = socket;
|
this.socket = socket;
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
|
||||||
inputKeyDown: function (e) {
|
inputKeyDown: function (e) {
|
||||||
if (e.keyCode === 13) {
|
if (e.keyCode === 13) {
|
||||||
if (this.sending) {
|
if (this.sending) {
|
||||||
@ -152,7 +185,10 @@ export default defineComponent({
|
|||||||
// TODO: 使用 websocket 提交数据到后端
|
// TODO: 使用 websocket 提交数据到后端
|
||||||
this.sending = true;
|
this.sending = true;
|
||||||
this.socket.send(this.inputValue);
|
this.socket.send(this.inputValue);
|
||||||
|
this.$refs["text-input"].blur();
|
||||||
this.inputValue = '';
|
this.inputValue = '';
|
||||||
|
// 等待 textarea 重新调整尺寸之后再自动获取焦点
|
||||||
|
setTimeout(() => this.$refs["text-input"].focus(), 100)
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -227,20 +263,12 @@ export default defineComponent({
|
|||||||
background-color: rgba(255, 255, 255, 1);
|
background-color: rgba(255, 255, 255, 1);
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
|
|
||||||
.input-text {
|
.el-textarea__inner {
|
||||||
font-size: 16px;
|
box-shadow: none
|
||||||
padding 0
|
padding 5px 0
|
||||||
margin 0
|
|
||||||
outline: none;
|
|
||||||
width 100%;
|
|
||||||
border none
|
|
||||||
background #ffffff
|
|
||||||
resize none
|
|
||||||
line-height 24px;
|
|
||||||
color #333;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-text::-webkit-scrollbar {
|
.el-textarea__inner::-webkit-scrollbar {
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
}
|
}
|
||||||
@ -267,9 +295,19 @@ export default defineComponent({
|
|||||||
width: 0;
|
width: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.el-message-box {
|
||||||
|
width 90%;
|
||||||
|
max-width 420px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-message {
|
||||||
|
width 90%;
|
||||||
|
min-width: 300px;
|
||||||
|
max-width 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
Loading…
Reference in New Issue
Block a user