thread-master/backend/internal/library/viral/analyze_viral.go

137 lines
4.3 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 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一個值得借鑑的「具體場景種子」時間/地點/動作,供啟發勿照搬)
- authenticityCues35 個讓它像真人的小細節(口語、坦白、不完美、具體細節等)
- 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 ""
}