package viral import ( "encoding/json" "fmt" "strings" ) type ViralAnalysis struct { StoryHook string `json:"storyHook"` NarrativeBeat string `json:"narrativeBeat"` EmotionalCore string `json:"emotionalCore"` SceneSeed string `json:"sceneSeed"` AuthenticityCues []string `json:"authenticityCues"` WhatToAvoid string `json:"whatToAvoid"` // Legacy fields kept for backward-compatible parsing. HookPattern string `json:"hookPattern"` StructurePattern string `json:"structurePattern"` EmotionalTrigger string `json:"emotionalTrigger"` ReplicationStrategy string `json:"replicationStrategy"` KeyTakeaways []string `json:"keyTakeaways"` } type AnalyzeViralInput struct { PostText string AuthorName string LikeCount int ReplyCount int SearchTag string TopicLabel string TopicBrief string Persona string } func BuildAnalyzeViralSystemPrompt() string { return strings.TrimSpace(`你是 Threads 敘事分析師。拆解這篇貼文為什麼讓人想看完、怎麼寫出同樣的故事感(不是抄句型)。 只回傳 JSON(繁體中文): - storyHook:讓人停下滑動的開場力量(描述手法,不要給可照抄的句子) - narrativeBeat:故事怎麼推進(經歷/轉折/感受,不是段落模板) - emotionalCore:核心情緒是什麼 - sceneSeed:一個值得借鑑的「具體場景種子」(時間/地點/動作,供啟發勿照搬) - authenticityCues:3~5 個讓它像真人的小細節(口語、坦白、不完美、具體細節等) - whatToAvoid:若照搬哪些寫法會立刻變成 AI 複製文`) } func BuildAnalyzeViralUserPrompt(in AnalyzeViralInput) string { var b strings.Builder b.WriteString("主題:") b.WriteString(strings.TrimSpace(in.TopicLabel)) b.WriteString("\n") if brief := strings.TrimSpace(in.TopicBrief); brief != "" { b.WriteString("Brief:") b.WriteString(brief) b.WriteString("\n") } if tag := strings.TrimSpace(in.SearchTag); tag != "" { b.WriteString("搜尋標籤:") b.WriteString(tag) b.WriteString("\n") } author := strings.TrimSpace(in.AuthorName) if author == "" { author = "匿名" } b.WriteString(fmt.Sprintf("\n作者 @%s · %d 讚 · %d 留言\n", author, in.LikeCount, in.ReplyCount)) b.WriteString("\n貼文:\n") b.WriteString(strings.TrimSpace(in.PostText)) return b.String() } func ParseAnalyzeViralOutput(raw string) (ViralAnalysis, error) { payload, err := extractCopyMapJSON(raw) if err != nil { return ViralAnalysis{}, err } var out ViralAnalysis if err := json.Unmarshal(payload, &out); err != nil { return ViralAnalysis{}, fmt.Errorf("parse viral analysis: %w", err) } if strings.TrimSpace(out.StoryHook) == "" && strings.TrimSpace(out.NarrativeBeat) == "" && strings.TrimSpace(out.HookPattern) == "" && strings.TrimSpace(out.StructurePattern) == "" { return ViralAnalysis{}, fmt.Errorf("viral analysis missing content") } return out, nil } func FormatAnalysisForReplicate(analysis ViralAnalysis) string { var b strings.Builder if hook := firstNonEmpty(analysis.StoryHook, analysis.HookPattern); hook != "" { b.WriteString("故事開場力量:") b.WriteString(hook) b.WriteString("\n") } if beat := firstNonEmpty(analysis.NarrativeBeat, analysis.StructurePattern); beat != "" { b.WriteString("敘事推進:") b.WriteString(beat) b.WriteString("\n") } if emotion := firstNonEmpty(analysis.EmotionalCore, analysis.EmotionalTrigger); emotion != "" { b.WriteString("核心情緒:") b.WriteString(emotion) b.WriteString("\n") } if scene := strings.TrimSpace(analysis.SceneSeed); scene != "" { b.WriteString("場景種子(啟發用,勿照搬):") b.WriteString(scene) b.WriteString("\n") } cues := analysis.AuthenticityCues if len(cues) == 0 { cues = analysis.KeyTakeaways } if len(cues) > 0 { b.WriteString("真人質感線索:") b.WriteString(strings.Join(cues, ";")) b.WriteString("\n") } if avoid := firstNonEmpty(analysis.WhatToAvoid, analysis.ReplicationStrategy); avoid != "" { b.WriteString("避免變成複製文:") b.WriteString(avoid) } return strings.TrimSpace(b.String()) } func firstNonEmpty(values ...string) string { for _, value := range values { if trimmed := strings.TrimSpace(value); trimmed != "" { return trimmed } } return "" }