thread-master/old/backend/internal/library/ownpost/post_formula.go

163 lines
5.5 KiB
Go
Raw Normal View History

package ownpost
import (
"context"
"encoding/json"
"fmt"
"regexp"
"strings"
domai "haixun-backend/internal/model/ai/domain/usecase"
)
var formulaFenceRE = regexp.MustCompile("(?s)```(?:json)?\\s*([\\s\\S]*?)```")
type PostFormulaInput struct {
AccountName string
PersonaBlock string
PostText string
TopicTag string
MediaType string
LikeCount int
ReplyCount int
Views int
RepostCount int
QuoteCount int
Shares int
Timestamp string
}
type PostFormulaReview struct {
Summary string `json:"summary"`
Wins []string `json:"wins"`
Improvements []string `json:"improvements"`
Formula string `json:"formula"`
PostTemplate string `json:"post_template"`
HookPattern string `json:"hook_pattern"`
Structure string `json:"structure"`
ReplicationTips []string `json:"replication_tips"`
Avoid []string `json:"avoid"`
}
func BuildPostFormulaPrompt(in PostFormulaInput) (system string, user string) {
personaBlock := strings.TrimSpace(in.PersonaBlock)
if personaBlock == "" {
personaBlock = "(未指定人設,請依貼文本身語氣分析)"
}
postText := strings.TrimSpace(in.PostText)
if postText == "" {
postText = "(貼文內容未提供)"
}
system = strings.TrimSpace(`
你是 Threads 內容策略教練任務是針對已發布的單篇貼文做誠實覆盤好的要提炼成可複製公式壞的要指出問題並給具體改善做法不要重寫整篇貼文
規則
- 用台灣繁體中文
- 必須同時寫優點與缺點依成效數據判斷成效普通或偏差時更要直言問題
- 分析要具體可執行禁止空泛稱讚或空泛批評
- formula做對的部分整理成步驟化公式Hook情境轉折觀點收尾/互動
- post_template formula 產出可複製的發文骨架 [括號] 標示可替換欄位讓使用者下次填空就能產文
- improvements每點格式固定為問題改善改善必須是可立刻執行的動作改哪一句加什麼刪什麼換什麼角度
- 只輸出一個 JSON 物件不要 markdown欄位
summary, wins, improvements, formula, post_template, hook_pattern, structure, replication_tips, avoid
- wins / improvements / replication_tips / avoid 25 點字串陣列`)
var b strings.Builder
b.WriteString("【1. 人設】\n")
b.WriteString(personaBlock)
b.WriteString("\n\n帳號")
b.WriteString(strings.TrimSpace(in.AccountName))
b.WriteString("\n\n【2. 貼文內容】\n")
b.WriteString(postText)
if tag := strings.TrimSpace(in.TopicTag); tag != "" {
b.WriteString("\n話題標籤#")
b.WriteString(tag)
}
if mt := strings.TrimSpace(in.MediaType); mt != "" {
b.WriteString("\n媒體類型")
b.WriteString(mt)
}
if ts := strings.TrimSpace(in.Timestamp); ts != "" {
b.WriteString("\n發布時間")
b.WriteString(ts)
}
b.WriteString("\n\n【3. 成效數據】\n")
b.WriteString(fmt.Sprintf("讚 %d回覆 %d瀏覽 %d轉發 %d引用 %d分享 %d",
in.LikeCount, in.ReplyCount, in.Views, in.RepostCount, in.QuoteCount, in.Shares))
b.WriteString("\n\n請誠實覆盤做對了什麼、哪裡不好、怎麼改並把做對的部分整理成下次可複製的公式與發文模板 JSON。")
user = b.String()
return system, user
}
func GeneratePostFormula(ctx context.Context, ai domai.UseCase, req domai.GenerateRequest, in PostFormulaInput) (*PostFormulaReview, error) {
system, user := BuildPostFormulaPrompt(in)
temp := 0.65
req.System = system
req.Messages = []domai.Message{{Role: "user", Content: user}}
req.Temperature = &temp
out, err := ai.GenerateText(ctx, req)
if err != nil {
return nil, err
}
review, err := ParsePostFormulaOutput(out.Text)
if err != nil {
return nil, err
}
return review, nil
}
func ParsePostFormulaOutput(raw string) (*PostFormulaReview, error) {
payload, err := extractFormulaJSONObject(raw)
if err != nil {
return nil, err
}
var review PostFormulaReview
if err := json.Unmarshal(payload, &review); err != nil {
return nil, fmt.Errorf("parse post formula json: %w", err)
}
review.Summary = strings.TrimSpace(review.Summary)
review.Formula = strings.TrimSpace(review.Formula)
review.PostTemplate = strings.TrimSpace(review.PostTemplate)
review.HookPattern = strings.TrimSpace(review.HookPattern)
review.Structure = strings.TrimSpace(review.Structure)
review.Wins = trimStringList(review.Wins)
review.Improvements = trimStringList(review.Improvements)
review.ReplicationTips = trimStringList(review.ReplicationTips)
review.Avoid = trimStringList(review.Avoid)
if review.Summary == "" && review.Formula == "" && review.PostTemplate == "" {
return nil, fmt.Errorf("AI 未回傳有效覆盤內容")
}
return &review, nil
}
func trimStringList(items []string) []string {
if len(items) == 0 {
return nil
}
out := make([]string, 0, len(items))
for _, item := range items {
item = strings.TrimSpace(item)
if item != "" {
out = append(out, item)
}
}
if len(out) == 0 {
return nil
}
return out
}
func extractFormulaJSONObject(raw string) ([]byte, error) {
raw = strings.TrimSpace(raw)
if m := formulaFenceRE.FindStringSubmatch(raw); len(m) == 2 {
raw = strings.TrimSpace(m[1])
}
start := strings.Index(raw, "{")
end := strings.LastIndex(raw, "}")
if start < 0 || end <= start {
return nil, fmt.Errorf("formula output missing json object")
}
return []byte(raw[start : end+1]), nil
}