Examples
Explore different ways to wire the completion plugin into editors and runtimes. Each scenario focuses on a single idea with focused snippets so you can mix and match in your app.
Vue + WebLLM Example
Combine WebLLM with the plugin inside a Vue 3 + ProseMirror setup for a fully in-browser AI completion experience. See the full write-up for additional explanations.
<template>
<div class="editor" ref="editorEl"></div>
<p class="hint">Tab to accept · Esc to cancel</p>
</template>
<script setup lang="ts">
import { onMounted, ref, onBeforeUnmount } from "vue";
import { EditorState } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import { schema } from "prosemirror-schema-basic";
import { exampleSetup } from "prosemirror-example-setup";
import { completion } from "prosemirror-completion";
import { CreateMLCEngine } from "@mlc-ai/web-llm";
const editorEl = ref<HTMLDivElement | null>(null);
let enginePromise: Promise<ReturnType<typeof CreateMLCEngine>> | null = null;
const getEngine = () => enginePromise ??= CreateMLCEngine("Llama-3.1-8B-Instruct-q4f32_1-MLC");
const plugin = completion({
debounceMs: 400,
callCompletion: async (context) => {
const engine = await getEngine();
const response = await engine.chat.completions.create({
messages: [{ role: "user", content: context.beforeText }],
temperature: 0.6,
max_tokens: 80,
stream: false,
});
return response.choices[0]?.message?.content ?? "";
},
});
let view: EditorView | null = null;
onMounted(() => {
view = new EditorView(editorEl.value!, {
state: EditorState.create({
schema,
plugins: [...exampleSetup({ schema }), plugin],
}),
});
});
onBeforeUnmount(() => view?.destroy());
</script>
<style scoped>
.editor {
border: 1px solid #d0d7de;
border-radius: 12px;
padding: 16px;
min-height: 240px;
}
.hint {
margin-top: 8px;
color: #768390;
font-size: 13px;
}
</style>Basic Example
import { EditorState } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import { schema } from "prosemirror-schema-basic";
import { exampleSetup } from "prosemirror-example-setup";
import { completion } from "prosemirror-completion";
const completionPlugin = completion({
debounceMs: 300,
minTriggerLength: 3,
callCompletion: async (context) => {
return ` completion for: ${context.beforeText.slice(-20)}`;
},
});
const state = EditorState.create({
schema,
plugins: [...exampleSetup({ schema }), completionPlugin],
});
const view = new EditorView(document.querySelector("#editor"), { state });HTML Rich Text Completion
Return HTML so the suggestion inserts formatted content:
const htmlPlugin = completion({
debounceMs: 500,
callCompletion: async (context) => {
return {
plain: "Bold and italic text",
html: "<p>This is <strong>bold</strong> and <em>italic</em> text</p>"
};
},
});Markdown to ProseMirror Node
Parse Markdown into a ProseMirror node via prosemirror-markdown:
import { defaultMarkdownParser } from "prosemirror-markdown";
const markdownPlugin = completion({
debounceMs: 500,
callCompletion: async (context) => {
const markdown = `
## Suggestion
This is **bold** and *italic* text.
- Item 1
- Item 2
`;
const node = defaultMarkdownParser.parse(markdown);
return { prosemirror: node };
},
});Direct ProseMirror Node
Return fully constructed ProseMirror nodes:
import { schema } from "prosemirror-schema-basic";
const nodePlugin = completion({
debounceMs: 500,
callCompletion: async (context) => {
// Create a formatted paragraph
const paragraph = schema.nodes.paragraph.create(
null,
schema.text("Bold text", [schema.marks.strong.create()])
);
return { prosemirror: paragraph };
},
});Mock Completion
const mockPlugin = completion({
debounceMs: 500,
callCompletion: async (context) => {
await new Promise((resolve) => setTimeout(resolve, 300));
if (context.promptType === "code") {
return `// TODO: Complete this code\n// ${context.beforeText.slice(-30)}`;
}
return `Continue: ${context.beforeText.slice(-20)}`;
},
getPromptType: (context) => {
if (context.beforeText.includes("\`\`\`")) {
return "code";
}
return "common";
},
});WebLLM Integration
import { CreateMLCEngine } from "@mlc-ai/web-llm";
let enginePromise = null;
async function getEngine() {
if (!enginePromise) {
enginePromise = CreateMLCEngine("Llama-3.1-8B-Instruct-q4f32_1-MLC");
}
return enginePromise;
}
const webLLMPlugin = completion({
debounceMs: 800,
callCompletion: async (context) => {
const engine = await getEngine();
const prompt = `Continue: ${context.beforeText}`;
const response = await engine.chat.completions.create({
messages: [{ role: "user", content: prompt }],
temperature: 0.7,
max_tokens: 100,
});
return response.choices[0]?.message?.content ?? "";
},
});With Callbacks
const pluginWithCallbacks = completion({
callCompletion: async (context) => {
return "suggestion";
},
onChange: (context, view) => {
console.log("Completion triggered:", context.promptType);
},
onApply: (result, view) => {
console.log("Applied:", result);
},
onExit: (view) => {
console.log("Completion cancelled");
},
});Custom Styling
/* Custom ghost text styling */
.my-ghost-text {
color: #888;
opacity: 0.5;
font-style: italic;
pointer-events: none;
}
/* Loading state */
.my-ghost-text.loading {
animation: pulse 1s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 0.5; }
50% { opacity: 0.8; }
}completion({
callCompletion: myCompletionFn,
ghostClassName: "my-ghost-text",
});Prompt Templates
Provide a default prompt builder so downstream apps share a consistent completion style:
const basePrompt = ({ beforeText, promptType }: CompletionContext) => {
return `You are a helpful ${promptType} assistant. Continue the user input in the same tone.
Context (last 120 chars):
${beforeText.slice(-120)}
Rules:
- Keep the completion under 40 words.
- Do not repeat existing text.
- Finish the sentence naturally.`;
};
const promptAwarePlugin = completion({
callCompletion: async (context) => {
const prompt = basePrompt(context);
return fetchLLM(prompt);
},
getPromptType: (context) => {
if (context.parent.type.name === "code_block") return "code";
if (context.beforeText.includes("#")) return "markdown";
return "common";
},
});Tip: expose
basePromptas a shared utility so other teams can reuse the same tone/structure.
Full Demo
See the Live Demo for a complete working example with all completion modes:
- Mock - Basic text completion
- HTML - Rich text with HTML formatting
- Markdown - Markdown parsed to ProseMirror nodes
- ProseMirror - Direct Node construction
- WebLLM - Real AI completion