feat(config): add OCR URL configuration and improve image node handling

- Add VITE_OCR_URL environment variable with fallback URL construction
- Define IMAGE_NODE_TYPES constant to support 'image', 'image-block', and 'imageBlock' node types
- Add helper functions for safer image attribute access (getImageSrc, isImageNodeWithSrc, getImageLabel)
- Improve OCR error handling with HTTP status checking and error details
- Wrap OCR context in HTML comments to prevent prompt injection issues
- Update MilkdownEditor to use centralized OCR_URL configuration
This commit is contained in:
“ydy0615”
2026-02-14 21:21:06 +08:00
parent 64cfa58376
commit 794fbf8493
4 changed files with 58 additions and 15 deletions
+29 -7
View File
@@ -9,6 +9,7 @@ import { getOcrCache, checkSizeLimit as checkOcrSizeLimit, OCR_SIZE_LIMIT } from
const COPILOT_PLUGIN_KEY = new PluginKey('milkdown-copilot')
const DEBOUNCE_MS = 1000
const SIZE_LIMIT = OCR_SIZE_LIMIT
const IMAGE_NODE_TYPES = new Set(['image', 'image-block', 'imageBlock'])
interface CopilotState {
from: number
@@ -251,11 +252,28 @@ function insertPlainText(view: EditorView, suggestion: string, from: number, mar
view.dispatch(tr)
}
function getImageSrc(node: ProseNode): string {
const src = node.attrs?.src
return typeof src === 'string' ? src : ''
}
function isImageNodeWithSrc(node: ProseNode): boolean {
return IMAGE_NODE_TYPES.has(node.type.name) && Boolean(getImageSrc(node))
}
function getImageLabel(node: ProseNode): string {
const candidates = [node.attrs?.alt, node.attrs?.title, node.attrs?.caption]
for (const value of candidates) {
if (typeof value === 'string' && value.trim()) return value.trim()
}
return 'untitled'
}
function extractImageFilenames(doc: ProseNode): string[] {
const filenames: string[] = []
doc.descendants((node: ProseNode) => {
if (node.type.name === 'image' && node.attrs.src) {
filenames.push(node.attrs.src)
if (isImageNodeWithSrc(node)) {
filenames.push(getImageSrc(node))
}
})
return filenames
@@ -266,18 +284,22 @@ function buildPrefixWithOCR(prefix: string, doc: ProseNode, cursorPos: number):
doc.descendants((node: ProseNode, pos) => {
if (pos >= cursorPos) return false
if (node.type.name !== 'image' || !node.attrs.src) return true
if (!isImageNodeWithSrc(node)) return true
const ocrText = getOcrCache(node.attrs.src)
const src = getImageSrc(node)
const ocrText = getOcrCache(src)
if (!ocrText) return true
const altText = typeof node.attrs.alt === 'string' ? node.attrs.alt : ''
ocrEntries.push(`image(${altText || 'untitled'}): ${ocrText}`)
const label = getImageLabel(node)
const safeOcrText = ocrText.replace(/<!--|-->/g, '').trim()
if (!safeOcrText) return true
ocrEntries.push(`image(${label}): ${safeOcrText}`)
return true
})
if (!ocrEntries.length) return prefix
return `${prefix}\n\n[OCR Context]\n${ocrEntries.join('\n')}`
return `${prefix}\n\n<!--OCR:\n${ocrEntries.join('\n')}\n-->`
}
function doFetchSuggestion(view: EditorView, runtime: CopilotRuntime, pos: number, prefix: string, suffix: string) {