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:
@@ -3,6 +3,8 @@ import type { SaveStatus } from '../../hooks/useAutoSave'
|
||||
interface Props {
|
||||
wordCount: number
|
||||
saveStatus: SaveStatus
|
||||
// True while a grammar checkpoint is in flight — shows the breathing rose dot.
|
||||
checking: boolean
|
||||
}
|
||||
|
||||
const SAVE_LABEL: Record<SaveStatus, string> = {
|
||||
@@ -13,9 +15,10 @@ const SAVE_LABEL: Record<SaveStatus, string> = {
|
||||
error: "Couldn't save",
|
||||
}
|
||||
|
||||
// StatusBar is the slim footer: word count on the left, save state on the right.
|
||||
// The checkpoint dot (grammar pass) joins it in Phase 3.
|
||||
export function StatusBar({ wordCount, saveStatus }: Props) {
|
||||
// StatusBar is the slim footer: word count on the left, save state and the
|
||||
// grammar-checkpoint indicator on the right. The checkpoint dot is a soft rose
|
||||
// circle that breathes while a check is in flight (spec → Signature animations).
|
||||
export function StatusBar({ wordCount, saveStatus, checking }: Props) {
|
||||
const label = SAVE_LABEL[saveStatus]
|
||||
return (
|
||||
<footer
|
||||
@@ -25,6 +28,18 @@ export function StatusBar({ wordCount, saveStatus }: Props) {
|
||||
<span>
|
||||
{wordCount} {wordCount === 1 ? 'word' : 'words'}
|
||||
</span>
|
||||
{checking && (
|
||||
<>
|
||||
<span aria-hidden>·</span>
|
||||
<span className="inline-flex items-center gap-1.5" title="Petal is reading your writing…">
|
||||
<span
|
||||
className="petal-checkpoint-dot inline-block h-2 w-2 rounded-full"
|
||||
style={{ background: 'var(--color-accent)' }}
|
||||
/>
|
||||
Checking…
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
{label && (
|
||||
<>
|
||||
<span aria-hidden>·</span>
|
||||
|
||||
Reference in New Issue
Block a user