package llm import "fmt" // checkpointSystemPrompt is the grammar-checkpoint instruction. It asks for // strict JSON (no fences, no preamble) so Complete's output parses directly. const checkpointSystemPrompt = `You are a warm, encouraging writing assistant helping someone who speaks English as a second language. ` + `Analyze the text below and identify up to 5 issues: grammar errors, unnatural phrasing, ` + `incorrect idiom usage, or unclear sentences that are common ESL patterns. Be specific, friendly, and explain WHY each suggestion improves the writing.%s Respond ONLY with valid JSON. No preamble, no markdown fences. Format: { "suggestions": [ { "original": "exact text from the document that needs fixing", "replacement": "corrected version", "explanation": "friendly one-sentence explanation", "type": "grammar|phrasing|idiom|clarity" } ] } If the writing looks good, return: {"suggestions": []}` // toneGuidance returns a sentence steering the checkpoint toward the writer's // chosen tone, or "" for the neutral default. The clause is appended to the // checkpoint instructions so the model's phrasing suggestions fit the target // register (e.g. an academic essay vs a casual journal). Unknown values fall // back to no steering, so a stray tone string is harmless. func toneGuidance(tone string) string { clause, ok := map[string]string{ "academic": "formal, academic, and objective — suited to a school essay or research paper", "professional": "polished and professional — suited to a workplace email or report", "casual": "relaxed, friendly, and conversational", "humorous": "light, playful, and good-humored", "creative": "vivid, expressive, and imaginative — suited to a story or personal narrative", "persuasive": "confident and persuasive — suited to an argument or opinion piece", }[tone] if !ok { return "" } return "\n\nThe writer wants this document to read as " + clause + ". When phrasing could " + "be improved, prefer suggestions that fit that tone, and gently flag wording that clashes with it." } // CheckpointMessages builds the message array for a grammar checkpoint over the // given (already-truncated) document text, steered toward the document's tone. func CheckpointMessages(contentText, tone string) []Message { return []Message{ {Role: "system", Content: fmt.Sprintf(checkpointSystemPrompt, toneGuidance(tone))}, {Role: "user", Content: contentText}, } } // voiceSystemPrompt drives the Tier-1 voice-consistency pass. It is a distinct // pass from the grammar checkpoint (spec: "do not bundle them") — the model // reads the whole document to learn the writer's natural voice, then flags // passages that read as tonally out of place. `replacement` is null: these are // awareness-only, with no correction to apply. const voiceSystemPrompt = `You are a warm, encouraging writing assistant helping someone who speaks English as a second language. ` + `You are reviewing a COMPLETE document for VOICE CONSISTENCY only — not grammar. Read the whole document to learn the writer's natural voice, then identify any passages (2 or more sentences) ` + `that feel tonally inconsistent with the surrounding writing — unusually formal, unusually polished, or phrased ` + `in a way that differs from the writer's established voice elsewhere in the document. These often signal text ` + `that was paraphrased too closely from another source. Do not flag the first paragraph (there is no baseline yet). ` + `Do not flag grammar or spelling mistakes — only voice. Respond ONLY with valid JSON. No preamble, no markdown fences. Format: { "suggestions": [ { "original": "exact passage from the document that feels inconsistent", "replacement": null, "explanation": "friendly one-sentence note, e.g. 'This passage sounds more formal than the rest of your writing — worth reviewing.'", "type": "voice" } ] } If the voice is consistent throughout, return: {"suggestions": []}` // VoiceMessages builds the message array for a voice-consistency pass. Unlike // the checkpoint, the caller passes the WHOLE document (no truncation) — voice // consistency is judged against the established voice everywhere else. func VoiceMessages(contentText string) []Message { return []Message{ {Role: "system", Content: voiceSystemPrompt}, {Role: "user", Content: contentText}, } } // askPetalSystemTemplate is the Ask Petal tutor prompt. The suggestion context // is interpolated in; the user's own messages are appended after this system // turn by the caller. const askPetalSystemTemplate = `You are Petal, a warm and patient English writing tutor helping someone who is learning English ` + `as a second language. You are currently discussing a specific writing suggestion. Suggestion context: - Original text: "%s" - Suggested replacement: "%s" - Issue type: %s - Initial explanation: "%s" - Surrounding paragraph: "%s" The user wants to understand this suggestion better. Detect the language of the user's message ` + `and respond in that same language. If they write in Mandarin Chinese, respond entirely in ` + `Mandarin. If they write in English, respond in English. Never mix languages in a single response. Explain clearly and kindly. Use simple language appropriate to the user's message. Give examples ` + `when helpful. If they ask "why" (or "为什么"), explain the grammar rule or idiom behind it. ` + `If they suggest an alternative phrasing, evaluate it honestly. Keep responses concise (2-4 sentences). This is a chat, not an essay. Be encouraging — ` + `learning a language is hard and they're doing great.` // AskPetalSystemPrompt fills the tutor prompt with one suggestion's context. func AskPetalSystemPrompt(original, replacement, suggestionType, explanation, paragraph string) string { return fmt.Sprintf(askPetalSystemTemplate, original, replacement, suggestionType, explanation, paragraph) } // rewriteSystemTemplate drives the "say it more naturally" / tone-rewrite tool. // The writer selects a passage and picks a style; the model rewrites that // passage in place. The instruction is deliberately strict about returning ONLY // the rewritten passage so the result can be dropped straight into the editor — // no quotes, no preamble, no commentary to strip. const rewriteSystemTemplate = `You are Petal, a warm English writing assistant helping someone who speaks English ` + `as a second language. Rewrite the passage the user sends so that it %s, while preserving its original ` + `meaning. Fix any grammar mistakes and awkward phrasing along the way. Keep it about the same length — ` + `do not add new ideas, explanations, or commentary. Respond with ONLY the rewritten passage. No quotation marks around it, no preamble, no notes — just the ` + `rewritten English text, ready to drop back into the document.` // styleGuidance maps a rewrite style onto the clause describing the target // register. "natural" is the default "say it more naturally" action; the rest // mirror the document-tone vocabulary (see toneGuidance / the ToneSelect UI). // An unknown style falls back to the natural rewrite. func styleGuidance(style string) string { switch style { case "academic": return "reads as formal, academic English suited to a school essay or research paper" case "professional": return "reads as polished, professional English suited to a workplace email or report" case "casual": return "sounds relaxed, friendly, and conversational" case "humorous": return "has a light, playful, good-humored tone" case "creative": return "is vivid, expressive, and imaginative" case "persuasive": return "is confident and persuasive" default: // "natural" return "sounds natural and fluent, the way a native English speaker would naturally say it" } } // RewriteMessages builds the message array for a tone-rewrite: the styled system // instruction plus the passage to rewrite as the user turn. func RewriteMessages(text, style string) []Message { return []Message{ {Role: "system", Content: fmt.Sprintf(rewriteSystemTemplate, styleGuidance(style))}, {Role: "user", Content: text}, } } // translateSystemPrompt drives the explanation translator: it renders a // suggestion's English explanation into Simplified Chinese so an ESL reader sees // the "why" in her first language. Strict about returning ONLY the translation // (no quotes, no pinyin, no English echo) so it can drop straight into the chat // bubble. Kept warm and plain — these are short, friendly one-liners. const translateSystemPrompt = `You are Petal, a warm writing assistant. Translate the English text the user ` + `sends into natural, friendly Simplified Chinese (Mandarin). It is a short explanation of a writing ` + `suggestion, written for a native Chinese speaker learning English. Respond with ONLY the Simplified Chinese translation. No quotation marks, no pinyin, no English, no preamble — ` + `just the translated sentence.` // TranslateMessages builds the message array for translating one short English // explanation into Simplified Chinese. func TranslateMessages(text string) []Message { return []Message{ {Role: "system", Content: translateSystemPrompt}, {Role: "user", Content: text}, } }