153 lines
4.6 KiB
Go
153 lines
4.6 KiB
Go
package aiprompts
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"encoding/json"
|
||
|
|
"regexp"
|
||
|
|
"strings"
|
||
|
|
)
|
||
|
|
|
||
|
|
var (
|
||
|
|
reLegacyHTMLBlock = regexp.MustCompile(`(?is)<\s*(h[1-4]|p|ul|ol|li|b|strong)\s*>\s*\{?\s*(.*?)\s*\}?\s*<\s*/\s*(?:h[1-4]|p|ul|ol|li|b|strong)\s*>`)
|
||
|
|
reLegacyBoldUL = regexp.MustCompile(`(?is)<\s*b\s*>\s*\{?\s*(.*?)\s*\}?\s*<\s*/\s*b\s*>\s*<\s*ul\s*>\s*<\s*li\s*>\s*\{?\s*(.*?)\s*\}?\s*<\s*/\s*li\s*>\s*<\s*/\s*ul\s*>`)
|
||
|
|
)
|
||
|
|
|
||
|
|
// DescriptionFormulaSection is one ordered block in categories.description_template.
|
||
|
|
type DescriptionFormulaSection struct {
|
||
|
|
Type string `json:"type"`
|
||
|
|
Instructions string `json:"instructions"`
|
||
|
|
}
|
||
|
|
|
||
|
|
// DescriptionFormula is the JSON shape stored in categories.description_template
|
||
|
|
// (sections + optional SEO meta instruction strings).
|
||
|
|
type DescriptionFormula struct {
|
||
|
|
Sections []DescriptionFormulaSection `json:"sections,omitempty"`
|
||
|
|
MetaTitle string `json:"metaTitle,omitempty"`
|
||
|
|
MetaDescription string `json:"metaDescription,omitempty"`
|
||
|
|
}
|
||
|
|
|
||
|
|
// DescriptionTemplateNeedsRepair is true when template is missing or has no sections
|
||
|
|
// (and no usable meta instructions).
|
||
|
|
func DescriptionTemplateNeedsRepair(template any) bool {
|
||
|
|
f, ok := parseDescriptionFormula(template)
|
||
|
|
if !ok {
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
if len(f.Sections) > 0 {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
return strings.TrimSpace(f.MetaTitle) == "" && strings.TrimSpace(f.MetaDescription) == ""
|
||
|
|
}
|
||
|
|
|
||
|
|
// DeriveDescriptionFormulaFromLegacyParts builds description_template from split
|
||
|
|
// legacy DescriptionRules HTML + MetaRules.
|
||
|
|
func DeriveDescriptionFormulaFromLegacyParts(parts LegacyEnhanceParts) DescriptionFormula {
|
||
|
|
out := DescriptionFormula{
|
||
|
|
MetaDescription: strings.TrimSpace(parts.MetaRules),
|
||
|
|
}
|
||
|
|
body := strings.TrimSpace(parts.DescriptionRules)
|
||
|
|
if body == "" {
|
||
|
|
return out
|
||
|
|
}
|
||
|
|
|
||
|
|
// Walk left-to-right so section order matches the legacy HTML template.
|
||
|
|
remaining := body
|
||
|
|
for remaining != "" {
|
||
|
|
boldUL := reLegacyBoldUL.FindStringSubmatchIndex(remaining)
|
||
|
|
block := reLegacyHTMLBlock.FindStringSubmatchIndex(remaining)
|
||
|
|
switch {
|
||
|
|
case boldUL != nil && (block == nil || boldUL[0] <= block[0]):
|
||
|
|
m := reLegacyBoldUL.FindStringSubmatch(remaining[boldUL[0]:boldUL[1]])
|
||
|
|
heading, instr := "", ""
|
||
|
|
if len(m) >= 3 {
|
||
|
|
heading = cleanLegacyInstruction(m[1])
|
||
|
|
instr = cleanLegacyInstruction(m[2])
|
||
|
|
}
|
||
|
|
if heading != "" && instr != "" && !strings.Contains(strings.ToLower(instr), strings.ToLower(heading)) {
|
||
|
|
instr = heading + ": " + instr
|
||
|
|
} else if instr == "" {
|
||
|
|
instr = heading
|
||
|
|
}
|
||
|
|
if strings.TrimSpace(instr) != "" {
|
||
|
|
out.Sections = append(out.Sections, DescriptionFormulaSection{Type: "ul", Instructions: instr})
|
||
|
|
}
|
||
|
|
remaining = remaining[boldUL[1]:]
|
||
|
|
case block != nil:
|
||
|
|
m := reLegacyHTMLBlock.FindStringSubmatch(remaining[block[0]:block[1]])
|
||
|
|
if len(m) >= 3 {
|
||
|
|
typ := strings.ToLower(strings.TrimSpace(m[1]))
|
||
|
|
instr := cleanLegacyInstruction(m[2])
|
||
|
|
if instr != "" {
|
||
|
|
switch typ {
|
||
|
|
case "b", "strong":
|
||
|
|
typ = "h2"
|
||
|
|
case "li", "ol":
|
||
|
|
typ = "ul"
|
||
|
|
}
|
||
|
|
out.Sections = append(out.Sections, DescriptionFormulaSection{Type: typ, Instructions: instr})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
remaining = remaining[block[1]:]
|
||
|
|
default:
|
||
|
|
return out
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return out
|
||
|
|
}
|
||
|
|
|
||
|
|
// DeriveDescriptionTemplateJSON encodes DeriveDescriptionFormulaFromLegacyParts for DB write.
|
||
|
|
func DeriveDescriptionTemplateJSON(parts LegacyEnhanceParts) string {
|
||
|
|
f := DeriveDescriptionFormulaFromLegacyParts(parts)
|
||
|
|
if len(f.Sections) == 0 && strings.TrimSpace(f.MetaTitle) == "" && strings.TrimSpace(f.MetaDescription) == "" {
|
||
|
|
return ""
|
||
|
|
}
|
||
|
|
b, err := json.Marshal(f)
|
||
|
|
if err != nil {
|
||
|
|
return ""
|
||
|
|
}
|
||
|
|
return string(b)
|
||
|
|
}
|
||
|
|
|
||
|
|
func parseDescriptionFormula(template any) (DescriptionFormula, bool) {
|
||
|
|
if template == nil {
|
||
|
|
return DescriptionFormula{}, false
|
||
|
|
}
|
||
|
|
switch t := template.(type) {
|
||
|
|
case DescriptionFormula:
|
||
|
|
return t, true
|
||
|
|
case *DescriptionFormula:
|
||
|
|
if t == nil {
|
||
|
|
return DescriptionFormula{}, false
|
||
|
|
}
|
||
|
|
return *t, true
|
||
|
|
case []byte:
|
||
|
|
if len(t) == 0 {
|
||
|
|
return DescriptionFormula{}, false
|
||
|
|
}
|
||
|
|
var f DescriptionFormula
|
||
|
|
if err := json.Unmarshal(t, &f); err != nil {
|
||
|
|
return DescriptionFormula{}, false
|
||
|
|
}
|
||
|
|
return f, true
|
||
|
|
case string:
|
||
|
|
s := strings.TrimSpace(t)
|
||
|
|
if s == "" || s == "null" || s == "{}" {
|
||
|
|
return DescriptionFormula{}, false
|
||
|
|
}
|
||
|
|
var f DescriptionFormula
|
||
|
|
if err := json.Unmarshal([]byte(s), &f); err != nil {
|
||
|
|
return DescriptionFormula{}, false
|
||
|
|
}
|
||
|
|
return f, true
|
||
|
|
default:
|
||
|
|
b, err := json.Marshal(t)
|
||
|
|
if err != nil {
|
||
|
|
return DescriptionFormula{}, false
|
||
|
|
}
|
||
|
|
var f DescriptionFormula
|
||
|
|
if err := json.Unmarshal(b, &f); err != nil {
|
||
|
|
return DescriptionFormula{}, false
|
||
|
|
}
|
||
|
|
return f, true
|
||
|
|
}
|
||
|
|
}
|