fonchain-fiee/pkg/service/chat.go

615 lines
16 KiB
Go
Raw Normal View History

2025-02-19 06:24:15 +00:00
package service
import (
"bytes"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"github.com/carmel/gooxml/document"
"github.com/fonchain_enterprise/fonchain-main/pkg/config"
"github.com/fonchain_enterprise/fonchain-main/pkg/e"
"github.com/fonchain_enterprise/fonchain-main/pkg/model"
"github.com/fonchain_enterprise/fonchain-main/pkg/request"
"github.com/fonchain_enterprise/fonchain-main/pkg/response"
"github.com/fonchain_enterprise/fonchain-main/pkg/service/aliyun"
"github.com/fonchain_enterprise/fonchain-main/pkg/utils"
"github.com/fonchain_enterprise/fonchain-main/pkg/utils/stringutils"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"os"
"path/filepath"
"regexp"
"strings"
"time"
)
type ChatRequestData struct {
Model string `json:"model"`
MaxTokens int `json:"max_tokens"`
Temperature float64 `json:"temperature"`
TopP float64 `json:"top_p"`
PresencePenalty float64 `json:"presence_penalty"`
FrequencyPenalty float64 `json:"frequency_penalty"`
Messages []Message `json:"messages"`
Stream bool `json:"stream"`
ListUuid string `json:"listUuid"`
Type string `json:"type"`
}
type Message struct {
Role string `json:"role"`
Content interface{} `json:"content"`
}
type RequestData struct {
Model string `json:"model"`
MaxTokens int `json:"max_tokens"`
Temperature float64 `json:"temperature"`
TopP float64 `json:"top_p"`
PresencePenalty float64 `json:"presence_penalty"`
FrequencyPenalty float64 `json:"frequency_penalty"`
}
type ChatMsg struct {
ID string `json:"id"`
Object string `json:"object"`
Created int `json:"created"`
Model string `json:"model"`
Choices []Choice `json:"choices"`
}
type Choice struct {
Index int `json:"index"`
Delta Delta `json:"delta"`
FinishReason string `json:"finish_reason"`
}
type Delta struct {
Content string `json:"content"`
}
type DefineRequest struct {
Model string `json:"model"`
Stream bool `json:"stream,omitempty"`
Stop []string `json:"stop,omitempty"`
PresencePenalty float32 `json:"presence_penalty,omitempty"`
FrequencyPenalty float32 `json:"frequency_penalty,omitempty"`
// LogitBias is must be a token id string (specified by their token ID in the tokenizer), not a word string.
// incorrect: `"logit_bias":{"You": 6}`, correct: `"logit_bias":{"1639": 6}`
// refs: https://platform.openai.com/docs/api-reference/chat/create#chat/create-logit_bias
LogitBias map[string]int `json:"logit_bias,omitempty"`
User string `json:"user,omitempty"`
}
type MessageContentImage struct {
Type string `json:"type"`
Text string `json:"text"`
ImageUrl string `json:"image_url"`
}
func Completion(ctx *gin.Context) {
var request ChatRequestData
bodyByte, _ := io.ReadAll(ctx.Request.Body)
ctx.Request.Body = io.NopCloser(bytes.NewBuffer(bodyByte))
_ = json.Unmarshal(bodyByte, &request)
ctx.Header("Content-Type", "text/event-stream")
ctx.Header("Cache-Control", "no-cache")
ctx.Header("Connection", "keep-alive")
//url := config.ChatGptHost + "/chat/completion"
url := config.ChatGptHost + "/baidu-chat/chat"
//url := "http://114.217.150.188:9011" + "/baidu-chat/chat"
var jsonData []byte
var err error
request, err = NewRequest(request)
if err != nil {
ResponseQuickMsg(ctx, e.Failed, err.Error(), nil)
return
}
if request.ListUuid == "" {
request.ListUuid = "web"
}
jsonData, err = json.Marshal(request)
if err != nil {
ResponseQuickMsg(ctx, e.Failed, e.GetMsg(e.InvalidParams), nil)
return
}
zap.L().Info("Completion", zap.Any("url", url))
zap.L().Info("Completion", zap.Any("jsonData", jsonData))
resp, errA := http.Post(url, "application/json", bytes.NewBuffer(jsonData))
if errA != nil {
zap.L().Error("Completion", zap.Error(errA))
ResponseQuickMsg(ctx, e.Failed, errA.Error(), nil)
return
}
defer resp.Body.Close()
ctx.Stream(func(w io.Writer) bool {
buf := make([]byte, 4096)
for {
n, errS := resp.Body.Read(buf)
if n > 0 {
msg := string(buf[:n])
ctx.Stream(func(w io.Writer) bool {
if strings.Contains(msg, "DONE") {
fmt.Println(msg)
fmt.Fprint(w, msg)
return false
}
fmt.Println(msg)
zap.L().Info("Completion", zap.Any("msg", msg))
fmt.Fprint(w, msg)
return false
})
}
if errS != nil {
return false
}
}
})
}
func AppCompletion(ctx *gin.Context) {
var request ChatRequestData
err := ctx.BindJSON(&request)
if err != nil {
ResponseQuickMsg(ctx, e.Failed, err.Error(), nil)
return
}
ctx.Header("Content-Type", "text/event-stream")
ctx.Header("Cache-Control", "no-cache")
ctx.Header("Connection", "keep-alive")
url := config.ChatGptHost + "/baidu-chat/chat"
//url := "http://127.0.0.1:9010/chat/completion"
request, err = NewRequest(request)
if err != nil {
ResponseQuickMsg(ctx, e.Failed, err.Error(), nil)
return
}
jsonData, err := json.Marshal(request)
if err != nil {
ResponseQuickMsg(ctx, e.Failed, e.GetMsg(e.InvalidParams), nil)
return
}
if request.ListUuid == "" {
request.ListUuid = "app"
}
resp, errA := http.Post(url, "application/json", bytes.NewBuffer(jsonData))
if err != nil {
ResponseQuickMsg(ctx, e.Failed, errA.Error(), nil)
return
}
defer resp.Body.Close()
/*var chatData ChatMsg
var content string
ctx.Stream(func(w io.Writer) bool {
buf := make([]byte, 4096)
for {
n, errS := resp.Body.Read(buf)
if errS != nil {
return false
}
if n > 0 {
msg := string(buf[:n])
msg = strings.Replace(msg, `\n`, "", -1)
if len(msg) > 0 && msg[len(msg)-1] == '\n' {
msg = msg[:len(msg)-1]
}
re := regexp.MustCompile(`data:\s*({.+?}]})`)
matches := re.FindAllStringSubmatch(msg, -1)
for _, match := range matches {
msg = match[1]
fmt.Println("msg1111", msg)
if !strings.Contains(msg, "[DONE]") {
fmt.Println("msg222", msg)
_ = json.Unmarshal([]byte(msg), &chatData)
fmt.Println("msg333", chatData)
for _, choice := range chatData.Choices {
content = fmt.Sprintf("%s%s", content, choice.Delta.Content)
}
}
}
}
}
}
if err != nil {
response.ResponseQuickMsg(ctx, e.Failed, err.Error(), nil)
return
}
//fmt.Println("ap-complete", string(jsonData), content)
response.ResponseQuickMsg(ctx, e.Ok, "", map[string]interface{}{
"content": content,
})*/
defer resp.Body.Close()
ctx.Stream(func(w io.Writer) bool {
buf := make([]byte, 4096)
for {
n, errS := resp.Body.Read(buf)
if n > 0 {
msg := string(buf[:n])
ctx.Stream(func(w io.Writer) bool {
if strings.Contains(msg, "DONE") {
fmt.Println(msg)
fmt.Fprint(w, msg)
return false
}
fmt.Println(msg)
fmt.Fprint(w, msg)
return false
})
}
if errS != nil {
return false
}
}
})
return
}
func AudioToText(c *gin.Context) {
err := c.Request.ParseMultipartForm(32 << 20) // 限制最大文件大小
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
file, handler, err := c.Request.FormFile("file")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
defer file.Close()
body := new(bytes.Buffer)
writer := multipart.NewWriter(body)
part, err := writer.CreateFormFile("file", handler.Filename)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
_, err = io.Copy(part, file)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
err = writer.Close()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// 创建一个新的请求,将文件发送到美国接口
client := &http.Client{}
url := config.ChatGptHost + "/chat/audio-to-text"
req, err := http.NewRequest("POST", url, body)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
req.Header.Set("Content-Type", writer.FormDataContentType())
resp, err := client.Do(req)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
c.JSON(http.StatusInternalServerError, gin.H{"error": resp.Status})
return
}
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
var audioToTextInfo model.AudioToTextResp
if err = json.Unmarshal(data, &audioToTextInfo); err != nil {
response.ResponseQuickMsg(c, e.Failed, e.GetMsg(e.JsonUnmarshal), nil)
}
response.ResponseQuickMsg(c, e.Failed, e.GetMsg(e.Success), map[string]interface{}{
"text": audioToTextInfo.Data.Text,
})
return
}
func ImageUrlToBase64(imageURL string) (base64Val string, err error) {
resp, err := http.Get(imageURL)
if err != nil {
err = errors.New("读取图片错误")
return
}
defer resp.Body.Close()
imageData, err := ioutil.ReadAll(resp.Body)
if err != nil {
err = errors.New("读取图片内容失败")
return
}
base64Val = "data:image/png;base64," + base64.StdEncoding.EncodeToString(imageData)
return
}
func NewRequest(request ChatRequestData) (resp ChatRequestData, err error) {
resp = request
return
if request.Model == "gpt-4-vision-preview" {
//bodyByte, _ := io.ReadAll(ctx.Request.Body)
for key, messages := range request.Messages {
var contentData []map[string]string
contentBytes, _ := json.Marshal(messages.Content)
_ = json.Unmarshal(contentBytes, &contentData)
var needModify bool
for kk, content := range contentData {
needModify = false
if strings.Contains(content["image_url"], "http") {
needModify = true
contentData[kk]["image_url_origin"] = content["image_url"]
if content["image_url"], err = ImageUrlToBase64(content["image_url"]); err != nil {
return
}
contentData[kk]["image_url"] = content["image_url"]
}
fmt.Println(kk)
fmt.Println(contentData)
}
fmt.Println(contentData)
fmt.Println(key)
if needModify {
var mm []interface{}
cc, _ := json.Marshal(contentData)
_ = json.Unmarshal([]byte(cc), &mm)
request.Messages[key].Content = mm
}
}
}
resp = request
return
}
func ChatList(ctx *gin.Context) {
var (
req request.ChatListReq
err error
)
if err = ctx.BindJSON(&req); err != nil {
response.ResponseQuickMsg(ctx, e.Failed, e.GetMsg(e.InvalidParams), nil)
return
}
if req.UserUuid == "" {
if mLoginInfoAny, exists := ctx.Get("mLoginInfo"); exists {
userInfo := mLoginInfoAny.(model.LoginInfo)
req.UserUuid = fmt.Sprint(userInfo.ID)
}
}
if req.UserUuid == "" {
response.ResponseQuickMsg(ctx, e.Failed, e.ErrNotLogin, nil)
return
}
code, body, err := utils.PostBody(config.ChatGptHost+"/chat/list", req)
if err != nil {
response.ResponseQuickMsg(ctx, e.Failed, err.Error(), nil)
return
}
fmt.Println(code)
var newData interface{}
_ = json.Unmarshal([]byte(body), &newData)
ctx.JSON(http.StatusOK, newData)
return
}
func ChatCreate(ctx *gin.Context) {
var req request.ChatCreateReq
var err error
if err = ctx.BindJSON(&req); err != nil {
response.ResponseQuickMsg(ctx, e.Failed, e.GetMsg(e.InvalidParams), nil)
return
}
if mLoginInfoAny, exists := ctx.Get("mLoginInfo"); exists {
userInfo := mLoginInfoAny.(model.LoginInfo)
req.UserUuid = fmt.Sprint(userInfo.ID)
}
if req.GptModel == "" {
response.ResponseQuickMsg(ctx, e.Failed, e.GetMsg(e.InvalidParams), nil)
return
}
code, body, err := utils.PostBody(config.ChatGptHost+"/chat/create", req)
if err != nil {
response.ResponseQuickMsg(ctx, e.Failed, err.Error(), nil)
return
}
fmt.Println(code)
var newData interface{}
_ = json.Unmarshal([]byte(body), &newData)
ctx.JSON(http.StatusOK, newData)
return
}
func ChatDel(ctx *gin.Context) {
var (
req request.DelListReq
err error
)
if err = ctx.BindJSON(&req); err != nil {
response.ResponseQuickMsg(ctx, e.Failed, e.GetMsg(e.InvalidParams), nil)
return
}
code, body, err := utils.PostBody(config.ChatGptHost+"/chat/del", req)
if err != nil {
response.ResponseQuickMsg(ctx, e.Failed, err.Error(), nil)
return
}
fmt.Println(code)
var newData interface{}
_ = json.Unmarshal([]byte(body), &newData)
ctx.JSON(http.StatusOK, newData)
return
}
func ChatDetail(ctx *gin.Context) {
var (
req request.ListDetailReq
err error
)
if err = ctx.BindJSON(&req); err != nil {
response.ResponseQuickMsg(ctx, e.Failed, e.GetMsg(e.InvalidParams), nil)
return
}
var newData interface{}
var body string
var code int
if req.GptModel == "gpt-4-vision-preview" {
file, _ := os.Open("./data/mock/detail.txt")
defer file.Close()
bodyBytes, _ := io.ReadAll(file)
body = string(bodyBytes)
} else {
code, body, err = utils.PostBody(config.ChatGptHost+"/chat/detail", req)
if err != nil {
response.ResponseQuickMsg(ctx, e.Failed, err.Error(), nil)
return
}
fmt.Println(code)
}
_ = json.Unmarshal([]byte(body), &newData)
ctx.JSON(http.StatusOK, newData)
return
}
func GetFileText(ctx *gin.Context) {
var (
filePath string
text string
subtexts []string
)
var wordExts string = ".doc,.docx"
// doc提交调用API解析
file, err := ctx.FormFile("file")
if err != nil {
response.ResponseQuickMsg(ctx, e.Failed, e.GetMsg(e.ErrorReadFile), nil)
return
}
filePath = fmt.Sprintf("%s%s", model.MediaPath, file.Filename)
fileExt := filepath.Ext(filePath)
if !strings.Contains(wordExts, fileExt) {
response.ResponseQuickMsg(ctx, e.Failed, e.GetMsg(e.ERROR_INVALID_FILE_EXT), nil)
return
}
if fileExt == ".doc" {
fileUrl, _err := quickBos(file, "doc", "file", "word", "")
if _err != nil {
response.ResponseQuickMsg(ctx, e.Failed, _err.Error(), nil)
return
}
docId, _err := aliyun.DocStruct(fileUrl, file.Filename)
if _err != nil {
response.ResponseQuickMsg(ctx, e.Failed, e.GetMsg(e.ErrorUploadFile), nil)
return
}
for {
time.Sleep(1 * time.Second)
complete, texts, errs := aliyun.DocResult(docId)
if errs != nil {
response.ResponseQuickMsg(ctx, e.Failed, e.GetMsg(e.ErrorUploadFile), nil)
return
}
if complete {
for _, v := range texts {
text += strings.Trim(v, "")
}
break
}
}
fmt.Println(text)
} else {
err = ctx.SaveUploadedFile(file, filePath)
if err != nil {
response.ResponseQuickMsg(ctx, e.Failed, e.ErrorSaveFile, nil)
return
}
doc, _err := document.Open(filePath)
defer os.Remove(filePath)
if _err != nil {
response.ResponseQuickMsg(ctx, e.Failed, e.GetMsg(e.ErrorReadFile), nil)
return
}
for _, p := range doc.Paragraphs() {
for _, run := range p.Runs() {
if strings.Trim(run.Text(), "") != "" {
text += strings.Trim(run.Text(), "")
}
}
}
}
subtexts = stringutils.SplitText(text, 15000)
response.ResponseQuickMsg(ctx, e.SUCCESS, e.GetMsg(e.Ok), map[string]interface{}{
"count": len(subtexts),
"paragraph": subtexts,
})
return
}
func CompletionText(ctx *gin.Context) {
var request ChatRequestData
bodyByte, _ := io.ReadAll(ctx.Request.Body)
ctx.Request.Body = io.NopCloser(bytes.NewBuffer(bodyByte))
_ = json.Unmarshal(bodyByte, &request)
ctx.Header("Content-Type", "text/event-stream")
ctx.Header("Cache-Control", "no-cache")
ctx.Header("Connection", "keep-alive")
url := config.ChatGptHost + "/baidu-chat/chat"
var jsonData []byte
var err error
request, err = NewRequest(request)
if err != nil {
ResponseQuickMsg(ctx, e.Failed, err.Error(), nil)
return
}
if request.ListUuid == "" {
request.ListUuid = "web"
}
jsonData, err = json.Marshal(request)
if err != nil {
ResponseQuickMsg(ctx, e.Failed, e.GetMsg(e.InvalidParams), nil)
return
}
zap.L().Info("Completion", zap.Any("url", url))
zap.L().Info("Completion", zap.Any("jsonData", jsonData))
resp, errA := http.Post(url, "application/json", bytes.NewBuffer(jsonData))
if errA != nil {
zap.L().Error("Completion", zap.Error(errA))
ResponseQuickMsg(ctx, e.Failed, errA.Error(), nil)
return
}
defer resp.Body.Close()
// 创建一个 bytes.Buffer并将 resp.Body 中的内容复制到其中
var buf bytes.Buffer
_, err = io.Copy(&buf, resp.Body)
if err != nil {
// 错误处理
ResponseQuickMsg(ctx, e.Failed, err.Error(), nil)
return
}
// 获取所有的数据,并将其转化为字符串
allData := buf.String()
// 打印所有的数据
re := regexp.MustCompile(`"content":"(.*?)"`)
matches := re.FindAllStringSubmatch(allData, -1)
var answer string
for _, match := range matches {
answer = fmt.Sprintf("%s%s", answer, match[1])
}
ResponseQuickMsg(ctx, e.Ok, e.GetMsg(e.Success), map[string]string{
"answer": answer,
})
return
}