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
64 lines
2.7 KiB
Go
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)
|
|
}
|