thread-master/backend/internal/library/style8d/prompt.go

113 lines
3.4 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 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 D1D8 summaries exist and can drive copy generation.
// personaDraft or legacy persona text alone is not sufficient for matrix/copy gates.
func HasReady8D(personaText, styleProfileJSON string) bool {
_ = personaText
return BuildStyle8DPromptBlock(styleProfileJSON) != ""
}
var copyVoiceDimensionOrder = []string{"d1Tone", "d8Risk"}
// BuildStyle8DPromptBlock formats D1D8 summaries for LLM prompts.
func BuildStyle8DPromptBlock(styleProfileJSON string) string {
return buildStylePromptBlock(styleProfileJSON, dimensionOrder, "【8D 風格策略】\n產文必須遵守\n")
}
// BuildLightPersonaVoiceBlock keeps only tone + risk guardrails for copy generation.
func BuildLightPersonaVoiceBlock(styleProfileJSON string) string {
return buildStylePromptBlock(
styleProfileJSON,
copyVoiceDimensionOrder,
"【語氣與禁忌參考】\n以下僅供口語風格與紅線參考勿照搬句型、段落模板或固定 hook\n",
)
}
func buildStylePromptBlock(styleProfileJSON string, keys []string, header string) string {
profile, ok := ParseStoredProfile(styleProfileJSON)
if !ok {
return ""
}
lines := make([]string, 0, len(keys))
for _, key := range keys {
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(header)
b.WriteString(strings.Join(lines, "\n"))
return b.String()
}
// ResolvePersonaBlock combines explicit persona positioning with analyzed writing fingerprints.
// The persona field answers "who this is"; personaDraft/style profile answers "how this person says things".
func ResolvePersonaBlock(personaText, styleProfileJSON, brief string) string {
var parts []string
profile, hasProfile := ParseStoredProfile(styleProfileJSON)
persona := strings.TrimSpace(personaText)
if persona != "" {
parts = append(parts, "【人設定位】\n"+persona)
} else if fallback := strings.TrimSpace(brief); fallback != "" {
parts = append(parts, "【人設定位】\n"+fallback)
}
if hasProfile {
if draft := strings.TrimSpace(profile.PersonaDraft); draft != "" {
parts = append(parts, "【語言指紋與寫作規則】\n"+draft)
}
}
if block := BuildLightPersonaVoiceBlock(styleProfileJSON); block != "" {
parts = append(parts, block)
}
return strings.TrimSpace(strings.Join(parts, "\n\n"))
}