246 lines
5.9 KiB
Go
246 lines
5.9 KiB
Go
|
|
package flow
|
||
|
|
|
||
|
|
import (
|
||
|
|
"ai-agent/workflow/model/dto"
|
||
|
|
flowDto "ai-agent/workflow/model/dto/flow"
|
||
|
|
"bytes"
|
||
|
|
"context"
|
||
|
|
"fmt"
|
||
|
|
"io"
|
||
|
|
"mime/multipart"
|
||
|
|
"net/http"
|
||
|
|
"strings"
|
||
|
|
|
||
|
|
commonHttp "gitea.com/red-future/common/http"
|
||
|
|
"gitea.com/red-future/common/utils"
|
||
|
|
"github.com/gogf/gf/v2/frame/g"
|
||
|
|
"github.com/gogf/gf/v2/util/gconv"
|
||
|
|
)
|
||
|
|
|
||
|
|
func GetIsChatModel(ctx context.Context) (*flowDto.GetIsChatModelRes, error) {
|
||
|
|
headers := make(map[string]string)
|
||
|
|
if r := g.RequestFromCtx(ctx); r != nil {
|
||
|
|
for k, v := range r.Request.Header {
|
||
|
|
if len(v) > 0 {
|
||
|
|
headers[k] = v[0]
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
res := new(flowDto.GetIsChatModelRes)
|
||
|
|
err := commonHttp.Get(ctx, "model-gateway/model/getIsChatModel", headers, res, nil)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
return res, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func CreateGatewayTask(ctx context.Context, req *flowDto.CreateTaskReq) (string, error) {
|
||
|
|
headers := make(map[string]string)
|
||
|
|
if r := g.RequestFromCtx(ctx); r != nil {
|
||
|
|
for k, v := range r.Request.Header {
|
||
|
|
if len(v) > 0 {
|
||
|
|
headers[k] = v[0]
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
res := new(flowDto.CreateTaskRes)
|
||
|
|
err := commonHttp.Post(ctx, "model-gateway/task/createTask", headers, res, &req)
|
||
|
|
if err != nil {
|
||
|
|
return "", err
|
||
|
|
}
|
||
|
|
|
||
|
|
return res.TaskId, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func ComposeMessages(ctx context.Context, req *flowDto.ComposeMessagesReq) (*flowDto.ComposeMessagesRes, error) {
|
||
|
|
headers := make(map[string]string)
|
||
|
|
if r := g.RequestFromCtx(ctx); r != nil {
|
||
|
|
for k, v := range r.Request.Header {
|
||
|
|
if len(v) > 0 {
|
||
|
|
headers[k] = v[0]
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
res := new(flowDto.ComposeMessagesRes)
|
||
|
|
err := commonHttp.Post(ctx, "prompts-core/prompt/composeMessages", headers, res, &req)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
return res, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func GatewayTask(ctx context.Context, epicycleId int64, model string, content map[string]any) (any, error) {
|
||
|
|
modelTaskId, err := CreateGatewayTask(ctx, &flowDto.CreateTaskReq{
|
||
|
|
ModelName: model,
|
||
|
|
BizName: g.Cfg().MustGet(ctx, "server.name").String(),
|
||
|
|
CallbackUrl: "/flow/execution/modelCallback",
|
||
|
|
RequestPayload: content,
|
||
|
|
EpicycleId: epicycleId,
|
||
|
|
})
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
return Wait(ctx, modelTaskId)
|
||
|
|
}
|
||
|
|
|
||
|
|
func GetTaskResult(ctx context.Context, result any) (*flowDto.TaskCallback, error) {
|
||
|
|
task := new(flowDto.TaskCallback)
|
||
|
|
if err := gconv.Struct(result, task); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
url, err := utils.GetFileAddressPrefix(ctx)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
// 获取远程文件内容
|
||
|
|
file, err := FetchRemoteJsonFile(ctx, url+task.OssFile)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
task.Text = gconv.String(file)
|
||
|
|
|
||
|
|
return task, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func FetchRemoteJsonFile(ctx context.Context, fileUrl string) ([]byte, error) {
|
||
|
|
// 1. 下载文件
|
||
|
|
resp, err := g.Client().Get(ctx, fileUrl)
|
||
|
|
if err != nil {
|
||
|
|
return nil, fmt.Errorf("get file failed: %w", err)
|
||
|
|
}
|
||
|
|
defer resp.Close()
|
||
|
|
|
||
|
|
if resp.StatusCode != http.StatusOK {
|
||
|
|
return nil, fmt.Errorf("http status error: %d", resp.StatusCode)
|
||
|
|
}
|
||
|
|
|
||
|
|
return io.ReadAll(resp.Body)
|
||
|
|
}
|
||
|
|
|
||
|
|
func GetImageBytesFromURL(url string) (all []byte, contentType string, err error) {
|
||
|
|
resp, err := http.Get(url)
|
||
|
|
if err != nil {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
defer resp.Body.Close()
|
||
|
|
all, err = io.ReadAll(resp.Body)
|
||
|
|
if err != nil {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
contentType = resp.Header.Get("Content-Type")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
func Upload(ctx context.Context, req *dto.UploadFileBytesReq) (*dto.UploadFileBytesRes, error) {
|
||
|
|
body := &bytes.Buffer{}
|
||
|
|
writer := multipart.NewWriter(body)
|
||
|
|
|
||
|
|
part, err := writer.CreateFormFile("file", req.FileName)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
if _, err = part.Write(req.FileBytes); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
if err = writer.Close(); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
headers := make(map[string]string)
|
||
|
|
headers["Content-Type"] = writer.FormDataContentType()
|
||
|
|
if r := g.RequestFromCtx(ctx); r != nil {
|
||
|
|
if auth := r.Header.Get("Authorization"); auth != "" {
|
||
|
|
headers["Authorization"] = auth
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 发起上传请求
|
||
|
|
res := &dto.UploadFileBytesRes{}
|
||
|
|
url := "oss/file/uploadFile"
|
||
|
|
if err = commonHttp.Post(ctx, url, headers, res, body.Bytes()); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
g.Log().Infof(ctx, "[Upload] success url=%s size=%d", res.FileURL, res.FileSize)
|
||
|
|
return res, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func buildMergeHtml(texts []string, images []string) string {
|
||
|
|
html := strings.Builder{}
|
||
|
|
|
||
|
|
html.WriteString(`
|
||
|
|
<!DOCTYPE html>
|
||
|
|
<html lang="zh-CN">
|
||
|
|
<head>
|
||
|
|
<meta charset="UTF-8">
|
||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
|
|
<style>
|
||
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
|
|
body {
|
||
|
|
font-family: "Microsoft YaHei", Arial, sans-serif;
|
||
|
|
background: #fff;
|
||
|
|
color: #333;
|
||
|
|
}
|
||
|
|
.container {
|
||
|
|
max-width: 960px;
|
||
|
|
margin: 0 auto;
|
||
|
|
}
|
||
|
|
/* 图片:完全贴边,无额外间距 */
|
||
|
|
.image-block {
|
||
|
|
width: 100%;
|
||
|
|
margin: 0;
|
||
|
|
padding: 0;
|
||
|
|
}
|
||
|
|
.image-block img {
|
||
|
|
width: 100%;
|
||
|
|
height: auto;
|
||
|
|
display: block;
|
||
|
|
border-radius: 0;
|
||
|
|
}
|
||
|
|
/* 文案:极致紧凑 */
|
||
|
|
.text-block {
|
||
|
|
margin: 0;
|
||
|
|
padding: 16px; /* 仅保留内边距,不设外边距 */
|
||
|
|
line-height: 1.6;
|
||
|
|
font-size: 14px;
|
||
|
|
color: #444;
|
||
|
|
white-space: pre-wrap;
|
||
|
|
}
|
||
|
|
/* 分割线:完全去掉,改用内边距自然分隔 */
|
||
|
|
</style>
|
||
|
|
</head>
|
||
|
|
<body>
|
||
|
|
<div class="container">
|
||
|
|
`)
|
||
|
|
|
||
|
|
// 1. 先渲染图片(无任何上下边距,占满宽度)
|
||
|
|
if len(images) > 0 {
|
||
|
|
html.WriteString(`<div class="image-block">`)
|
||
|
|
for _, img := range images {
|
||
|
|
html.WriteString(fmt.Sprintf(`<img src="%s" alt="" />`, img))
|
||
|
|
}
|
||
|
|
html.WriteString(`</div>`)
|
||
|
|
}
|
||
|
|
|
||
|
|
// 2. 渲染文案(紧贴图片下方,仅用内边距留白)
|
||
|
|
if len(texts) > 0 {
|
||
|
|
html.WriteString(`<div class="text-block">`)
|
||
|
|
// 段落之间用 <br> 而不是 <br><br>,减少空行
|
||
|
|
html.WriteString(strings.Join(texts, "<br>"))
|
||
|
|
html.WriteString(`</div>`)
|
||
|
|
}
|
||
|
|
|
||
|
|
html.WriteString(`
|
||
|
|
</div>
|
||
|
|
</body>
|
||
|
|
</html>
|
||
|
|
`)
|
||
|
|
|
||
|
|
return html.String()
|
||
|
|
}
|