Files
petal/internal/llm/prompts.go
prosolis a4069d5755 Phase 3: LLM grammar checkpoint
Backend (internal/llm): backend-agnostic LLMClient interface + factory
with vLLM (OpenAI-compat) and Ollama (native) clients, each Complete +
Stream. prompts.go holds the checkpoint and Ask Petal templates;
checkpoint.go salvages JSON from model output (brace-matched), enforces a
per-doc 30s RateLimiter, and truncates the doc to a latency cap.

internal/suggestions: POST /api/docs/:id/check runs a checkpoint and
replaces the doc's pending suggestions in one tx (accepted/rejected kept
as history); GET /api/docs/:id/suggestions lists pending;
POST /api/suggestions/:id/{accept,dismiss} resolves one. Throttled checks
return the current set rather than erroring.

Frontend: useCheckpoint (4s debounce, loads existing on open, stale-guard
tokens); SuggestionHighlight renders ProseMirror decorations re-anchored
by the `original` string on every doc change (not stored marks), with
precise textblock-offset→PM-position mapping; SuggestionCard shows the
type tag + diff + explanation and applies the replacement in-editor on
accept; breathing rose checkpoint dot in the StatusBar; fade-float +
breathe animations.

Tests: llm parse/rate-limit/truncate; suggestions full flow + rate-limit
over httptest with a stub client. Smoke-tested end-to-end against a fake
vLLM endpoint (anchoring verified) and the LLM-unreachable 502 path.

Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
2026-06-25 20:45:30 -07:00

64 lines
2.7 KiB
Go

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.
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": []}`
// CheckpointMessages builds the message array for a grammar checkpoint over the
// given (already-truncated) document text.
func CheckpointMessages(contentText string) []Message {
return []Message{
{Role: "system", Content: checkpointSystemPrompt},
{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)
}