package ownpost import ( "context" "encoding/json" "fmt" "regexp" "strings" domai "haixun-backend/internal/model/ai/domain/usecase" ) var formulaFenceRE = regexp.MustCompile("(?s)```(?:json)?\\s*([\\s\\S]*?)```") type PostFormulaInput struct { AccountName string PersonaBlock string PostText string TopicTag string MediaType string LikeCount int ReplyCount int Views int RepostCount int QuoteCount int Shares int Timestamp string } type PostFormulaReview 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 BuildPostFormulaPrompt(in PostFormulaInput) (system string, user string) { personaBlock := strings.TrimSpace(in.PersonaBlock) if personaBlock == "" { personaBlock = "(未指定人設,請依貼文本身語氣分析)" } postText := strings.TrimSpace(in.PostText) if postText == "" { postText = "(貼文內容未提供)" } system = strings.TrimSpace(` 你是 Threads 內容策略教練。任務是針對「已發布的單篇貼文」做誠實覆盤:好的要提炼成可複製公式,壞的要指出問題並給具體改善做法。不要重寫整篇貼文。 規則: - 用台灣繁體中文。 - 必須同時寫優點與缺點;依成效數據判斷,成效普通或偏差時更要直言問題。 - 分析要具體、可執行,禁止空泛稱讚或空泛批評。 - formula:把「做對的部分」整理成步驟化公式(例:Hook→情境→轉折→觀點→收尾/互動)。 - post_template:依 formula 產出可複製的發文骨架,用 [括號] 標示可替換欄位,讓使用者下次填空就能產文。 - improvements:每點格式固定為「問題:…|改善:…」,改善必須是可立刻執行的動作(改哪一句、加什麼、刪什麼、換什麼角度)。 - 只輸出一個 JSON 物件,不要 markdown,欄位: summary, wins, improvements, formula, post_template, hook_pattern, structure, replication_tips, avoid - wins / improvements / replication_tips / avoid 各 2~5 點字串陣列。`) var b strings.Builder b.WriteString("【1. 人設】\n") b.WriteString(personaBlock) b.WriteString("\n\n帳號:") b.WriteString(strings.TrimSpace(in.AccountName)) b.WriteString("\n\n【2. 貼文內容】\n") b.WriteString(postText) if tag := strings.TrimSpace(in.TopicTag); tag != "" { b.WriteString("\n話題標籤:#") b.WriteString(tag) } if mt := strings.TrimSpace(in.MediaType); mt != "" { b.WriteString("\n媒體類型:") b.WriteString(mt) } if ts := strings.TrimSpace(in.Timestamp); ts != "" { b.WriteString("\n發布時間:") b.WriteString(ts) } b.WriteString("\n\n【3. 成效數據】\n") b.WriteString(fmt.Sprintf("讚 %d|回覆 %d|瀏覽 %d|轉發 %d|引用 %d|分享 %d", in.LikeCount, in.ReplyCount, in.Views, in.RepostCount, in.QuoteCount, in.Shares)) b.WriteString("\n\n請誠實覆盤:做對了什麼、哪裡不好、怎麼改,並把做對的部分整理成下次可複製的公式與發文模板 JSON。") user = b.String() return system, user } func GeneratePostFormula(ctx context.Context, ai domai.UseCase, req domai.GenerateRequest, in PostFormulaInput) (*PostFormulaReview, error) { system, user := BuildPostFormulaPrompt(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 } review, err := ParsePostFormulaOutput(out.Text) if err != nil { return nil, err } return review, nil } func ParsePostFormulaOutput(raw string) (*PostFormulaReview, error) { payload, err := extractFormulaJSONObject(raw) if err != nil { return nil, err } var review PostFormulaReview if err := json.Unmarshal(payload, &review); err != nil { return nil, fmt.Errorf("parse post 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) review.Wins = trimStringList(review.Wins) review.Improvements = trimStringList(review.Improvements) review.ReplicationTips = trimStringList(review.ReplicationTips) review.Avoid = trimStringList(review.Avoid) if review.Summary == "" && review.Formula == "" && review.PostTemplate == "" { return nil, fmt.Errorf("AI 未回傳有效覆盤內容") } return &review, nil } func trimStringList(items []string) []string { if len(items) == 0 { return nil } out := make([]string, 0, len(items)) for _, item := range items { item = strings.TrimSpace(item) if item != "" { out = append(out, item) } } if len(out) == 0 { return nil } return out } func extractFormulaJSONObject(raw string) ([]byte, error) { raw = strings.TrimSpace(raw) if m := formulaFenceRE.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 }