From 62ef79e345a621bb5a20343226b6f476cd2275d5 Mon Sep 17 00:00:00 2001 From: lisongwei Date: Tue, 12 Mar 2024 14:13:08 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E7=81=AB=E5=B1=B1?= =?UTF-8?q?=E5=BC=95=E6=93=8E=E7=9A=84=E4=BA=91=E9=9B=80=E5=A4=A7=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/core/types/chat.go | 19 +- api/core/types/config.go | 2 + api/handler/chatimpl/chat_handler.go | 37 +++ api/handler/chatimpl/skylark_handler.go | 328 ++++++++++++++++++++++++ database/chatgpt_plus-v3.2.7.sql | 8 +- web/src/views/admin/ApiKey.vue | 5 + web/src/views/admin/ChatModel.vue | 1 + 7 files changed, 390 insertions(+), 10 deletions(-) create mode 100644 api/handler/chatimpl/skylark_handler.go diff --git a/api/core/types/chat.go b/api/core/types/chat.go index 175cd3ab..cb71b48a 100644 --- a/api/core/types/chat.go +++ b/api/core/types/chat.go @@ -1,15 +1,18 @@ package types +import "encoding/json" + // ApiRequest API 请求实体 type ApiRequest struct { - Model string `json:"model,omitempty"` // 兼容百度文心一言 - Temperature float32 `json:"temperature"` - MaxTokens int `json:"max_tokens,omitempty"` // 兼容百度文心一言 - Stream bool `json:"stream"` - Messages []interface{} `json:"messages,omitempty"` - Prompt []interface{} `json:"prompt,omitempty"` // 兼容 ChatGLM - Tools []interface{} `json:"tools,omitempty"` - Functions []interface{} `json:"functions,omitempty"` // 兼容中转平台 + Model string `json:"-"` + ModelRaw json.RawMessage `json:"model,omitempty"` // 兼容 model 字段不是字符串的接口 + Temperature float32 `json:"temperature"` + MaxTokens int `json:"max_tokens,omitempty"` // 兼容百度文心一言 + Stream bool `json:"stream"` + Messages []interface{} `json:"messages,omitempty"` + Prompt []interface{} `json:"prompt,omitempty"` // 兼容 ChatGLM + Tools []interface{} `json:"tools,omitempty"` + Functions []interface{} `json:"functions,omitempty"` // 兼容中转平台 ToolChoice string `json:"tool_choice,omitempty"` diff --git a/api/core/types/config.go b/api/core/types/config.go index bb71dd8d..87b38964 100644 --- a/api/core/types/config.go +++ b/api/core/types/config.go @@ -135,6 +135,7 @@ type ChatConfig struct { ChatGML ModelAPIConfig `json:"chat_gml"` Baidu ModelAPIConfig `json:"baidu"` XunFei ModelAPIConfig `json:"xun_fei"` + Skylark ModelAPIConfig `json:"skylark"` EnableContext bool `json:"enable_context"` // 是否开启聊天上下文 EnableHistory bool `json:"enable_history"` // 是否允许保存聊天记录 @@ -150,6 +151,7 @@ const ChatGLM = Platform("ChatGLM") const Baidu = Platform("Baidu") const XunFei = Platform("XunFei") const QWen = Platform("QWen") +const Skylark = Platform("Skylark") // UserChatConfig 用户的聊天配置 type UserChatConfig struct { diff --git a/api/handler/chatimpl/chat_handler.go b/api/handler/chatimpl/chat_handler.go index 90effac7..3198f705 100644 --- a/api/handler/chatimpl/chat_handler.go +++ b/api/handler/chatimpl/chat_handler.go @@ -293,6 +293,17 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio req.Input = map[string]interface{}{"messages": []map[string]string{{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}}} req.Parameters = map[string]interface{}{} break + case types.Skylark: + modelStr, err := json.Marshal(map[string]interface{}{"name": req.Model}) + if err != nil { + break + } + req.Model = string(modelStr) + req.Parameters = map[string]interface{}{ + "temperature": h.App.ChatConfig.Skylark.Temperature, + "max_new_tokens": h.App.ChatConfig.Skylark.MaxTokens, + } + break default: utils.ReplyMessage(ws, "不支持的平台:"+session.Model.Platform+",请联系管理员!") utils.ReplyMessage(ws, ErrImg) @@ -368,6 +379,8 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio return h.sendXunFeiMessage(chatCtx, req, userVo, ctx, session, role, prompt, ws) case types.QWen: return h.sendQWenMessage(chatCtx, req, userVo, ctx, session, role, prompt, ws) + case types.Skylark: + return h.sendSkylarkMessage(chatCtx, req, userVo, ctx, session, role, prompt, ws) } utils.ReplyChunkMessage(ws, types.WsMessage{ Type: types.WsMiddle, @@ -465,6 +478,10 @@ func (h *ChatHandler) doRequest(ctx context.Context, req types.ApiRequest, platf apiURL = apiKey.ApiURL req.Messages = nil break + case types.Skylark: + apiURL = apiKey.ApiURL + req.Input = nil + break default: apiURL = apiKey.ApiURL } @@ -484,6 +501,7 @@ func (h *ChatHandler) doRequest(ctx context.Context, req types.ApiRequest, platf // 创建 HttpClient 请求对象 var client *http.Client + req.ModelRaw = json.RawMessage(req.Model) requestBody, err := json.Marshal(req) if err != nil { return nil, err @@ -528,6 +546,25 @@ func (h *ChatHandler) doRequest(ctx context.Context, req types.ApiRequest, platf request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey.Value)) request.Header.Set("X-DashScope-SSE", "enable") break + case types.Skylark: + key := strings.Split(apiKey.Value, "|") + if len(key) != 2 { + return nil, errors.New("非法的 API KEY!") + } + header, err := h.makeSkylarkRequestUrl(SkylarkRequest{ + Method: "POST", + Url: apiKey.ApiURL, + Body: requestBody, + AccessKeyID: key[0], + SecretAccessKey: key[1], + }) + if err != nil { + return nil, err + } + for hn, hv := range header { + request.Header.Set(hn, hv[0]) + } + break } return client.Do(request) } diff --git a/api/handler/chatimpl/skylark_handler.go b/api/handler/chatimpl/skylark_handler.go new file mode 100644 index 00000000..47a580e9 --- /dev/null +++ b/api/handler/chatimpl/skylark_handler.go @@ -0,0 +1,328 @@ +package chatimpl + +import ( + "bufio" + "chatplus/core/types" + "chatplus/store/model" + "chatplus/store/vo" + "chatplus/utils" + "context" + "crypto/hmac" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "html/template" + "io" + "net/http" + "net/url" + "strings" + "time" + "unicode/utf8" +) + +const ( + Region = "cn-beijing" + Service = "ml_maas" +) + +type skylarkResp struct { + Id string `json:"req_id"` + Choice struct { + Message struct { + Content string `json:"content"` + } `json:"message"` + } `json:"choice"` +} + +// Skylark 消息发送实现 +func (h *ChatHandler) sendSkylarkMessage( + chatCtx []interface{}, + req types.ApiRequest, + userVo vo.User, + ctx context.Context, + session *types.ChatSession, + role model.ChatRole, + prompt string, + ws *types.WsClient) error { + promptCreatedAt := time.Now() // 记录提问时间 + start := time.Now() + var apiKey model.ApiKey + response, err := h.doRequest(ctx, req, session.Model.Platform, &apiKey) + logger.Info("HTTP请求完成,耗时:", time.Since(start)) + if err != nil { + if strings.Contains(err.Error(), "context canceled") { + logger.Info("用户取消了请求:", prompt) + return nil + } else if strings.Contains(err.Error(), "no available key") { + utils.ReplyMessage(ws, "抱歉😔😔😔,系统已经没有可用的 API KEY,请联系管理员!") + return nil + } else { + logger.Error(err) + } + + utils.ReplyMessage(ws, ErrorMsg) + utils.ReplyMessage(ws, ErrImg) + return err + } else { + defer response.Body.Close() + } + + contentType := response.Header.Get("Content-Type") + if strings.Contains(contentType, "text/event-stream") { + replyCreatedAt := time.Now() // 记录回复时间 + // 循环读取 Chunk 消息 + var message = types.Message{} + var contents = make([]string, 0) + var content string + scanner := bufio.NewScanner(response.Body) + for scanner.Scan() { + line := scanner.Text() + if len(line) < 5 || strings.HasPrefix(line, "id:") { + continue + } + + if strings.HasPrefix(line, "data:") { + content = line[5:] + } + + if content == "[DONE]" { + break + } + + // 处理代码换行 + if len(content) == 0 { + content = "\n" + } + + var resp skylarkResp + err := utils.JsonDecode(content, &resp) + if err != nil { + logger.Error("error with parse data line: ", err) + utils.ReplyMessage(ws, fmt.Sprintf("**解析数据行失败:%s**", err)) + break + } + + if len(contents) == 0 { + utils.ReplyChunkMessage(ws, types.WsMessage{Type: types.WsStart}) + } + utils.ReplyChunkMessage(ws, types.WsMessage{ + Type: types.WsMiddle, + Content: utils.InterfaceToString(resp.Choice.Message.Content), + }) + contents = append(contents, resp.Choice.Message.Content) + + } // end for + + if err := scanner.Err(); err != nil { + if strings.Contains(err.Error(), "context canceled") { + logger.Info("用户取消了请求:", prompt) + } else { + logger.Error("信息读取出错:", err) + } + } + + // 消息发送成功 + if len(contents) > 0 { + // 更新用户的对话次数 + h.subUserCalls(userVo, session) + + if message.Role == "" { + message.Role = "assistant" + } + message.Content = strings.Join(contents, "") + useMsg := types.Message{Role: "user", Content: prompt} + + // 更新上下文消息,如果是调用函数则不需要更新上下文 + if h.App.ChatConfig.EnableContext { + chatCtx = append(chatCtx, useMsg) // 提问消息 + chatCtx = append(chatCtx, message) // 回复消息 + h.App.ChatContexts.Put(session.ChatId, chatCtx) + } + + // 追加聊天记录 + if h.App.ChatConfig.EnableHistory { + // for prompt + promptToken, err := utils.CalcTokens(prompt, req.Model) + if err != nil { + logger.Error(err) + } + historyUserMsg := model.ChatMessage{ + UserId: userVo.Id, + ChatId: session.ChatId, + RoleId: role.Id, + Type: types.PromptMsg, + Icon: userVo.Avatar, + Content: template.HTMLEscapeString(prompt), + Tokens: promptToken, + UseContext: true, + Model: req.Model, + } + historyUserMsg.CreatedAt = promptCreatedAt + historyUserMsg.UpdatedAt = promptCreatedAt + res := h.db.Save(&historyUserMsg) + if res.Error != nil { + logger.Error("failed to save prompt history message: ", res.Error) + } + + // for reply + // 计算本次对话消耗的总 token 数量 + replyToken, _ := utils.CalcTokens(message.Content, req.Model) + totalTokens := replyToken + getTotalTokens(req) + historyReplyMsg := model.ChatMessage{ + UserId: userVo.Id, + ChatId: session.ChatId, + RoleId: role.Id, + Type: types.ReplyMsg, + Icon: role.Icon, + Content: message.Content, + Tokens: totalTokens, + UseContext: true, + Model: req.Model, + } + historyReplyMsg.CreatedAt = replyCreatedAt + historyReplyMsg.UpdatedAt = replyCreatedAt + res = h.db.Create(&historyReplyMsg) + if res.Error != nil { + logger.Error("failed to save reply history message: ", res.Error) + } + // 更新用户信息 + h.incUserTokenFee(userVo.Id, totalTokens) + } + + // 保存当前会话 + var chatItem model.ChatItem + res := h.db.Where("chat_id = ?", session.ChatId).First(&chatItem) + if res.Error != nil { + chatItem.ChatId = session.ChatId + chatItem.UserId = session.UserId + chatItem.RoleId = role.Id + chatItem.ModelId = session.Model.Id + if utf8.RuneCountInString(prompt) > 30 { + chatItem.Title = string([]rune(prompt)[:30]) + "..." + } else { + chatItem.Title = prompt + } + chatItem.Model = req.Model + h.db.Create(&chatItem) + } + } + } else { + body, err := io.ReadAll(response.Body) + if err != nil { + return fmt.Errorf("error with reading response: %v", err) + } + + var res struct { + Code int `json:"error_code"` + Msg string `json:"error_msg"` + } + err = json.Unmarshal(body, &res) + if err != nil { + return fmt.Errorf("error with decode response: %v", err) + } + utils.ReplyMessage(ws, "请求 Skylark API 失败:"+res.Msg) + } + + return nil +} + +func hmacSHA256(key []byte, content string) []byte { + mac := hmac.New(sha256.New, key) + mac.Write([]byte(content)) + return mac.Sum(nil) +} + +func getSignedKey(secretKey, date, region, service string) []byte { + kDate := hmacSHA256([]byte(secretKey), date) + kRegion := hmacSHA256(kDate, region) + kService := hmacSHA256(kRegion, service) + kSigning := hmacSHA256(kService, "request") + + return kSigning +} + +func hashSHA256(data []byte) []byte { + hash := sha256.New() + if _, err := hash.Write(data); err != nil { + logger.Debugf("input hash err:%s", err.Error()) + } + + return hash.Sum(nil) +} + +type SkylarkRequest struct { + Method string + Url string + Body []byte + AccessKeyID string + SecretAccessKey string +} + +func (h *ChatHandler) makeSkylarkRequestUrl(params SkylarkRequest) (http.Header, error) { + ul, err := url.Parse(params.Url) + if err != nil { + return nil, err + } + requestAddr := fmt.Sprintf("%s%s?%s", ul.Host, ul.Path, ul.Query().Encode()) + logger.Debugf("request addr: %s\n", requestAddr) + + var reqHeaders = http.Header{} + + now := time.Now() + date := now.UTC().Format("20060102T150405Z") + authDate := date[:8] + reqHeaders.Set("X-Date", date) + payload := hex.EncodeToString(hashSHA256(params.Body)) + reqHeaders.Set("X-Content-Sha256", payload) + reqHeaders.Set("Content-Type", "application/json") + + queryString := strings.Replace(ul.Query().Encode(), "+", "%20", -1) + signedHeaders := []string{"content-type", "host", "x-content-sha256", "x-date"} + var headerList []string + for _, header := range signedHeaders { + if header == "host" { + headerList = append(headerList, header+":"+ul.Host) + } else { + v := reqHeaders.Get(header) + headerList = append(headerList, header+":"+strings.TrimSpace(v)) + } + } + headerString := strings.Join(headerList, "\n") + + canonicalString := strings.Join([]string{ + params.Method, + ul.Path, + queryString, + headerString + "\n", + strings.Join(signedHeaders, ";"), + payload, + }, "\n") + logger.Debugf("canonical string:\n%s\n", canonicalString) + + hashedCanonicalString := hex.EncodeToString(hashSHA256([]byte(canonicalString))) + logger.Debugf("hashed canonical string: %s\n", hashedCanonicalString) + + credentialScope := authDate + "/" + Region + "/" + Service + "/request" + signString := strings.Join([]string{ + "HMAC-SHA256", + date, + credentialScope, + hashedCanonicalString, + }, "\n") + logger.Debugf("sign string:\n%s\n", signString) + + // 3. 构建认证请求头 + signedKey := getSignedKey(params.SecretAccessKey, authDate, Region, Service) + signature := hex.EncodeToString(hmacSHA256(signedKey, signString)) + logger.Debugf("signature: %s\n", signature) + + authorization := "HMAC-SHA256" + + " Credential=" + params.AccessKeyID + "/" + credentialScope + + ", SignedHeaders=" + strings.Join(signedHeaders, ";") + + ", Signature=" + signature + reqHeaders.Set("Authorization", authorization) + logger.Debugf("authorization: %s\n", authorization) + + return reqHeaders, nil +} diff --git a/database/chatgpt_plus-v3.2.7.sql b/database/chatgpt_plus-v3.2.7.sql index 6f8ea61e..3d494960 100644 --- a/database/chatgpt_plus-v3.2.7.sql +++ b/database/chatgpt_plus-v3.2.7.sql @@ -127,7 +127,11 @@ INSERT INTO `chatgpt_chat_models` (`id`, `platform`, `name`, `value`, `sort_num` (16, 'OpenAI', 'MJ Prompt Generator (V6)', 'gpt-4-gizmo-g-tc0eHXdgb', 0, 1, 1, 1, '2024-01-15 14:46:35', '2024-01-15 14:53:16'), (17, 'QWen', '通义千问-Turbo', 'qwen-turbo', 0, 1, 1, 1, '2024-01-19 10:42:24', '2024-01-19 10:45:16'), (18, 'QWen', '通义千问-Plus', 'qwen-plus', 0, 1, 1, 1, '2024-01-19 10:42:49', '2024-01-19 10:51:09'), -(19, 'QWen', '通义千问-Max', 'qwen-max-1201', 0, 1, 1, 1, '2024-01-19 10:51:03', '2024-01-19 10:51:03'); +(19, 'QWen', '通义千问-Max', 'qwen-max-1201', 0, 1, 1, 1, '2024-01-19 10:51:03', '2024-01-19 10:51:03') +(20, 'Skylark', 'Skylark-Lite', 'skylark-lite-public', 0, 1, 1, 1, '2024-03-11 17:07:24', '2024-03-11 17:07:26'), +(21, 'Skylark', 'Skylark-Plus', 'skylark-plus-public', 0, 1, 1, 1, '2024-03-11 17:07:24', '2024-03-11 17:07:26'), +(22, 'Skylark', 'Skylark-Pro', 'skylark-pro-public', 0, 1, 1, 1, '2024-03-11 17:07:24', '2024-03-11 17:07:26'), +(23, 'Skylark', 'Skylark-Chat', 'skylark-chat', 0, 1, 1, 1, '2024-03-11 17:07:24', '2024-03-11 17:07:26'); -- -------------------------------------------------------- @@ -191,7 +195,7 @@ CREATE TABLE `chatgpt_configs` ( INSERT INTO `chatgpt_configs` (`id`, `marker`, `config_json`) VALUES (1, 'system', '{\"admin_title\":\"ChatPlus 控制台\",\"chat_call_price\":0.1,\"default_models\":[\"eb-instant\",\"completions_pro\",\"generalv2\",\"general\",\"chatglm_pro\",\"gpt-3.5-turbo-16k\",\"chatglm_lite\",\"chatglm_std\",\"generalv3\",\"qwen-max-1201\"],\"enabled_alipay\":true,\"enabled_draw\":true,\"enabled_function\":true,\"enabled_msg\":true,\"enabled_msg_service\":false,\"enabled_register\":true,\"enabled_reward\":true,\"force_invite\":false,\"img_call_price\":0.2,\"init_calls\":1000,\"init_chat_calls\":10,\"init_img_calls\":5,\"invite_calls\":10,\"invite_chat_calls\":100,\"invite_img_calls\":50,\"models\":[\"gpt-3.5-turbo-16k\",\"gpt-3.5-turbo\",\"gpt-4\",\"gpt-4-32k\"],\"order_pay_info_text\":\"成为本站会员后每月有500次对话额度,50次 AI 绘画额度,限制下月1号解除,若在期间超过次数后可单独购买点卡。当月充值的点卡有效期可以延期到下个月底。\",\"order_pay_timeout\":1800,\"register_ways\":[],\"reward_img\":\"https://img.r9it.com/chatgpt-plus/1696824231905289.png\",\"show_demo_notice\":true,\"title\":\"ChatPlus AI 智能助手\",\"user_init_calls\":10,\"vip_month_calls\":500,\"vip_month_img_calls\":50,\"wechat_card_url\":\"/images/wx.png\"}'), -(2, 'chat', '{\"azure\":{\"api_url\":\"https://chat-bot-api.openai.azure.com/openai/deployments/{model}/chat/completions?api-version=2023-05-15\",\"max_tokens\":1024,\"temperature\":1},\"baidu\":{\"api_url\":\"https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/{model}\",\"max_tokens\":1024,\"temperature\":0.95},\"chat_gml\":{\"api_url\":\"https://open.bigmodel.cn/api/paas/v3/model-api/{model}/sse-invoke\",\"max_tokens\":1024,\"temperature\":0.95},\"context_deep\":4,\"dall_api_url\":\"http://89.117.18.9:8001/v1/images/generations\",\"dall_img_num\":1,\"enable_context\":true,\"enable_history\":true,\"open_ai\":{\"api_url\":\"https://api.fast-tunnel.one/v1/chat/completions\",\"max_tokens\":2048,\"temperature\":1},\"xun_fei\":{\"api_url\":\"wss://spark-api.xf-yun.com/{version}/chat\",\"max_tokens\":1024,\"temperature\":0.5}}'), +(2, 'chat', '{\"azure\":{\"api_url\":\"https://chat-bot-api.openai.azure.com/openai/deployments/{model}/chat/completions?api-version=2023-05-15\",\"max_tokens\":1024,\"temperature\":1},\"baidu\":{\"api_url\":\"https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/{model}\",\"max_tokens\":1024,\"temperature\":0.95},\"chat_gml\":{\"api_url\":\"https://open.bigmodel.cn/api/paas/v3/model-api/{model}/sse-invoke\",\"max_tokens\":1024,\"temperature\":0.95},\"context_deep\":4,\"dall_api_url\":\"http://89.117.18.9:8001/v1/images/generations\",\"dall_img_num\":1,\"enable_context\":true,\"enable_history\":true,\"open_ai\":{\"api_url\":\"https://api.fast-tunnel.one/v1/chat/completions\",\"max_tokens\":2048,\"temperature\":1},\"xun_fei\":{\"api_url\":\"wss://spark-api.xf-yun.com/{version}/chat\",\"max_tokens\":1024,\"temperature\":0.5},"skylark":{"api_url":"https://maas-api.ml-platform-cn-beijing.volces.com/api/v1/chat","max_tokens":1024,"temperature":0.7}}'), (3, 'notice', '{\"content\":\"注意:当前站点仅为开源项目 \\u003ca style=\\\"color: #F56C6C\\\" href=\\\"https://github.com/yangjian102621/chatgpt-plus\\\" target=\\\"_blank\\\"\\u003eChatPlus\\u003c/a\\u003e 的演示项目,本项目单纯就是给大家体验项目功能使用。\\n体验额度用完之后请不要在当前站点进行任何充值操作!!!\\n体验额度用完之后请不要在当前站点进行任何充值操作!!!\\n体验额度用完之后请不要在当前站点进行任何充值操作!!!\\n 如果觉得好用你就花几分钟自己部署一套,没有API KEY 的同学可以去 \\u003ca href=\\\"https://gpt.bemore.lol\\\" target=\\\"_blank\\\"\\n style=\\\"font-size: 20px;color:#F56C6C\\\"\\u003ehttps://gpt.bemore.lol\\u003c/a\\u003e 购买,现在有超级优惠,价格远低于 OpenAI 官方。\\nGPT-3.5,GPT-4,DALL-E3 绘图......你都可以随意使用,无需魔法。\\nMidJourney API 购买地址:\\u003ca href=\\\"https://api.chat-plus.net\\\" target=\\\"_blank\\\"\\n style=\\\"font-size: 20px;color:#F56C6C\\\"\\u003ehttps://api.chat-plus.net\\u003c/a\\u003e\\n接入教程: \\u003ca href=\\\"https://ai.r9it.com/docs/install/\\\" target=\\\"_blank\\\"\\n style=\\\"font-size: 20px;color:#F56C6C\\\"\\u003ehttps://ai.r9it.com/docs/install/\\u003c/a\\u003e\\n本项目源码地址:\\u003ca href=\\\"https://github.com/yangjian102621/chatgpt-plus\\\" target=\\\"_blank\\\"\\u003ehttps://github.com/yangjian102621/chatgpt-plus\\u003c/a\\u003e\",\"updated\":true}'); -- -------------------------------------------------------- diff --git a/web/src/views/admin/ApiKey.vue b/web/src/views/admin/ApiKey.vue index a6c62589..f3a75179 100644 --- a/web/src/views/admin/ApiKey.vue +++ b/web/src/views/admin/ApiKey.vue @@ -180,6 +180,11 @@ const platforms = ref([ value: "QWen", api_url: "https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation" }, + { + name: "【火山引擎】Skylark", + value: "Skylark", + api_url: "https://maas-api.ml-platform-cn-beijing.volces.com/api/v1/chat" + }, ]) const types = ref([ {name: "聊天", value: "chat"}, diff --git a/web/src/views/admin/ChatModel.vue b/web/src/views/admin/ChatModel.vue index d0fff12d..5ef5c827 100644 --- a/web/src/views/admin/ChatModel.vue +++ b/web/src/views/admin/ChatModel.vue @@ -135,6 +135,7 @@ const platforms = ref([ {name: "【百度】文心一言", value: "Baidu"}, {name: "【微软】Azure", value: "Azure"}, {name: "【阿里】通义千问", value: "QWen"}, + {name: "【火山引擎】Skylark", value: "Skylark"}, ])