package app import ( "encoding/json" "fmt" "regexp" "strings" "unicode/utf8" ) var promptVariablePattern = regexp.MustCompile(`\{\{\s*([a-zA-Z0-9_]+)\s*\}\}`) func buildGenerationPrompt(templateName string, promptTemplate *string, params map[string]interface{}) string { basePrompt := strings.TrimSpace(renderPromptTemplate(promptTemplate, params)) if basePrompt == "" { basePrompt = fmt.Sprintf("Write a complete article in markdown for the template %q.", templateName) } var sections []string sections = append(sections, basePrompt) contextBlock := buildPromptContext(params) if contextBlock != "" { sections = append(sections, "Request context:\n"+contextBlock) } sections = append(sections, strings.Join([]string{ "Output requirements:", "- Return only article markdown, no surrounding explanation.", "- Use the provided title when available.", "- Follow the provided outline sections and key points when available.", "- Keep structure clear with headings, short paragraphs, and concrete details.", }, "\n")) return strings.Join(sections, "\n\n") } func renderPromptTemplate(promptTemplate *string, params map[string]interface{}) string { if promptTemplate == nil { return "" } return promptVariablePattern.ReplaceAllStringFunc(*promptTemplate, func(match string) string { submatches := promptVariablePattern.FindStringSubmatch(match) if len(submatches) != 2 { return match } value, ok := params[submatches[1]] if !ok { return "" } return formatPromptValue(value) }) } func buildPromptContext(params map[string]interface{}) string { if len(params) == 0 { return "" } orderedKeys := []string{ "locale", "title", "topic", "product_name", "subject", "brand_name", "brand", "category", "count", "depth", "outline_sections", "key_points", "keywords", "competitors", } used := make(map[string]struct{}, len(params)) lines := make([]string, 0, len(params)) appendLine := func(key string, value interface{}) { if value == nil { return } formatted := strings.TrimSpace(formatPromptValue(value)) if formatted == "" { return } lines = append(lines, fmt.Sprintf("- %s: %s", key, formatted)) used[key] = struct{}{} } for _, key := range orderedKeys { appendLine(key, params[key]) } for key, value := range params { if _, ok := used[key]; ok { continue } appendLine(key, value) } return strings.Join(lines, "\n") } func formatPromptValue(value interface{}) string { switch v := value.(type) { case nil: return "" case string: return strings.TrimSpace(v) case []string: return strings.Join(v, ", ") case []interface{}: parts := make([]string, 0, len(v)) for _, item := range v { formatted := strings.TrimSpace(formatPromptValue(item)) if formatted != "" { parts = append(parts, formatted) } } return strings.Join(parts, ", ") default: bytes, err := json.Marshal(v) if err != nil { return fmt.Sprint(v) } return string(bytes) } } func resolveArticleTitle(params map[string]interface{}, markdown string) string { if title := strings.TrimSpace(stringValue(params["title"])); title != "" { return title } if topic := strings.TrimSpace(stringValue(params["topic"])); topic != "" { return topic } if product := strings.TrimSpace(stringValue(params["product_name"])); product != "" { return product + " Review" } if subject := strings.TrimSpace(stringValue(params["subject"])); subject != "" { return subject + " Report" } for _, line := range strings.Split(markdown, "\n") { line = strings.TrimSpace(line) if strings.HasPrefix(line, "#") { return strings.TrimSpace(strings.TrimLeft(line, "#")) } } return "Generated Article" } func estimateWordCount(markdown string) int { trimmed := strings.TrimSpace(markdown) if trimmed == "" { return 0 } words := len(strings.Fields(trimmed)) if words > 0 { return words } return utf8.RuneCountInString(trimmed) } func stringValue(value interface{}) string { if value == nil { return "" } text, ok := value.(string) if !ok { return "" } return text }