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 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) (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 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 BuildText(text string) string { // 生成单条HTML var htmlBuilder strings.Builder htmlBuilder.WriteString(`
`) //// 写入图片(支持0张、1张、多张) //if len(images) > 0 { // htmlBuilder.WriteString(`
`) // for _, imgUrl := range images { // htmlBuilder.WriteString(fmt.Sprintf(`图片`, imgUrl)) // } // htmlBuilder.WriteString(`
`) //} // 🔥 写入文案前:删除

需要配图:X 张

if text != "" { // 正则删除整行 imageTagRegex := regexp.MustCompile(`

[\s\S]*?

`) //re := regexp.MustCompile(`

需要配图:\d+ 张

`) cleanContent := imageTagRegex.ReplaceAllString(text, "") // 写入清理后的文案 htmlBuilder.WriteString(fmt.Sprintf(`
%s
`, cleanContent)) } htmlBuilder.WriteString(`
`) return htmlBuilder.String() } func BuildHtml(text string, images []string) string { var htmlBuilder strings.Builder htmlBuilder.WriteString(`
`) // 写入图片(支持0张、1张、多张) if len(images) > 0 { htmlBuilder.WriteString(`
`) for _, imgUrl := range images { htmlBuilder.WriteString(fmt.Sprintf(`图片`, imgUrl)) } htmlBuilder.WriteString(`
`) } htmlBuilder.WriteString(`
加载中...
`) return htmlBuilder.String() } // ExtractImageCount 从 HTML 内容里提取图片数量(例如从

需要配图:3 张

拿到 3) func ExtractImageCount(content string) int { re := regexp.MustCompile(`

[^\d]*(\d+)[^\d]*

`) match := re.FindStringSubmatch(content) if len(match) >= 2 { num, _ := strconv.Atoi(match[1]) return num } return 0 } // StripHtmlTags 去掉所有HTML标签,保留换行和文本结构,并删除配图标记行 func StripHtmlTags(html string, delImageCount bool) string { if delImageCount { // 🔥 第一步:直接删除整个

...

标签(包含内容) imageTagRegex := regexp.MustCompile(`

[\s\S]*?

`) html = imageTagRegex.ReplaceAllString(html, "") } // 1. 替换块级标签为换行,保证排版 blockTags := regexp.MustCompile(`]*>`) 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 // 正则匹配
包裹的内容 re := regexp.MustCompile(`
([\s\S]*?)
`) matches := re.FindAllStringSubmatch(htmlContent, -1) for _, match := range matches { if len(match) > 1 { // 清理空内容 trimmed := strings.TrimSpace(match[1]) if trimmed != "" { contents = append(contents, trimmed) } } } // 兜底:如果没有匹配到结构化内容,按换行/分隔符拆分 if len(contents) == 0 { contents = strings.Split(htmlContent, "===分隔符===") // 提示词中可新增此兜底规则 } return contents }