package aiprompts import ( "regexp" "strings" ) var ( reLegacyNameTag = regexp.MustCompile(`(?is)<\s*name\s*>\s*\{?\s*(.*?)\s*\}?\s*<\s*/\s*name\s*>`) reLegacyMetaTag = regexp.MustCompile(`(?is)<\s*metaDescription\s*>\s*\{?\s*(.*?)\s*\}?\s*<\s*/\s*metaDescription\s*>`) reLegacyDescPH = regexp.MustCompile(`(?i)\{\s*""?\s*OPIS\s+IZDELKA\s*""?\s*\}`) reLegacyNamePH = regexp.MustCompile(`(?i)\{\s*""?\s*STARO\s+IME\s+IZDELKA\s*""?\s*\}`) reDoubledQuotes = regexp.MustCompile(`"{2,}`) ) // LegacyEnhanceParts holds extracted role content from a combined PHP/A1 category Prompt. type LegacyEnhanceParts struct { TitleRules string DescriptionRules string MetaRules string WasLegacy bool } // IsLegacyCombinedEnhancePrompt reports whether prompt looks like the old A1/PHP // combined name+description(+meta) marketing blob (//GPT predloga). func IsLegacyCombinedEnhancePrompt(prompt string) bool { p := strings.TrimSpace(prompt) if p == "" { return false } // Require real tag wrappers — not instructional mentions like // `do NOT … / tags` in CategoryEnhanceUserTemplate. hasNameTag := reLegacyNameTag.MatchString(p) hasMetaTag := reLegacyMetaTag.MatchString(p) if hasNameTag && (hasMetaTag || strings.Contains(strings.ToLower(p), "gpt predloga")) { return true } if hasNameTag && (strings.Contains(p, "STARO IME IZDELKA") || strings.Contains(p, "OPIS IZDELKA")) { return true } if strings.Contains(p, "STARO IME IZDELKA") && strings.Contains(p, "OPIS IZDELKA") && strings.Contains(strings.ToLower(p), "gpt predloga") { return true } return false } // ParseLegacyCombinedEnhancePrompt extracts title / description / meta instruction // blobs from a legacy combined prompt. Placeholders are modernized to {{name}} / // {{description}}. WasLegacy is false when no combined markers are found. func ParseLegacyCombinedEnhancePrompt(prompt string) LegacyEnhanceParts { raw := strings.TrimSpace(prompt) out := LegacyEnhanceParts{} if raw == "" || !IsLegacyCombinedEnhancePrompt(raw) { return out } out.WasLegacy = true if m := reLegacyNameTag.FindStringSubmatch(raw); len(m) == 2 { out.TitleRules = cleanLegacyInstruction(m[1]) } if m := reLegacyMetaTag.FindStringSubmatch(raw); len(m) == 2 { out.MetaRules = cleanLegacyInstruction(m[1]) } rest := reLegacyNameTag.ReplaceAllString(raw, "") rest = reLegacyMetaTag.ReplaceAllString(rest, "") if idx := strings.Index(strings.ToLower(rest), "gpt predloga:"); idx >= 0 { rest = rest[idx+len("gpt predloga:"):] } // Drop Slovenian boilerplate intro lines that are not body structure. rest = stripLegacyIntroNoise(rest) out.DescriptionRules = cleanLegacyInstruction(rest) return out } // SplitLegacyCombinedEnhancePrompt rewrites a legacy combined name+description(+meta) // prompt into role sections carrying ONLY the original per-category text — the // Slovenian naming formula under Title, the HTML body structure under Description, // the SEO instruction under Meta. No schema/role boilerplate is stored: JSON // schema, role framing, and {{name}}/{{description}}/{{category}}/{{attrs}} // product context are all supplied at render time (system template + // processing.ensureCategoryEnhanceUserContext). Non-legacy input returns "" — // callers leave the category without an override (company default applies). func SplitLegacyCombinedEnhancePrompt(prompt string) string { parts := ParseLegacyCombinedEnhancePrompt(prompt) if !parts.WasLegacy { return "" } return BuildCategoryEnhanceOverlay(parts) } // BuildCategoryEnhanceOverlay composes a role-sectioned enhance USER overlay from // extracted legacy parts — the stored text is exactly the split legacy content, // matching what a user would type into the Title/Description/Meta prompt areas. func BuildCategoryEnhanceOverlay(parts LegacyEnhanceParts) string { var b strings.Builder section := func(start, end, body string) { b.WriteString(start) b.WriteByte('\n') if body = strings.TrimSpace(body); body != "" { b.WriteString(body) b.WriteByte('\n') } b.WriteString(end) b.WriteString("\n\n") } section(SectionTitleStart, SectionTitleEnd, parts.TitleRules) section(SectionDescriptionStart, SectionDescriptionEnd, parts.DescriptionRules) section(SectionMetaStart, SectionMetaEnd, parts.MetaRules) return strings.TrimSpace(b.String()) } func cleanLegacyInstruction(s string) string { s = modernizeLegacyPlaceholders(s) s = reDoubledQuotes.ReplaceAllString(s, `"`) s = strings.ReplaceAll(s, "\r\n", "\n") s = strings.TrimSpace(s) // Collapse excessive blank lines. for strings.Contains(s, "\n\n\n") { s = strings.ReplaceAll(s, "\n\n\n", "\n\n") } return s } func modernizeLegacyPlaceholders(prompt string) string { prompt = reLegacyDescPH.ReplaceAllString(prompt, "{{description}}") prompt = reLegacyNamePH.ReplaceAllString(prompt, "{{name}}") return prompt } func stripLegacyIntroNoise(s string) string { lines := strings.Split(s, "\n") out := make([]string, 0, len(lines)) for _, line := range lines { trim := strings.TrimSpace(line) lower := strings.ToLower(trim) switch { case trim == "": if len(out) > 0 { out = append(out, "") } case strings.HasPrefix(lower, "ustvari nov opis"), strings.HasPrefix(lower, "star_opis_izdelka"), strings.HasPrefix(lower, "staro_ime_izdelka"), strings.HasPrefix(lower, "uporabi spodnjo gpt"), strings.HasPrefix(lower, "sledi tej gpt"): continue default: out = append(out, trim) } } return strings.TrimSpace(strings.Join(out, "\n")) }