优化 ChatGPT API 重试逻辑

This commit is contained in:
RockYang 2023-03-20 15:02:42 +08:00
parent 0ca104bac8
commit 3bb6814493
9 changed files with 167 additions and 113 deletions

View File

@ -6,5 +6,9 @@
* [ ] 使用 level DB 保存用户聊天的上下文
* [ ] 使用 MySQL 保存用户的聊天的历史记录
* [ ] 用户聊天鉴权
* [ ] 用户聊天鉴权,设置口令模式
* [ ] 每次连接自动加载历史记录
* [ ] 角色设定,预设一些角色,比如程序员,产品经理,医生,作家,老师...
* [ ] markdown 语法解析
* [ ] 用户配置界面

View File

@ -10,7 +10,6 @@ import (
"io"
"math/rand"
"net/http"
"net/url"
"openai/types"
"strings"
"time"
@ -74,34 +73,32 @@ func (s *Server) sendMessage(userId string, text string, ws Client) error {
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("Authorization", fmt.Sprintf("Bearer %s", s.Config.Chat.ApiKeys[index]))
uri := url.URL{}
proxy, _ := uri.Parse(s.Config.ProxyURL)
client := &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyURL(proxy),
},
}
response, err := client.Do(request)
// 随机获取一个 API Key如果请求失败则更换 API Key 重试
// TODO: 需要将失败的 Key 移除列表
rand.Seed(time.Now().UnixNano())
var retryCount = 3
for err != nil {
if retryCount <= 0 {
return err
var response *http.Response
for retryCount > 0 {
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--
}
if err != nil {
return err
}
var message = types.Message{}
var contents = make([]string, 0)
var responseBody = types.ApiResponse{}
reader := bufio.NewReader(response.Body)
for {
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)
if err != nil {
logger.Error(err)
logger.Error(line)
continue
}
// 初始化 role

View File

@ -73,6 +73,11 @@ func (s *Server) ConfigSetHandle(c *gin.Context) {
// 保存配置文件
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})
}

View File

@ -9,6 +9,7 @@ import (
"io/fs"
"log"
"net/http"
"net/url"
logger2 "openai/logger"
"openai/types"
"os"
@ -32,6 +33,7 @@ func (s StaticFile) Open(name string) (fs.File, error) {
type Server struct {
Config *types.Config
ConfigPath string
Client *http.Client
History map[string][]types.Message
}
@ -41,8 +43,17 @@ func NewServer(configPath string) (*Server, error) {
if err != nil {
return nil, err
}
uri := url.URL{}
proxy, _ := uri.Parse(config.ProxyURL)
client := &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyURL(proxy),
},
}
return &Server{
Config: config,
Client: client,
ConfigPath: configPath,
History: make(map[string][]types.Message, 16)}, nil
}

View File

@ -1 +1 @@
VUE_APP_WS_HOST=ws://127.0.0.1:5678
VUE_APP_WS_HOST=ws://172.22.11.200:5678

View File

