haixunMaster/haixun-backend/internal/library/viral/mission_inspire.go

179 lines
6.0 KiB
Go
Raw Permalink Normal View History

2026-06-25 08:20:03 +00:00
package viral
import (
"encoding/json"
"fmt"
"strings"
)
type MissionInspireInput struct {
2026-06-25 09:34:28 +00:00
PersonaDisplayName string
PersonaBrief string
PersonaBlock string
StyleBenchmark string
PersonaAudience string
PersonaContentGoal string
PersonaQuestions []string
PersonaPillars []string
RecentMissionLabels []string
RecentSeedQueries []string
TrendSnippets []MissionInspireTrendSnippet
WebSearchProvider string
LLMOnly bool
2026-06-25 08:20:03 +00:00
}
type MissionInspireTrendSnippet struct {
Query string
Title string
Snippet string
URL string
}
type MissionInspireOutput struct {
2026-06-25 09:34:28 +00:00
Label string
SeedQuery string
Brief string
TrendReason string
TrendKeywords []string
2026-06-25 08:20:03 +00:00
}
func BuildMissionInspireSystemPrompt() string {
return strings.TrimSpace(`你是 Threads 拷貝忍者的靈感骰子顧問根據近期網路熱搜Google Trends 類訊號與創作者人設產出一組**全新**拷貝任務草稿
規則
1. 近期趨勢訊號有內容從中挑一個適合在 Threads 海巡的方向不要編造不存在的時事
2. 若趨勢訊號為空未連線網路搜尋**必須**改依人設受眾痛點與常見 Threads 討論型態推測近期可能被搜尋的話題並在 trendReason 說明推測理由不要假裝有外部熱搜來源
3. label任務名稱618 像企劃案標題不要標點堆疊
4. seedQuery種子關鍵字近期熱詞26 個詞用頓號或空格分隔適合當 Threads 搜尋起點
5. brief這次想找什麼24 說明要海巡哪類爆款語氣或格式偏好
6. trendReason12 為何選這個趨勢可引用訊號來源大意不要貼 URL
7. trendKeywords36 個相關熱詞字串陣列
8. 避開近期已做過的任務相同題材
9. 繁體中文只回傳 JSON
{"label":"","seedQuery":"","brief":"","trendReason":"","trendKeywords":[]}`)
}
func BuildMissionInspireUserPrompt(in MissionInspireInput) string {
var b strings.Builder
if name := strings.TrimSpace(in.PersonaDisplayName); name != "" {
b.WriteString("【人設名稱】")
b.WriteString(name)
b.WriteString("\n")
}
if p := strings.TrimSpace(in.PersonaBlock); p != "" {
b.WriteString("【人設摘要】\n")
b.WriteString(p)
b.WriteString("\n")
}
if bench := strings.TrimSpace(in.StyleBenchmark); bench != "" {
b.WriteString("【對標帳號】@")
b.WriteString(strings.TrimPrefix(bench, "@"))
b.WriteString("\n")
}
if aud := strings.TrimSpace(in.PersonaAudience); aud != "" {
b.WriteString("【受眾方向】")
b.WriteString(aud)
b.WriteString("\n")
}
if goal := strings.TrimSpace(in.PersonaContentGoal); goal != "" {
b.WriteString("【內容目標】")
b.WriteString(goal)
b.WriteString("\n")
}
if len(in.PersonaPillars) > 0 {
b.WriteString("【內容支柱】")
b.WriteString(strings.Join(in.PersonaPillars, "、"))
b.WriteString("\n")
}
if len(in.RecentMissionLabels) > 0 || len(in.RecentSeedQueries) > 0 {
b.WriteString("【近期已做過的任務(請避開)】\n")
for _, label := range in.RecentMissionLabels {
if label = strings.TrimSpace(label); label != "" {
b.WriteString("- ")
b.WriteString(label)
b.WriteString("\n")
}
}
for _, seed := range in.RecentSeedQueries {
if seed = strings.TrimSpace(seed); seed != "" {
b.WriteString("- 種子:")
b.WriteString(seed)
b.WriteString("\n")
}
}
}
if in.LLMOnly {
b.WriteString("【模式】未設定 Web Search API key請純依人設與受眾推測靈感勿假裝有 Google 熱搜)\n")
} else if provider := strings.TrimSpace(in.WebSearchProvider); provider != "" {
b.WriteString("【趨勢來源】")
b.WriteString(provider)
b.WriteString(" 網路搜尋\n")
}
b.WriteString("【近期趨勢訊號】\n")
if len(in.TrendSnippets) == 0 {
b.WriteString("(無外部趨勢結果,請改用人設推測近期 Threads 可能熱議方向)\n")
} else {
for i, item := range in.TrendSnippets {
b.WriteString(fmt.Sprintf("[%d] 查詢:%s\n", i+1, strings.TrimSpace(item.Query)))
if title := strings.TrimSpace(item.Title); title != "" {
b.WriteString("標題:")
b.WriteString(title)
b.WriteString("\n")
}
if snippet := strings.TrimSpace(item.Snippet); snippet != "" {
b.WriteString(snippet)
b.WriteString("\n")
}
b.WriteString("\n")
}
}
b.WriteString("請產出拷貝任務靈感 JSON。")
return b.String()
}
func InspireTrendSearchQueries(personaBrief, styleBenchmark string) []string {
queries := []string{
"Google Trends 台灣 今日 熱門搜尋 關鍵字",
"Threads 台灣 熱門 話題 最近 一週",
"近期 網路 熱搜 關鍵字 台灣",
}
context := strings.TrimSpace(personaBrief + " " + styleBenchmark)
if context != "" {
trimmed := context
if len([]rune(trimmed)) > 24 {
trimmed = string([]rune(trimmed)[:24])
}
queries = append(queries, trimmed+" 熱門 話題 最近")
}
return queries
}
func ParseMissionInspireOutput(raw string) (MissionInspireOutput, error) {
payload, err := extractCopyMapJSON(raw)
if err != nil {
return MissionInspireOutput{}, err
}
var root map[string]json.RawMessage
if err := json.Unmarshal(payload, &root); err != nil {
return MissionInspireOutput{}, err
}
out := MissionInspireOutput{
Label: firstJSONString(root, "label", "title", "mission_label"),
SeedQuery: firstJSONString(root, "seedQuery", "seed_query", "seed", "keywords"),
Brief: firstJSONString(root, "brief", "description", "goal"),
TrendReason: firstJSONString(root, "trendReason", "trend_reason", "reason"),
}
if out.Label == "" || out.SeedQuery == "" || out.Brief == "" {
return MissionInspireOutput{}, fmt.Errorf("missing label, seedQuery, or brief")
}
if rawKW, ok := root["trendKeywords"]; ok {
out.TrendKeywords = parseFlexibleStringList(rawKW)
}
if len(out.TrendKeywords) == 0 {
if rawKW, ok := root["trend_keywords"]; ok {
out.TrendKeywords = parseFlexibleStringList(rawKW)
}
}
return out, nil
2026-06-25 09:34:28 +00:00
}