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

165 lines
5.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}