Ready
Network activity
0 requests
Connected - models run on-device
No recent network activity
0 groups0 total requests
Browse blocks
Complete
Fill in a blank word in your sentence with the top suggestions ranked by likelihood, then click one to apply it and keep going. Runs entirely in your browser, and nothing downloads until you ask.
Install this block
npx shadcn@latest add @localmode/ui/blocks/writing-tools/completeComplete - ModernBERT fill-mask. Model loads only behind an explicit action.
Resolving provider…ModernBERT · ~150 MB · no Chrome AI fill-mask exists
The weather today is very [MASK].
Mask detected
⌘↵
'use client';/** * @file complete.tsx * @description Complete block — ModernBERT (topK 5) fill-mask word prediction with `[MASK]` authoring, ranked predictions, per-candidate substituted-sentence previews, and click-to-apply-and-iterate. Transformers.js-only — there is no Chrome AI fill-mask, so there is no provider to fall back from. */import { useEffect, useState } from 'react';import { useFillMask, toAppError } from '@localmode/react';import type { FillMaskModel } from '@localmode/core';import { MaskTokenInput } from '@/components/mask-token-input';import { ScoredResultBarList } from '@/components/scored-result-bar-list';import { ConfidenceScoreBadge } from '@/components/confidence-score-badge';import { ProviderBadge } from '@/components/provider-badge';import { ErrorAlert } from '@/components/error-alert';import { cn } from '@/lib/utils';const FILL_MASK_MODEL_ID = 'onnx-community/ModernBERT-base-ONNX';const FILL_MASK_MODEL_SIZE = '~150 MB';const MASK_TOKEN = '[MASK]';const COMPLETE_SAMPLES = [ 'The weather today is very [MASK].', 'I love to eat [MASK] for breakfast.', 'Paris is the capital of [MASK].', 'She is a very [MASK] person.', 'The cat sat on the [MASK].',];function confidenceLabel(score: number): 'High' | 'Good' | 'Fair' | 'Low' { if (score >= 0.3) return 'High'; if (score >= 0.1) return 'Good'; if (score >= 0.05) return 'Fair'; return 'Low';}const FILL_MASK_THRESHOLDS = { high: 0.3, medium: 0.1 };function replaceMask(text: string, word: string): string { return text.replace(MASK_TOKEN, word);}type ResolvedFillMask = { model: FillMaskModel; provider: 'transformers'; modelId: string };let fillMaskCache: ResolvedFillMask | null = null;async function resolveFillMask(modelId: string): Promise<ResolvedFillMask> { if (fillMaskCache) return fillMaskCache; const { transformers } = await import('@localmode/transformers'); const model = transformers.fillMask(modelId); fillMaskCache = { model, provider: 'transformers', modelId: model.modelId }; return fillMaskCache;}export function CompleteBlock() { const [input, setInput] = useState('The weather today is very [MASK].'); const [resolved, setResolved] = useState<ResolvedFillMask | null>(null); useEffect(() => { let alive = true; void resolveFillMask(FILL_MASK_MODEL_ID).then((r) => { if (alive) setResolved(r); }); return () => { alive = false; }; }, []); const { data, error, isLoading, execute, cancel, reset } = useFillMask({ model: resolved?.model as FillMaskModel, topK: 5, }); const hasMask = input.includes(MASK_TOKEN); const ready = !!resolved; const predictions = data?.predictions ?? []; const appErr = toAppError(error); const modelId = data?.response.modelId ?? resolved?.modelId ?? null; const predict = () => { if (!hasMask || !ready || isLoading) return; void execute(input); }; const apply = (token: string) => { setInput(replaceMask(input, token)); reset(); }; const top = predictions[0]; return ( <div className="mx-auto flex max-w-4xl flex-col gap-4 p-4"> <p className="text-xs text-muted-foreground"> Complete - ModernBERT fill-mask. Model loads only behind an explicit action. </p> <div className="flex flex-wrap items-center gap-2"> <span data-provider={resolved?.provider ?? 'resolving'}> <ProviderBadge providerName={resolved ? 'Transformers.js' : null} tier="download" modelId={modelId} note={`ModernBERT · ${FILL_MASK_MODEL_SIZE} · no Chrome AI fill-mask exists`} /> </span> <span data-model-id={modelId ?? ''} className="sr-only"> {modelId ?? ''} </span> </div> <div> <MaskTokenInput value={input} onChange={setInput} maskToken={MASK_TOKEN} ariaLabel="Sentence with a [MASK] token" samples={COMPLETE_SAMPLES} onSubmit={predict} placeholder="The weather today is very [MASK]." disabled={isLoading} /> </div> <div className="flex items-center gap-2"> <button type="button" onClick={isLoading ? cancel : predict} disabled={!ready || (!hasMask && !isLoading)} className={cn( 'inline-flex h-8 items-center rounded-md px-3 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50', isLoading ? 'bg-destructive text-destructive-foreground hover:bg-destructive/90' : 'bg-primary text-primary-foreground hover:bg-primary/90', )} > {isLoading ? 'Stop' : ready ? 'Predict' : 'Preparing…'} </button> {!hasMask && ( <span className="text-xs text-amber-600 dark:text-amber-500"> Add a {MASK_TOKEN} token to predict </span> )} </div> {appErr && ( <span> <ErrorAlert message={appErr.message} onRetry={predict} onDismiss={reset} /> </span> )} {(isLoading || predictions.length > 0) && ( <div className="flex flex-col gap-3"> <p className="text-xs font-medium text-muted-foreground"> {isLoading ? 'Predicting…' : `Top ${predictions.length} predictions`} </p> {} <ScoredResultBarList results={predictions.map((p) => ({ label: p.token, score: p.score }))} isLoading={isLoading} skeletonRows={5} limit={5} /> {} {!isLoading && predictions.length > 0 && ( <ul className="flex flex-col gap-1.5"> {predictions.map((p, i) => ( <li key={`${p.token}-${i}`}> <button type="button" onClick={() => apply(p.token)} className="flex w-full items-center gap-3 rounded-md border border-border p-2 text-left text-sm transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" > <span className="inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-muted text-[10px] font-semibold"> {i + 1} </span> <span className="min-w-0 flex-1"> <span className="font-medium">{p.token}</span> <span className="ml-2 text-muted-foreground"> “{replaceMask(input, p.token)}” </span> </span> <span className="shrink-0 text-xs text-muted-foreground"> {confidenceLabel(p.score)} </span> <ConfidenceScoreBadge score={p.score} thresholds={FILL_MASK_THRESHOLDS} /> </button> </li> ))} </ul> )} {} {top && ( <span role="status" aria-label="Top prediction" className="sr-only" > {top.token} </span> )} </div> )} </div> );}