feat: support dall-e3 api mirrors, add name field for ApiKey

This commit is contained in:
RockYang 2024-01-04 16:29:57 +08:00
parent 4b1c4f7ccc
commit ea3301c75c
13 changed files with 51 additions and 75 deletions

View File

@ -115,11 +115,10 @@ type ChatConfig struct {
Baidu ModelAPIConfig `json:"baidu"` Baidu ModelAPIConfig `json:"baidu"`
XunFei ModelAPIConfig `json:"xun_fei"` XunFei ModelAPIConfig `json:"xun_fei"`
EnableContext bool `json:"enable_context"` // 是否开启聊天上下文 EnableContext bool `json:"enable_context"` // 是否开启聊天上下文
EnableHistory bool `json:"enable_history"` // 是否允许保存聊天记录 EnableHistory bool `json:"enable_history"` // 是否允许保存聊天记录
ContextDeep int `json:"context_deep"` // 上下文深度 ContextDeep int `json:"context_deep"` // 上下文深度
DallApiURL string `json:"dall_api_url"` // dall-e3 绘图 API 地址 DallImgNum int `json:"dall_img_num"` // dall-e3 出图数量
DallImgNum int `json:"dall_img_num"` // dall-e3 出图数量
} }
type Platform string type Platform string
@ -143,7 +142,6 @@ type InviteReward struct {
type ModelAPIConfig struct { type ModelAPIConfig struct {
Temperature float32 `json:"temperature"` Temperature float32 `json:"temperature"`
MaxTokens int `json:"max_tokens"` MaxTokens int `json:"max_tokens"`
ApiKey string `json:"api_key"`
} }
type SystemConfig struct { type SystemConfig struct {

View File

@ -27,6 +27,7 @@ func (h *ApiKeyHandler) Save(c *gin.Context) {
var data struct { var data struct {
Id uint `json:"id"` Id uint `json:"id"`
Platform string `json:"platform"` Platform string `json:"platform"`
Name string `json:"name"`
Type string `json:"type"` Type string `json:"type"`
Value string `json:"value"` Value string `json:"value"`
ApiURL string `json:"api_url"` ApiURL string `json:"api_url"`
@ -48,6 +49,7 @@ func (h *ApiKeyHandler) Save(c *gin.Context) {
apiKey.ApiURL = data.ApiURL apiKey.ApiURL = data.ApiURL
apiKey.Enabled = data.Enabled apiKey.Enabled = data.Enabled
apiKey.UseProxy = data.UseProxy apiKey.UseProxy = data.UseProxy
apiKey.Name = data.Name
res := h.db.Save(&apiKey) res := h.db.Save(&apiKey)
if res.Error != nil { if res.Error != nil {
resp.ERROR(c, "更新数据库失败!") resp.ERROR(c, "更新数据库失败!")

View File

@ -448,8 +448,9 @@ func (h *ChatHandler) doRequest(ctx context.Context, req types.ApiRequest, platf
request = request.WithContext(ctx) request = request.WithContext(ctx)
request.Header.Set("Content-Type", "application/json") request.Header.Set("Content-Type", "application/json")
proxyURL := h.App.Config.ProxyURL var proxyURL string
if proxyURL != "" && platform == types.OpenAI { // 使用代理 if h.App.Config.ProxyURL != "" && apiKey.UseProxy { // 使用代理
proxyURL = h.App.Config.ProxyURL
proxy, _ := url.Parse(proxyURL) proxy, _ := url.Parse(proxyURL)
client = &http.Client{ client = &http.Client{
Transport: &http.Transport{ Transport: &http.Transport{

View File

@ -231,15 +231,10 @@ func (h *FunctionHandler) Dall3(c *gin.Context) {
// translate prompt // translate prompt
const translatePromptTemplate = "Translate the following painting prompt words into English keyword phrases. Without any explanation, directly output the keyword phrases separated by commas. The content to be translated is: [%s]" const translatePromptTemplate = "Translate the following painting prompt words into English keyword phrases. Without any explanation, directly output the keyword phrases separated by commas. The content to be translated is: [%s]"
pt, err := utils.OpenAIRequest(fmt.Sprintf(translatePromptTemplate, params["prompt"]), apiKey, h.App.Config.ProxyURL) pt, err := utils.OpenAIRequest(h.db, fmt.Sprintf(translatePromptTemplate, params["prompt"]), h.App.Config.ProxyURL)
if err == nil { if err == nil {
prompt = pt prompt = pt
} }
apiURL := chatConfig.DallApiURL
if utils.IsEmptyValue(apiURL) {
apiURL = "https://api.openai.com/v1/images/generations"
}
imgNum := chatConfig.DallImgNum imgNum := chatConfig.DallImgNum
if imgNum <= 0 { if imgNum <= 0 {
imgNum = 1 imgNum = 1
@ -247,11 +242,12 @@ func (h *FunctionHandler) Dall3(c *gin.Context) {
var res imgRes var res imgRes
var errRes ErrRes var errRes ErrRes
var request *req.Request var request *req.Request
if strings.Contains(apiURL, "api.openai.com") { if apiKey.UseProxy && h.proxyURL != "" {
request = req.C().SetProxyURL(h.proxyURL).R() request = req.C().SetProxyURL(h.proxyURL).R()
} else { } else {
request = req.C().R() request = req.C().R()
} }
logger.Debugf("Sending %s request, ApiURL:%s, ApiKey:%s, PROXY: %s", apiKey.Platform, apiKey.ApiURL, apiKey.Value, h.proxyURL)
r, err := request.SetHeader("Content-Type", "application/json"). r, err := request.SetHeader("Content-Type", "application/json").
SetHeader("Authorization", "Bearer "+apiKey.Value). SetHeader("Authorization", "Bearer "+apiKey.Value).
SetBody(imgReq{ SetBody(imgReq{
@ -261,7 +257,7 @@ func (h *FunctionHandler) Dall3(c *gin.Context) {
Size: "1024x1024", Size: "1024x1024",
}). }).
SetErrorResult(&errRes). SetErrorResult(&errRes).
SetSuccessResult(&res).Post(apiURL) SetSuccessResult(&res).Post(apiKey.ApiURL)
if r.IsErrorState() { if r.IsErrorState() {
resp.ERROR(c, "请求 OpenAI API 失败: "+errRes.Error.Message) resp.ERROR(c, "请求 OpenAI API 失败: "+errRes.Error.Message)
return return

View File

@ -3,11 +3,8 @@ package handler
import ( import (
"chatplus/core" "chatplus/core"
"chatplus/core/types" "chatplus/core/types"
"chatplus/store/model"
"chatplus/utils" "chatplus/utils"
"chatplus/utils/resp" "chatplus/utils/resp"
"fmt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"gorm.io/gorm" "gorm.io/gorm"
) )
@ -36,7 +33,7 @@ func (h *PromptHandler) Rewrite(c *gin.Context) {
return return
} }
content, err := h.request(data.Prompt, rewritePromptTemplate) content, err := utils.OpenAIRequest(h.db, data.Prompt, rewritePromptTemplate)
if err != nil { if err != nil {
resp.ERROR(c, err.Error()) resp.ERROR(c, err.Error())
return return
@ -54,7 +51,7 @@ func (h *PromptHandler) Translate(c *gin.Context) {
return return
} }
content, err := h.request(data.Prompt, translatePromptTemplate) content, err := utils.OpenAIRequest(h.db, data.Prompt, translatePromptTemplate)
if err != nil { if err != nil {
resp.ERROR(c, err.Error()) resp.ERROR(c, err.Error())
return return
@ -62,14 +59,3 @@ func (h *PromptHandler) Translate(c *gin.Context) {
resp.SUCCESS(c, content) resp.SUCCESS(c, content)
} }
func (h *PromptHandler) request(prompt string, promptTemplate string) (string, error) {
// 获取 OpenAI 的 API KEY
var apiKey model.ApiKey
res := h.db.Where("platform = ?", types.OpenAI).Where("type = ?", "chat").Where("enabled = ?", true).First(&apiKey)
if res.Error != nil {
return "", fmt.Errorf("error with fetch OpenAI API KEY%v", res.Error)
}
return utils.OpenAIRequest(fmt.Sprintf(promptTemplate, prompt), apiKey, h.App.Config.ProxyURL)
}

View File

@ -303,7 +303,7 @@ func (h *UserHandler) ProfileUpdate(c *gin.Context) {
} }
h.db.First(&user, user.Id) h.db.First(&user, user.Id)
user.Avatar = data.Avatar user.Avatar = data.Avatar
user.ChatConfig = utils.JsonEncode(data.ChatConfig) user.Nickname = data.Nickname
res := h.db.Updates(&user) res := h.db.Updates(&user)
if res.Error != nil { if res.Error != nil {
resp.ERROR(c, "更新用户信息失败") resp.ERROR(c, "更新用户信息失败")

View File

@ -4,6 +4,7 @@ package model
type ApiKey struct { type ApiKey struct {
BaseModel BaseModel
Platform string Platform string
Name string
Type string // 用途 chat => 聊天img => 绘图 Type string // 用途 chat => 聊天img => 绘图
Value string // API Key 的值 Value string // API Key 的值
ApiURL string // 当前 KEY 的 API 地址 ApiURL string // 当前 KEY 的 API 地址

View File

@ -4,6 +4,7 @@ package vo
type ApiKey struct { type ApiKey struct {
BaseVo BaseVo
Platform string `json:"platform"` Platform string `json:"platform"`
Name string `json:"name"`
Type string `json:"type"` Type string `json:"type"`
Value string `json:"value"` // API Key 的值 Value string `json:"value"` // API Key 的值
ApiURL string `json:"api_url"` ApiURL string `json:"api_url"`

View File

@ -7,6 +7,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/imroc/req/v3" "github.com/imroc/req/v3"
"gorm.io/gorm"
"io" "io"
"net/http" "net/http"
"net/url" "net/url"
@ -86,7 +87,13 @@ type apiErrRes struct {
} `json:"error"` } `json:"error"`
} }
func OpenAIRequest(prompt string, apiKey model.ApiKey, proxy string) (string, error) { func OpenAIRequest(db *gorm.DB, prompt string, proxy string) (string, error) {
var apiKey model.ApiKey
res := db.Where("platform = ?", types.OpenAI).Where("type = ?", "chat").Where("enabled = ?", true).First(&apiKey)
if res.Error != nil {
return "", fmt.Errorf("error with fetch OpenAI API KEY%v", res.Error)
}
messages := make([]interface{}, 1) messages := make([]interface{}, 1)
messages[0] = types.Message{ messages[0] = types.Message{
Role: "user", Role: "user",

View File

@ -3,4 +3,5 @@ ALTER TABLE `chatgpt_rewards` ADD `exchange` VARCHAR(255) NOT NULL COMMENT '兑
ALTER TABLE `chatgpt_api_keys` ADD `api_url` VARCHAR(255) NULL COMMENT 'API 地址' AFTER `last_used_at`, ADD `enabled` TINYINT(1) NULL COMMENT '是否启用' AFTER `api_url`; ALTER TABLE `chatgpt_api_keys` ADD `api_url` VARCHAR(255) NULL COMMENT 'API 地址' AFTER `last_used_at`, ADD `enabled` TINYINT(1) NULL COMMENT '是否启用' AFTER `api_url`;
ALTER TABLE `chatgpt_api_keys` DROP INDEX `value`; ALTER TABLE `chatgpt_api_keys` DROP INDEX `value`;
ALTER TABLE `chatgpt_mj_jobs` ADD UNIQUE(`task_id`); ALTER TABLE `chatgpt_mj_jobs` ADD UNIQUE(`task_id`);
ALTER TABLE `chatgpt_api_keys` ADD `use_proxy` TINYINT(1) NULL COMMENT '是否使用代理访问' AFTER `enabled`; ALTER TABLE `chatgpt_api_keys` ADD `use_proxy` TINYINT(1) NULL COMMENT '是否使用代理访问' AFTER `enabled`;
ALTER TABLE `chatgpt_api_keys` ADD `name` VARCHAR(30) NULL COMMENT '名称' AFTER `platform`;

View File

@ -15,7 +15,7 @@
</el-upload> </el-upload>
</el-row> </el-row>
<el-form-item label="昵称"> <el-form-item label="昵称">
{{ user['nickname'] }} <el-input v-model="user['nickname']"/>
</el-form-item> </el-form-item>
<el-form-item label="手机号"> <el-form-item label="手机号">
<span>{{ user.mobile }}</span> <span>{{ user.mobile }}</span>
@ -44,16 +44,6 @@
<el-tag type="danger">{{ dateFormat(user['expired_time']) }}</el-tag> <el-tag type="danger">{{ dateFormat(user['expired_time']) }}</el-tag>
</el-form-item> </el-form-item>
<el-form-item label="OpenAI API KEY">
<el-input v-model="user.chat_config['api_keys']['OpenAI']"/>
</el-form-item>
<el-form-item label="Azure API KEY">
<el-input v-model="user['chat_config']['api_keys']['Azure']"/>
</el-form-item>
<el-form-item label="ChatGLM API KEY">
<el-input v-model="user['chat_config']['api_keys']['ChatGLM']"/>
</el-form-item>
<el-row class="opt-line"> <el-row class="opt-line">
<el-button color="#47fff1" :dark="false" round @click="save">保存</el-button> <el-button color="#47fff1" :dark="false" round @click="save">保存</el-button>
</el-row> </el-row>
@ -78,7 +68,6 @@ const user = ref({
mobile: '', mobile: '',
calls: 0, calls: 0,
tokens: 0, tokens: 0,
chat_config: {api_keys: {OpenAI: "", Azure: "", ChatGLM: ""}}
}) })
const vipImg = ref("/images/vip.png") const vipImg = ref("/images/vip.png")
@ -87,7 +76,6 @@ onMounted(() => {
// //
httpGet('/api/user/profile').then(res => { httpGet('/api/user/profile').then(res => {
user.value = res.data user.value = res.data
user.value.chat_config.api_keys = res.data.chat_config.api_keys ?? {OpenAI: "", Azure: "", ChatGLM: ""}
}).catch(e => { }).catch(e => {
ElMessage.error("获取用户信息失败:" + e.message) ElMessage.error("获取用户信息失败:" + e.message)
}); });

View File

@ -8,7 +8,16 @@
<el-row> <el-row>
<el-table :data="items" :row-key="row => row.id" table-layout="auto"> <el-table :data="items" :row-key="row => row.id" table-layout="auto">
<el-table-column prop="platform" label="所属平台"/> <el-table-column prop="platform" label="所属平台"/>
<el-table-column prop="value" label="KEY"/> <el-table-column prop="name" label="名称"/>
<el-table-column prop="value" label="KEY">
<template #default="scope">
<el-tooltip class="box-item"
effect="dark"
:content="scope.row.api_url"
placement="top">{{ scope.row.value }}
</el-tooltip>
</template>
</el-table-column>
<el-table-column prop="type" label="用途"> <el-table-column prop="type" label="用途">
<template #default="scope"> <template #default="scope">
<el-tag v-if="scope.row.type === 'chat'">聊天</el-tag> <el-tag v-if="scope.row.type === 'chat'">聊天</el-tag>
@ -67,6 +76,9 @@
</el-option> </el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="名称:" prop="name">
<el-input v-model="item.name" autocomplete="off"/>
</el-form-item>
<el-form-item label="用途:" prop="type"> <el-form-item label="用途:" prop="type">
<el-select v-model="item.type" placeholder="请选择用途" @change="changePlatform"> <el-select v-model="item.type" placeholder="请选择用途" @change="changePlatform">
<el-option v-for="item in types" :value="item.value" :label="item.name" :key="item.value">{{ <el-option v-for="item in types" :value="item.value" :label="item.name" :key="item.value">{{
@ -125,6 +137,7 @@ const item = ref({})
const showDialog = ref(false) const showDialog = ref(false)
const rules = reactive({ const rules = reactive({
platform: [{required: true, message: '请选择平台', trigger: 'change',}], platform: [{required: true, message: '请选择平台', trigger: 'change',}],
name: [{required: true, message: '请输入名称', trigger: 'change',}],
type: [{required: true, message: '请选择用途', trigger: 'change',}], type: [{required: true, message: '请选择用途', trigger: 'change',}],
value: [{required: true, message: '请输入 API KEY 值', trigger: 'change',}] value: [{required: true, message: '请输入 API KEY 值', trigger: 'change',}]
}) })
@ -135,8 +148,8 @@ const platforms = ref([
{ {
name: "【OpenAI】ChatGPT", name: "【OpenAI】ChatGPT",
value: "OpenAI", value: "OpenAI",
api_url: "https://api.fast-tunnel.one/v1/chat/completions", api_url: "https://gpt.bemore.lol/v1/chat/completions",
img_url: "https://api.openai.com/v1/images/generations" img_url: "https://gpt.bemore.lol/v1/images/generations"
}, },
{ {
name: "【讯飞】星火大模型", name: "【讯飞】星火大模型",

View File

@ -204,9 +204,6 @@
</el-form-item> </el-form-item>
<el-divider content-position="center">OpenAI</el-divider> <el-divider content-position="center">OpenAI</el-divider>
<el-form-item label="API 地址" prop="open_ai.api_url">
<el-input v-model="chat['open_ai']['api_url']" placeholder="支持变量,{model} => 模型名称"/>
</el-form-item>
<el-form-item label="模型创意度"> <el-form-item label="模型创意度">
<el-slider v-model="chat['open_ai']['temperature']" :max="2" :step="0.1"/> <el-slider v-model="chat['open_ai']['temperature']" :max="2" :step="0.1"/>
<div class="tip">值越大 AI 回答越发散值越小回答越保守建议保持默认值</div> <div class="tip">值越大 AI 回答越发散值越小回答越保守建议保持默认值</div>
@ -216,9 +213,6 @@
</el-form-item> </el-form-item>
<el-divider content-position="center">Azure</el-divider> <el-divider content-position="center">Azure</el-divider>
<el-form-item label="API 地址" prop="azure.api_url">
<el-input v-model="chat['azure']['api_url']" placeholder="支持变量,{model} => 模型名称"/>
</el-form-item>
<el-form-item label="模型创意度"> <el-form-item label="模型创意度">
<el-slider v-model="chat['azure']['temperature']" :max="2" :step="0.1"/> <el-slider v-model="chat['azure']['temperature']" :max="2" :step="0.1"/>
<div class="tip">值越大 AI 回答越发散值越小回答越保守建议保持默认值</div> <div class="tip">值越大 AI 回答越发散值越小回答越保守建议保持默认值</div>
@ -228,9 +222,6 @@
</el-form-item> </el-form-item>
<el-divider content-position="center">ChatGLM</el-divider> <el-divider content-position="center">ChatGLM</el-divider>
<el-form-item label="API 地址" prop="chat_gml.api_url">
<el-input v-model="chat['chat_gml']['api_url']" placeholder="支持变量,{model} => 模型名称"/>
</el-form-item>
<el-form-item label="模型创意度"> <el-form-item label="模型创意度">
<el-slider v-model="chat['chat_gml']['temperature']" :max="1" :step="0.01"/> <el-slider v-model="chat['chat_gml']['temperature']" :max="1" :step="0.01"/>
<div class="tip">值越大 AI 回答越发散值越小回答越保守建议保持默认值</div> <div class="tip">值越大 AI 回答越发散值越小回答越保守建议保持默认值</div>
@ -240,9 +231,6 @@
</el-form-item> </el-form-item>
<el-divider content-position="center">文心一言</el-divider> <el-divider content-position="center">文心一言</el-divider>
<el-form-item label="API 地址" prop="baidu.api_url">
<el-input v-model="chat['baidu']['api_url']" placeholder="支持变量,{model} => 模型名称"/>
</el-form-item>
<el-form-item label="模型创意度"> <el-form-item label="模型创意度">
<el-slider v-model="chat['baidu']['temperature']" :max="1" :step="0.01"/> <el-slider v-model="chat['baidu']['temperature']" :max="1" :step="0.01"/>
<div class="tip">值越大 AI 回答越发散值越小回答越保守建议保持默认值</div> <div class="tip">值越大 AI 回答越发散值越小回答越保守建议保持默认值</div>
@ -252,9 +240,6 @@
</el-form-item> </el-form-item>
<el-divider content-position="center">讯飞星火</el-divider> <el-divider content-position="center">讯飞星火</el-divider>
<el-form-item label="API 地址" prop="xun_fei.api_url">
<el-input v-model="chat['xun_fei']['api_url']" placeholder="支持变量,{model} => 模型名称"/>
</el-form-item>
<el-form-item label="模型创意度"> <el-form-item label="模型创意度">
<el-slider v-model="chat['xun_fei']['temperature']" :max="1" :step="0.1"/> <el-slider v-model="chat['xun_fei']['temperature']" :max="1" :step="0.1"/>
<div class="tip">值越大 AI 回答越发散值越小回答越保守建议保持默认值</div> <div class="tip">值越大 AI 回答越发散值越小回答越保守建议保持默认值</div>
@ -264,10 +249,7 @@
</el-form-item> </el-form-item>
<el-divider content-position="center">AI绘图</el-divider> <el-divider content-position="center">AI绘图</el-divider>
<el-form-item label="DALL-E3 API地址"> <el-form-item label="DALL-E3出图数量">
<el-input v-model="chat['dall_api_url']" placeholder="OpenAI官方API需要配合代理使用"/>
</el-form-item>
<el-form-item label="默认出图数量">
<el-input v-model.number="chat['dall_img_num']" placeholder="调用 DALL E3 API 传入的出图数量"/> <el-input v-model.number="chat['dall_img_num']" placeholder="调用 DALL E3 API 传入的出图数量"/>
</el-form-item> </el-form-item>
<el-form-item style="text-align: right"> <el-form-item style="text-align: right">
@ -287,11 +269,11 @@ import {InfoFilled, UploadFilled} from "@element-plus/icons-vue";
const system = ref({models: []}) const system = ref({models: []})
const chat = ref({ const chat = ref({
open_ai: {api_url: "", temperature: 1, max_tokens: 1024}, open_ai: {temperature: 1, max_tokens: 1024},
azure: {api_url: "", temperature: 1, max_tokens: 1024}, azure: {temperature: 1, max_tokens: 1024},
chat_gml: {api_url: "", temperature: 0.95, max_tokens: 1024}, chat_gml: {temperature: 0.95, max_tokens: 1024},
baidu: {api_url: "", temperature: 0.95, max_tokens: 1024}, baidu: {temperature: 0.95, max_tokens: 1024},
xun_fei: {api_url: "", temperature: 0.5, max_tokens: 1024}, xun_fei: {temperature: 0.5, max_tokens: 1024},
context_deep: 0, context_deep: 0,
enable_context: true, enable_context: true,
enable_history: true, enable_history: true,