package flow import ( "ai-agent/workflow/model/dto" flowDto "ai-agent/workflow/model/dto/flow" "bytes" "context" "fmt" "io" "mime/multipart" "net/http" "regexp" "strconv" "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) (res *flowDto.GetIsChatModelRes, err 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) return } func ComposeMessages(ctx context.Context, req *flowDto.ComposeMessagesReq) (res *flowDto.ComposeMessagesRes, err 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) return } func GetModelResult(ctx context.Context, modelName, skillName string, form, userFrom map[string]any, fileUrl []string, sessionId string, cause string) (mapTaskResult map[string]any, err error) { msgReq := flowDto.ComposeMessagesReq{ BuildType: 1, ModelName: modelName, SkillName: skillName, Cause: cause, Form: form, UserForm: userFrom, UserFiles: fileUrl, SessionId: sessionId, } msg, err := ComposeMessages(ctx, &msgReq) if err != nil { return } if g.IsEmpty(msg.Messages) { return nil, fmt.Errorf("msg is empty") } var taskResult any taskResult, err = GatewayTask(ctx, msg.EpicycleId, modelName, msg.Messages) if err != nil { return } var getTaskResult *flowDto.TaskCallback getTaskResult, err = GetTaskResult(ctx, taskResult) if err != nil { return } mapTaskResult = gconv.Map(getTaskResult.Text) return mapTaskResult, 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 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 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 GetFileBytesFromURL(url string) (all []byte, err error) { resp, err := http.Get(url) if err != nil { fmt.Printf("请求失败 %s: %v", url, err) return } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { fmt.Printf("请求失败,状态码: %d\n", resp.StatusCode) return } all, err = io.ReadAll(resp.Body) if err != nil { fmt.Printf("读取内容失败 %s: %v", url, err) return } 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 BuildText(text string) string { // 生成单条HTML var htmlBuilder strings.Builder htmlBuilder.WriteString(`
需要配图:X 张
if text != "" { // 写入清理后的文案 htmlBuilder.WriteString(fmt.Sprintf(`]*>.*?(\d+).*?
`) match := re.FindStringSubmatch(content) if len(match) >= 2 { num, err := strconv.Atoi(match[1]) if err == nil { return num } } return 0 } func ImageTagRegex(html string) string { // 🔥 修复:支持单引号、双引号、空格、换行,100% 删除imageTagRegex := regexp.MustCompile(`
]*>[\s\S]*?
`) return imageTagRegex.ReplaceAllString(html, "") } // StripHtmlTags 去掉所有HTML标签,保留换行和文本结构,并删除配图标记行 func StripHtmlTags(html string) string { // 1. 替换块级标签为换行,保证排版 blockTags := regexp.MustCompile(`?(div|p|h1|h2|h3|h4|h5|h6|li|ul|ol|br|tr|td|th)[^>]*>`) text := blockTags.ReplaceAllString(html, "\n") // 2. 去掉所有剩余的 HTML 标签 allTags := regexp.MustCompile(`<[^>]+>`) text = allTags.ReplaceAllString(text, "") // 4. 清理多余空行(多个换行只保留一个) text = regexp.MustCompile(`\n\s*\n`).ReplaceAllString(text, "\n") // 5. 只去掉首尾空白,中间换行保留 text = strings.TrimSpace(text) return text } // SplitMultiContents 拆分模型返回的多条文案(基于HTML标签分隔) func SplitMultiContents(htmlContent string) []string { var contents []string // 正则匹配