feat(api): add completion request cancellation and mermaid rendering
Add support for cancelling in-progress LLM completion requests via new /v1/completions/cancel endpoint with task tracking. Implement mermaid diagram rendering in the Milkdown editor with a new mermaidPlugin. Update copilotPlugin to properly abort requests with descriptive reasons. Refactor settings panel to handle system theme changes reactively. Add camera capture support for image uploads.
This commit is contained in:
@@ -59,14 +59,14 @@ export const copilotGhostMark = $markSchema('copilot_ghost', () => ({
|
||||
}
|
||||
}))
|
||||
|
||||
function clearRuntimeRequests(runtime: CopilotRuntime, invalidateRequest = true) {
|
||||
function clearRuntimeRequests(runtime: CopilotRuntime, invalidateRequest = true, abortReason = 'abort') {
|
||||
if (runtime.debounceTimer) {
|
||||
clearTimeout(runtime.debounceTimer)
|
||||
runtime.debounceTimer = null
|
||||
}
|
||||
|
||||
if (runtime.abortController) {
|
||||
runtime.abortController.abort()
|
||||
runtime.abortController.abort(abortReason)
|
||||
runtime.abortController = null
|
||||
}
|
||||
|
||||
@@ -302,7 +302,7 @@ function doFetchSuggestion(
|
||||
const config = runtime.ctx.get(copilotConfigCtx.key)
|
||||
|
||||
if (runtime.abortController) {
|
||||
runtime.abortController.abort()
|
||||
runtime.abortController.abort('superseded')
|
||||
runtime.abortController = null
|
||||
}
|
||||
|
||||
@@ -611,7 +611,7 @@ export const copilotPlugin = $prose((ctx) => new Plugin<CopilotState>({
|
||||
const nextHasGhost = Boolean(nextGhost?.suggestion && nextGhost.from < nextGhost.to)
|
||||
if (docChanged && prevHasGhost && nextHasGhost) {
|
||||
clearGhostText(nextView)
|
||||
clearRuntimeRequests(runtime)
|
||||
clearRuntimeRequests(runtime, true, 'superseded')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -630,7 +630,7 @@ export const copilotPlugin = $prose((ctx) => new Plugin<CopilotState>({
|
||||
|
||||
const { from, to } = nextView.state.selection
|
||||
if (from !== to) {
|
||||
clearRuntimeRequests(runtime)
|
||||
clearRuntimeRequests(runtime, true, 'manual')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -638,7 +638,7 @@ export const copilotPlugin = $prose((ctx) => new Plugin<CopilotState>({
|
||||
},
|
||||
destroy: () => {
|
||||
unbindDomListeners(activeDom)
|
||||
clearRuntimeRequests(runtime)
|
||||
clearRuntimeRequests(runtime, true, 'destroy')
|
||||
runtimeByView.delete(view)
|
||||
}
|
||||
}
|
||||
@@ -657,14 +657,14 @@ export function setCopilotEnabled(view: EditorView, value: boolean): void {
|
||||
|
||||
runtime.enabled = value
|
||||
if (!value) {
|
||||
clearRuntimeRequests(runtime)
|
||||
clearRuntimeRequests(runtime, true, 'disabled')
|
||||
}
|
||||
}
|
||||
|
||||
export function interruptCopilot(view: EditorView): void {
|
||||
const runtime = runtimeByView.get(view)
|
||||
if (!runtime) return
|
||||
clearRuntimeRequests(runtime)
|
||||
clearRuntimeRequests(runtime, true, 'manual')
|
||||
}
|
||||
|
||||
export function checkSizeLimit(view: EditorView): { size: number; overLimit: boolean } {
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import { codeBlockConfig } from '@milkdown/kit/component/code-block'
|
||||
import mermaid from 'mermaid'
|
||||
|
||||
// ── Mermaid init ────────────────────────────────────────────────────────────
|
||||
let mermaidReady = false
|
||||
let diagramCounter = 0
|
||||
|
||||
function ensureMermaid() {
|
||||
if (mermaidReady) return
|
||||
const dark = window.matchMedia?.('(prefers-color-scheme: dark)').matches
|
||||
mermaid.initialize({
|
||||
startOnLoad: false,
|
||||
theme: dark ? 'dark' : 'default',
|
||||
securityLevel: 'loose',
|
||||
fontFamily: 'inherit',
|
||||
})
|
||||
mermaidReady = true
|
||||
}
|
||||
|
||||
// ── renderPreview ───────────────────────────────────────────────────────────
|
||||
// Pass this function to codeBlockConfig.renderPreview via crepe.editor.config().
|
||||
// For non-mermaid languages, return null to use the default preview renderer.
|
||||
|
||||
export async function mermaidRenderPreview(
|
||||
language: string,
|
||||
content: string,
|
||||
applyPreview: (value: null | string | HTMLElement) => void,
|
||||
): Promise<void> {
|
||||
if (language !== 'mermaid') {
|
||||
applyPreview(null)
|
||||
return
|
||||
}
|
||||
|
||||
ensureMermaid()
|
||||
|
||||
// Show a placeholder immediately
|
||||
const wrapper = document.createElement('div')
|
||||
wrapper.className = 'mermaid-block'
|
||||
const inner = document.createElement('div')
|
||||
inner.className = 'mermaid-inner'
|
||||
inner.innerHTML = '<div class="mermaid-loading">···</div>'
|
||||
wrapper.appendChild(inner)
|
||||
applyPreview(wrapper)
|
||||
|
||||
const id = `mermaid-render-${++diagramCounter}`
|
||||
const code = content.trim() || 'graph TD\nA-->B'
|
||||
|
||||
try {
|
||||
const { svg } = await mermaid.render(id, code)
|
||||
inner.innerHTML = svg
|
||||
applyPreview(wrapper)
|
||||
} catch (err) {
|
||||
const pre = document.createElement('pre')
|
||||
pre.className = 'mermaid-error'
|
||||
pre.textContent = `Mermaid error:\n${err instanceof Error ? err.message : String(err)}`
|
||||
inner.innerHTML = ''
|
||||
inner.appendChild(pre)
|
||||
applyPreview(wrapper)
|
||||
}
|
||||
}
|
||||
|
||||
// ── Milkdown plugin helper ─────────────────────────────────────────────────
|
||||
// Call this inside a crepe.editor.config() callback:
|
||||
// ctx.update(codeBlockConfig.key, (prev) => ({ ...prev, renderPreview: mermaidRenderPreview }))
|
||||
//
|
||||
// Re-export the config key so callers don't need to import @milkdown/components directly.
|
||||
export { codeBlockConfig }
|
||||
Reference in New Issue
Block a user