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
This commit is contained in:
prosolis
2026-06-25 20:45:30 -07:00
parent 5e00cdce88
commit a4069d5755
18 changed files with 1640 additions and 19 deletions

View File

@@ -106,3 +106,41 @@ button, a, input {
height: 0;
pointer-events: none;
}
/* --- Suggestion decorations -------------------------------------------------
Inline highlights anchored by string at render time (not stored marks). Each
type gets a soft underline in its palette color; hovering opens its card. */
.petal-suggestion {
border-bottom: 2px solid transparent;
border-radius: 2px;
cursor: pointer;
transition: background 200ms ease;
/* gentle fade + slight upward float as decorations appear */
animation: petal-suggestion-in 260ms ease both;
}
.petal-suggestion:hover {
background: var(--color-surface-alt);
}
.petal-suggestion-grammar { border-bottom-color: var(--color-mint); }
.petal-suggestion-phrasing { border-bottom-color: var(--color-peach); }
.petal-suggestion-idiom { border-bottom-color: var(--color-lavender); }
.petal-suggestion-clarity { border-bottom-color: var(--color-sky); }
.petal-suggestion-voice { border-bottom-color: var(--color-honey); }
@keyframes petal-suggestion-in {
from { opacity: 0; transform: translateY(4px); }
to { opacity: 1; transform: translateY(0); }
}
.petal-suggestion-card {
animation: petal-suggestion-in 200ms ease both;
}
/* Breathing rose dot shown in the StatusBar while a checkpoint runs. */
.petal-checkpoint-dot {
animation: petal-breathe 2s ease-in-out infinite;
}
@keyframes petal-breathe {
0%, 100% { opacity: 0.4; }
50% { opacity: 1; }
}