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 CategoryEnhanceUserTemplate role sections, preserving Slovenian // naming / HTML / meta intent as category-specific rules under Title / Description / Meta. // Non-legacy input returns CategoryEnhanceUserTemplate (canonical shared overlay). func SplitLegacyCombinedEnhancePrompt(prompt string) string { parts := ParseLegacyCombinedEnhancePrompt(prompt) if !parts.WasLegacy { return strings.TrimSpace(CategoryEnhanceUserTemplate) } return BuildCategoryEnhanceOverlay(parts) } // BuildCategoryEnhanceOverlay composes a role-sectioned enhance USER overlay from // extracted legacy parts (or empty parts → canonical template text with no extra rules). func BuildCategoryEnhanceOverlay(parts LegacyEnhanceParts) string { var b strings.Builder b.WriteString(`Your reply is parsed as JSON {"name":"string","description":"string","meta_title":"string","meta_description":"string","attrs":{}} only (system schema). Write all string fields in {{language}} (do not hardcode a language).`) b.WriteString("\n\n") b.WriteString(SectionTitleStart) b.WriteByte('\n') b.WriteString(TitleRoleInstruction) b.WriteByte('\n') if rules := strings.TrimSpace(parts.TitleRules); rules != "" { b.WriteString("Category naming rules (preserve intent): ") b.WriteString(rules) b.WriteByte('\n') } b.WriteString("Name: {{name}}\n") b.WriteString(SectionTitleEnd) b.WriteString("\n\n") b.WriteString(SectionDescriptionStart) b.WriteString("\nRole: description. Build JSON \"description\": product body HTML only (not SEO meta). When a Description formula follows, emit ONE HTML string covering each section in order (tags matching type: h1/h2/h3/h4, p, ul); otherwise prefer 1-3 factual paragraphs as ONE string with limited HTML (

  • ) — do NOT emit a competing full HTML document or wrap the whole reply in / tags.\n") if rules := strings.TrimSpace(parts.DescriptionRules); rules != "" { b.WriteString("Category HTML structure (preserve intent; emit as ONE description HTML string, not tagged name/meta blocks):\n") b.WriteString(rules) b.WriteByte('\n') } b.WriteString("Description: {{description}}\n") b.WriteString(SectionDescriptionEnd) b.WriteString("\n\n") b.WriteString(SectionMetaStart) b.WriteString("\nRole: meta. Build JSON \"meta_title\" and \"meta_description\" as plain SEO text (never HTML). meta_title: 50-60 chars; meta_description: 120-155 chars; follow any SEO meta formula that follows; never copy the full description HTML into meta_description.\n") if rules := strings.TrimSpace(parts.MetaRules); rules != "" { b.WriteString("Category SEO rules (preserve intent; plain text only): ") b.WriteString(rules) b.WriteByte('\n') } b.WriteString(SectionMetaEnd) b.WriteString("\n\n") b.WriteString(SectionAttributesStart) b.WriteString("\nRole: attributes. Build JSON \"attrs\" as an object of attribute_key → value strings. Prefer Allowed attribute keys / Title formula attr slots that follow; remap near-miss labels onto those keys; fill missing keys only from Name/Description/Category/Attrs evidence; never invent specs; omit unknown keys; never invent dimensions.\n") b.WriteString("Category: {{category}}\n") b.WriteString("Attrs: {{attrs}}\n") b.WriteString(SectionAttributesEnd) 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")) }