This commit is contained in:
2026-08-23 20:49:40 +02:00
parent 3b678ca857
commit f3d4fb56ed
31 changed files with 3608 additions and 111 deletions
+102 -22
View File
@@ -14,7 +14,10 @@ import (
// <metaDescription> from the HTML reply. v2 must recreate that GPT-predloga
// dominance while keeping JSON {"name","description","meta_*","attrs"}.
const a1StyleEnhanceSystemTemplate = `Si tekstopisec, ki piše opise izdelkov v {{language}}.
// {{language}} renders an English label ("Slovenian"), so both templates name the
// language after a colon instead of inflecting it — legacy "v Slovenščini" read as
// "v Slovenian" and put a grammatical error in front of every A1 prompt.
const a1StyleEnhanceSystemTemplateSL = `Si tekstopisec, ki piše opise izdelkov v tem jeziku: {{language}}.
Rules:
- Reply with ONLY JSON (no markdown)
- Schema: {"name":"string","description":"string","meta_title":"string","meta_description":"string","attrs":{}}
@@ -25,6 +28,20 @@ Rules:
- attrs: only Allowed attribute keys when listed; fill from evidence only; omit unknowns
{{brand_voice}}`
// a1StyleEnhanceSystemTemplateEN is the same contract for the English
// platform-default formulas, matching the English scaffolding in a1StyleLabelsEN.
const a1StyleEnhanceSystemTemplateEN = `You are a copywriter who writes product descriptions in this language: {{language}}.
Rules:
- Reply with ONLY JSON (no markdown)
- Schema: {"name":"string","description":"string","meta_title":"string","meta_description":"string","attrs":{}}
- Obey the product template in the user message step by step
- "name" follows the <name> formula — never copy Old_product_name unchanged when the formula asks for a new name
- "description" is ONE HTML string covering each product template body section in order (tags matching type: h1/h2/h3/h4, p, ul) — do NOT wrap the reply in <name> or <metaDescription> tags
- "meta_title" and "meta_description" are plain SEO text from the meta / <metaDescription> formula (never HTML body)
- Use only the supplied Old_product_name, Old_product_description, Category and Attrs as facts; never invent specifications
- attrs: only Allowed attribute keys when listed; fill from evidence only; omit unknowns
{{brand_voice}}`
var (
reLegacyNameCapture = regexp.MustCompile(`(?is)<\s*name\s*>\s*(.*?)\s*<\s*/\s*name\s*>`)
reLegacyMetaCapture = regexp.MustCompile(`(?is)<\s*metaDescription\s*>\s*(.*?)\s*<\s*/\s*metaDescription\s*>`)
@@ -60,7 +77,7 @@ func categoryUsesA1StyleFormula(catPrompt string, titleTemplate, descriptionTemp
// buildA1StyleEnhanceUserTemplate rebuilds the legacy GPT-predloga user message
// (Title → <name>, Meta → <metaDescription>, Description HTML blocks) plus product
// evidence placeholders, asking for JSON field mapping.
func buildA1StyleEnhanceUserTemplate(catPrompt string, titleTemplate, descriptionTemplate any, omitSEOMeta bool) string {
func buildA1StyleEnhanceUserTemplate(catPrompt string, titleTemplate, descriptionTemplate any, omitSEOMeta bool) (string, bool) {
titleInstr := strings.TrimSpace(aiprompts.TitleSectionInstructions(catPrompt))
if titleInstr == "" || isBuiltInTitleRoleBoilerplate(titleInstr) {
titleInstr = ""
@@ -105,37 +122,100 @@ func buildA1StyleEnhanceUserTemplate(catPrompt string, titleTemplate, descriptio
predloga.WriteByte('\n')
}
var b strings.Builder
b.WriteString("Ustvari nov opis izdelka v {{language}} z naslednjimi spremenljivkami:\n\n")
b.WriteString("Staro_ime_izdelka: {{name}}\n")
b.WriteString("Star_opis_izdelka: {{description}}\n")
b.WriteString("Kategorija: {{category}}\n")
b.WriteString("Attrs: {{attrs}}\n\n")
b.WriteString("Uporabi spodnjo GPT predlogo.\n")
b.WriteString("Sledi tej GPT predlogi stavek po stavek in sestavi nov opis izdelka.\n\n")
b.WriteString("GPT predloga:\n\n")
b.WriteString(strings.TrimSpace(predloga.String()))
b.WriteString("\n\n")
b.WriteString("Odgovori SAMO z enim JSON objektom:\n")
b.WriteString(`- "name" = rezultat <name> formule (novo ime; ne kopiraj Staro_ime_izdelka, če formula zahteva novo ime)` + "\n")
b.WriteString(`- "description" = HTML telo po GPT predlogi (vsi razdelki po vrsti), BREZ <name>/<metaDescription> tagov` + "\n")
if !omitSEOMeta {
b.WriteString(`- "meta_title" / "meta_description" = plain SEO po meta / <metaDescription> formuli` + "\n")
body := strings.TrimSpace(predloga.String())
slovenian := reSlovenianFormulaCue.MatchString(body)
l := a1StyleLabelsEN
if slovenian {
l = a1StyleLabelsSL
}
b.WriteString(`- "attrs" = atributi (samo Allowed attribute keys, če so navedeni)`)
var b strings.Builder
fmt.Fprintf(&b, "%s\n\n", l.intro)
fmt.Fprintf(&b, "%s: {{name}}\n", l.oldName)
fmt.Fprintf(&b, "%s: {{description}}\n", l.oldDesc)
fmt.Fprintf(&b, "%s: {{category}}\n", l.category)
b.WriteString("Attrs: {{attrs}}\n\n")
fmt.Fprintf(&b, "%s\n%s\n\n", l.useTemplate, l.followTemplate)
fmt.Fprintf(&b, "%s\n\n", l.templateHeader)
b.WriteString(body)
b.WriteString("\n\n")
fmt.Fprintf(&b, "%s\n", l.replyHeader)
fmt.Fprintf(&b, "%s\n", l.nameRule)
fmt.Fprintf(&b, "%s\n", l.descRule)
if !omitSEOMeta {
fmt.Fprintf(&b, "%s\n", l.metaRule)
}
b.WriteString(l.attrsRule)
// Free-form category overlay (no role markers) — keep as extra guidance like
// soft "Category guidance" did, without replacing the GPT predloga.
// soft "Category guidance" did, without replacing the template.
extra := strings.TrimSpace(catPrompt)
if extra != "" &&
!aiprompts.CategoryEnhanceHasRoleSections(extra) &&
!aiprompts.IsLegacyCombinedEnhancePrompt(extra) {
b.WriteString("\n\nDodatna kategorijska navodila:\n")
fmt.Fprintf(&b, "\n\n%s\n", l.extraHeader)
b.WriteString(extra)
}
return strings.TrimSpace(b.String())
return strings.TrimSpace(b.String()), slovenian
}
// a1StyleLabels are the scaffolding strings around a category formula: how the
// supplied variables are labelled and how the reply is framed.
type a1StyleLabels struct {
intro string
oldName string
oldDesc string
category string
useTemplate string
followTemplate string
templateHeader string
replyHeader string
nameRule string
descRule string
metaRule string
attrsRule string
extraHeader string
}
var a1StyleLabelsSL = a1StyleLabels{
intro: "Ustvari nov opis izdelka v tem jeziku: {{language}}, z naslednjimi spremenljivkami:",
oldName: "Staro_ime_izdelka",
oldDesc: "Star_opis_izdelka",
category: "Kategorija",
useTemplate: "Uporabi spodnjo GPT predlogo.",
followTemplate: "Sledi tej GPT predlogi stavek po stavek in sestavi nov opis izdelka.",
templateHeader: "GPT predloga:",
replyHeader: "Odgovori SAMO z enim JSON objektom:",
nameRule: `- "name" = rezultat <name> formule (novo ime; ne kopiraj Staro_ime_izdelka, če formula zahteva novo ime)`,
descRule: `- "description" = HTML telo po GPT predlogi (vsi razdelki po vrsti), BREZ <name>/<metaDescription> tagov`,
metaRule: `- "meta_title" / "meta_description" = plain SEO po meta / <metaDescription> formuli`,
attrsRule: `- "attrs" = atributi (samo Allowed attribute keys, če so navedeni)`,
extraHeader: "Dodatna kategorijska navodila:",
}
var a1StyleLabelsEN = a1StyleLabels{
intro: "Create a new product description in {{language}} using the following variables:",
oldName: "Old_product_name",
oldDesc: "Old_product_description",
category: "Category",
useTemplate: "Use the product template below.",
followTemplate: "Follow this product template sentence by sentence and compose a new product description.",
templateHeader: "Product template:",
replyHeader: "Reply with ONE JSON object only:",
nameRule: `- "name" = the result of the <name> formula (a new name; do not copy Old_product_name when the formula asks for a new one)`,
descRule: `- "description" = the HTML body per the product template (every section in order), WITHOUT <name>/<metaDescription> tags`,
metaRule: `- "meta_title" / "meta_description" = plain SEO text per the meta / <metaDescription> formula`,
attrsRule: `- "attrs" = attributes (only Allowed attribute keys when listed)`,
extraHeader: "Additional category instructions:",
}
// reSlovenianFormulaCue keeps the scaffolding in the same language the formula
// speaks. A1's legacy formulas refer to "Novo ime izdelka" and were written against
// the Slovenian variable labels in generator.php, so those keep the Slovenian
// frame; the English platform defaults get the English one. Output language is set
// by {{language}} in the system prompt either way.
var reSlovenianFormulaCue = regexp.MustCompile(`(?i)novo[ _]ime[ _]izdelka|napiši|napisi|izpostavi|izdelka|znamka`)
func collapseLegacyInstr(s string) string {
s = strings.TrimSpace(s)
s = strings.ReplaceAll(s, "\r\n", "\n")