package style8d import ( "encoding/json" "strings" ) var dimensionLabels = map[string]string{ "d1Tone": "D1 語氣人格", "d2Structure": "D2 結構模板", "d3Interaction": "D3 互動方式", "d4Topics": "D4 主題分布", "d5Rhythm": "D5 發文節奏", "d6Visual": "D6 視覺語法", "d7Conversion": "D7 轉換方式", "d8Risk": "D8 風險紅線", } var dimensionOrder = []string{ "d1Tone", "d2Structure", "d3Interaction", "d4Topics", "d5Rhythm", "d6Visual", "d7Conversion", "d8Risk", } // ParseStoredProfile decodes the persona.style_profile JSON blob. func ParseStoredProfile(raw string) (*StoredProfile, bool) { raw = strings.TrimSpace(raw) if raw == "" { return nil, false } var profile StoredProfile if err := json.Unmarshal([]byte(raw), &profile); err != nil { return nil, false } if len(profile.Analysis) == 0 && strings.TrimSpace(profile.PersonaDraft) == "" { return nil, false } return &profile, true } // HasReady8D returns true when 8D analysis exists and can drive copy generation. func HasReady8D(personaText, styleProfileJSON string) bool { if profile, ok := ParseStoredProfile(styleProfileJSON); ok { if strings.TrimSpace(profile.PersonaDraft) != "" { return true } for _, key := range dimensionOrder { if summary := strings.TrimSpace(profile.Analysis[key].Summary); summary != "" { return true } } } return strings.TrimSpace(personaText) != "" && strings.TrimSpace(styleProfileJSON) != "" } // BuildStyle8DPromptBlock formats D1–D8 summaries for LLM prompts. func BuildStyle8DPromptBlock(styleProfileJSON string) string { profile, ok := ParseStoredProfile(styleProfileJSON) if !ok { return "" } lines := make([]string, 0, len(dimensionOrder)) for _, key := range dimensionOrder { summary := strings.TrimSpace(profile.Analysis[key].Summary) if summary == "" { continue } label := dimensionLabels[key] if label == "" { label = key } lines = append(lines, label+":"+summary) } if len(lines) == 0 { return "" } var b strings.Builder b.WriteString("【8D 風格策略】\n產文必須遵守:\n") b.WriteString(strings.Join(lines, "\n")) return b.String() } // ResolvePersonaBlock picks the best persona voice block for copy generation. // Priority: explicit persona field → 8D personaDraft → brief fallback; always append 8D strategy when present. func ResolvePersonaBlock(personaText, styleProfileJSON, brief string) string { var parts []string voice := strings.TrimSpace(personaText) if voice == "" { if profile, ok := ParseStoredProfile(styleProfileJSON); ok { voice = strings.TrimSpace(profile.PersonaDraft) } } if voice == "" { voice = strings.TrimSpace(brief) } if voice != "" { parts = append(parts, "【人設語氣】\n"+voice) } if block := BuildStyle8DPromptBlock(styleProfileJSON); block != "" { parts = append(parts, block) } return strings.TrimSpace(strings.Join(parts, "\n\n")) }