@ -34,7 +34,7 @@ export default defineComponent({
<style lang="stylus">
.chat-line-right {
justify-content: end;
justify-content: flex-end;
.chat-icon {
margin-left 5px;

View File

@ -6,10 +6,7 @@
<div class="chat-item">
<div class="triangle"></div>
<div class="content">
<span v-html="content"></span>
<span class="cursor" v-show="cursor"></span>
</div>
<div class="content" v-html="content"></div>
</div>
</div>
</template>
@ -41,7 +38,7 @@ export default defineComponent({
<style lang="stylus">
.chat-line-left {
justify-content: start;
justify-content: flex-start;
.chat-icon {
margin-right 5px;
@ -76,20 +73,20 @@ export default defineComponent({
font-size: var(--content-font-size);
border-radius: 5px;
.cursor {
height 24px;
border-left 1px solid black;
animation: cursorImg 1s infinite steps(1, start);
@keyframes cursorImg {
0%, 100% {
opacity: 0;
}
50% {
opacity: 1;
}
}
}
//.cursor {
// height 24px;
// border-left 1px solid black;
//
// animation: cursorImg 1s infinite steps(1, start);
// @keyframes cursorImg {
// 0%, 100% {
// opacity: 0;
// }
// 50% {
// opacity: 1;
// }
// }
//}
}
}
}

View File

@ -2,7 +2,6 @@
<el-dialog
v-model="$props.show"
title="聊天配置"
width="30%"
:before-close="beforeClose"
>
<span>正在努力开发中...</span>
@ -42,5 +41,8 @@ export default defineComponent({
</script>
<style lang="stylus">
.el-dialog {
--el-dialog-width 90%;
max-width 800px;
}
</style>

View File

@ -17,16 +17,16 @@
<div class="input-box">
<div class="input-container">
<el-form ref="form">
<el-input
ref="text-input"
v-model="inputValue"
:autosize="{ minRows: 1, maxRows: 10 }"
v-on:keydown="inputKeyDown"
v-on:focus="focus"
autofocus
type="textarea"
placeholder="Input any thing here..."
/>
</el-form>
</div>
<div class="btn-container">
@ -41,6 +41,7 @@
</div>
</div><!-- end input box -->
</div><!-- end container -->
<config-dialog v-model:show="showDialog"></config-dialog>
@ -52,7 +53,7 @@ import {defineComponent, nextTick} from 'vue'
import ChatPrompt from "@/components/ChatPrompt.vue";
import ChatReply from "@/components/ChatReply.vue";
import {randString} from "@/utils/libs";
import {ElMessage} from 'element-plus'
import {ElMessage, ElMessageBox} from 'element-plus'
import {Tools} from '@element-plus/icons-vue'
import ConfigDialog from '@/components/ConfigDialog.vue'
@ -67,6 +68,7 @@ export default defineComponent({
chatBoxHeight: 0,
showDialog: false,
connectingMessageBox: null,
socket: null,
sending: false
}
@ -78,12 +80,27 @@ export default defineComponent({
nextTick(() => {
this.chatBoxHeight = window.innerHeight - 61;
})
this.connect();
},
methods: {
connect: function () {
if (this.online) {
return
}
// WebSocket
const socket = new WebSocket(process.env.VUE_APP_WS_HOST + '/api/chat');
socket.addEventListener('open', () => {
ElMessage.success('创建会话成功!');
if (this.connectingMessageBox != null) {
this.connectingMessageBox.close();
this.connectingMessageBox = null;
}
});
socket.addEventListener('message', event => {
if (event.data instanceof Blob) {
const reader = new FileReader();
@ -103,6 +120,7 @@ export default defineComponent({
this.chatData[this.chatData.length - 1]["cursor"] = false;
} else {
let content = data.content;
//
if (content.indexOf("\n\n") >= 0) {
content = content.replace("\n\n", "<br />");
}
@ -116,15 +134,30 @@ export default defineComponent({
}
});
socket.addEventListener('error', () => {
ElMessage.error('会话发生异常,请刷新页面后重试');
socket.addEventListener('close', () => {
ElMessageBox.confirm(
'^_^ 会话发生异常,您已经从服务器断开连接!',
'注意:',
{
confirmButtonText: '重连会话',
cancelButtonText: '不聊了',
type: 'warning',
}
)
.then(() => {
this.connect();
})
.catch(() => {
ElMessage({
type: 'info',
message: '您关闭了会话',
})
})
});
this.socket = socket;
},
methods: {
inputKeyDown: function (e) {
if (e.keyCode === 13) {
if (this.sending) {
@ -152,7 +185,10 @@ export default defineComponent({
// TODO: 使 websocket
this.sending = true;
this.socket.send(this.inputValue);
this.$refs["text-input"].blur();
this.inputValue = '';
// textarea
setTimeout(() => this.$refs["text-input"].focus(), 100)
return true;
},
@ -227,20 +263,12 @@ export default defineComponent({
background-color: rgba(255, 255, 255, 1);
padding: 5px 10px;
.input-text {
font-size: 16px;
padding 0
margin 0
outline: none;
width 100%;
border none
background #ffffff
resize none
line-height 24px;
color #333;
.el-textarea__inner {
box-shadow: none
padding 5px 0
}
.input-text::-webkit-scrollbar {
.el-textarea__inner::-webkit-scrollbar {
width: 0;
height: 0;
}
@ -267,9 +295,19 @@ export default defineComponent({
width: 0;
height: 0;
}
}
}
.el-message-box {
width 90%;
max-width 420px;
}
.el-message {
width 90%;
min-width: 300px;
max-width 600px;
}
</style>