thread-master/backend/internal/library/formula/analyze_pasted.go

165 lines
5.0 KiB
Go
Raw Normal View History

2026-07-01 08:42:51 +00:00
package formula
import (
"context"
"encoding/json"
"fmt"
"regexp"
"strings"
libownpost "haixun-backend/internal/library/ownpost"
domai "haixun-backend/internal/model/ai/domain/usecase"
)
var pastedFenceRE = regexp.MustCompile("(?s)```(?:json)?\\s*([\\s\\S]*?)```")
type AnalyzePastedInput struct {
PostText string
AuthorName string
PersonaBlock string
LikeCount int
ReplyCount int
}
type PastedAnalysis 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 BuildAnalyzePastedPrompt(in AnalyzePastedInput) (system string, user string) {
personaBlock := strings.TrimSpace(in.PersonaBlock)
if personaBlock == "" {
personaBlock = "(未指定人設,請依貼文本身語氣分析)"
}
postText := strings.TrimSpace(in.PostText)
if postText == "" {
postText = "(貼文內容未提供)"
}
author := strings.TrimSpace(in.AuthorName)
if author == "" {
author = "匿名"
}
system = strings.TrimSpace(`
你是 Threads 爆款結構分析師任務是拆解外部貼文為什麼會紅怎麼用結構仿寫不要建議抄襲原文
規則
- 用台灣繁體中文
- formula整理成步驟化公式Hook情境轉折觀點收尾/互動
- post_template formula 產出可複製的發文骨架 [括號] 標示可替換欄位
- 只輸出一個 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("【人設脈絡】\n")
b.WriteString(personaBlock)
b.WriteString("\n\n【貼文】\n作者")
b.WriteString(author)
if in.LikeCount > 0 || in.ReplyCount > 0 {
b.WriteString(fmt.Sprintf(" · %d 讚 · %d 留言", in.LikeCount, in.ReplyCount))
}
b.WriteString("\n")
b.WriteString(postText)
b.WriteString("\n\n請分析爆款結構並輸出 JSON。")
user = b.String()
return system, user
}
func AnalyzePasted(ctx context.Context, ai domai.UseCase, req domai.GenerateRequest, in AnalyzePastedInput) (*PastedAnalysis, error) {
system, user := BuildAnalyzePastedPrompt(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
}
return ParsePastedAnalysis(out.Text)
}
func ParsePastedAnalysis(raw string) (*PastedAnalysis, error) {
payload, err := extractPastedJSON(raw)
if err != nil {
return nil, err
}
var review PastedAnalysis
if err := json.Unmarshal(payload, &review); err != nil {
return nil, fmt.Errorf("parse pasted 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)
if review.Summary == "" && review.Formula == "" && review.PostTemplate == "" {
return nil, fmt.Errorf("AI 未回傳有效公式內容")
}
return &review, nil
}
func FromOwnPostReview(review *libownpost.PostFormulaReview) *PastedAnalysis {
if review == nil {
return nil
}
return &PastedAnalysis{
Summary: review.Summary,
Wins: review.Wins,
Improvements: review.Improvements,
Formula: review.Formula,
PostTemplate: review.PostTemplate,
HookPattern: review.HookPattern,
Structure: review.Structure,
ReplicationTips: review.ReplicationTips,
Avoid: review.Avoid,
}
}
func FromViralAnalysis(hook, structure, emotion, strategy string, takeaways []string) *PastedAnalysis {
summary := strings.TrimSpace(emotion)
if summary == "" {
summary = "爆款結構分析"
}
formula := strings.TrimSpace(strategy)
if structure != "" {
if formula != "" {
formula = structure + " → " + formula
} else {
formula = structure
}
}
template := ""
if hook != "" && structure != "" {
template = "[" + hook + "] → [" + structure + "] → [你的觀點] → [互動收尾]"
}
return &PastedAnalysis{
Summary: summary,
Wins: takeaways,
Formula: formula,
PostTemplate: template,
HookPattern: hook,
Structure: structure,
ReplicationTips: takeaways,
}
}
func extractPastedJSON(raw string) ([]byte, error) {
raw = strings.TrimSpace(raw)
if m := pastedFenceRE.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
}