推进 OCR UI 与 provider 删除验收
This commit is contained in:
@@ -985,6 +985,173 @@ export const createResourceTabRuntime = (dependencies = {}) => {
|
||||
});
|
||||
};
|
||||
|
||||
const isLocalOcrSourceEntry = (entry) => {
|
||||
if (!entry || String(entry.sourceKind || '').trim() !== 'local_folder') return false;
|
||||
if (!String(entry.rootUri || '').trim() || !String(entry.path || '').trim()) return false;
|
||||
return entry.kind === 'image' || entry.kind === 'pdf';
|
||||
};
|
||||
|
||||
const localOcrProvider = () => {
|
||||
const override = String(window.__MNOTE_LOCAL_OCR_PROVIDER || '').trim().toLowerCase();
|
||||
return override === 'mock' ? 'mock' : 'mineru';
|
||||
};
|
||||
|
||||
const setLocalOcrStatus = (entry, status, message, job) => {
|
||||
if (!(entry?.panel instanceof HTMLElement)) return;
|
||||
const normalizedStatus = String(status || '').trim() || 'unknown';
|
||||
entry.localOcrJob = job && typeof job === 'object' ? job : entry.localOcrJob || null;
|
||||
entry.panel.setAttribute('data-mnote-local-ocr-status', normalizedStatus);
|
||||
if (entry.localOcrJob?.ocrRootRelativePath) {
|
||||
entry.panel.setAttribute('data-mnote-local-ocr-path', String(entry.localOcrJob.ocrRootRelativePath));
|
||||
}
|
||||
const statusNode = entry.panel.querySelector('[data-mnote-local-ocr-status-text]');
|
||||
if (statusNode instanceof HTMLElement) {
|
||||
statusNode.textContent = message || (
|
||||
normalizedStatus === 'done' ? 'OCR 已完成'
|
||||
: normalizedStatus === 'running' ? 'OCR 处理中'
|
||||
: normalizedStatus === 'failed' ? 'OCR 失败'
|
||||
: normalizedStatus === 'stale' ? 'OCR 需更新'
|
||||
: 'OCR 未生成'
|
||||
);
|
||||
}
|
||||
const openButton = entry.panel.querySelector('[data-mnote-local-ocr-action="open"]');
|
||||
if (openButton instanceof HTMLButtonElement) openButton.disabled = !entry.localOcrJob?.ocrRootRelativePath;
|
||||
const insertButton = entry.panel.querySelector('[data-mnote-local-ocr-action="insert"]');
|
||||
if (insertButton instanceof HTMLButtonElement) insertButton.disabled = !entry.localOcrJob?.ocrRootRelativePath;
|
||||
window.dispatchEvent(new CustomEvent('mnote:local-ocr-job-updated', {
|
||||
detail: {
|
||||
status: normalizedStatus,
|
||||
job: entry.localOcrJob || null,
|
||||
rootUri: entry.rootUri || '',
|
||||
sourceRootRelativePath: entry.path || '',
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
const readLocalOcrStatus = async (entry) => {
|
||||
if (!isLocalOcrSourceEntry(entry)) return null;
|
||||
const url = new URL('/api/local-folder/ocr/status', window.location.origin);
|
||||
url.searchParams.set('rootUri', entry.rootUri);
|
||||
url.searchParams.set('sourceRootRelativePath', entry.path);
|
||||
const response = await fetch(url.toString(), { cache: 'no-store', headers: { accept: 'application/json' } });
|
||||
const payload = await response.json().catch(() => null);
|
||||
if (!response.ok || !payload || payload.ok !== true) return null;
|
||||
return payload.job || null;
|
||||
};
|
||||
|
||||
const openLocalOcrSidecar = async (entry, job) => {
|
||||
const target = job || entry?.localOcrJob || null;
|
||||
const ocrPath = String(target?.ocrRootRelativePath || '').trim();
|
||||
if (!ocrPath || typeof openResourceInActiveTab !== 'function') return false;
|
||||
const title = ocrPath.split('/').filter(Boolean).pop() || 'OCR';
|
||||
return await openResourceInActiveTab({
|
||||
kind: 'markdown',
|
||||
title,
|
||||
path: ocrPath,
|
||||
objectIdentity: `local-ocr:${ocrPath}`,
|
||||
assetId: `local-ocr:${ocrPath}`,
|
||||
documentId: String(entry.ownerDocumentId || entry.documentId || currentWebShellDocumentId() || '').trim(),
|
||||
ownerDocumentId: String(entry.ownerDocumentId || entry.documentId || currentWebShellDocumentId() || '').trim(),
|
||||
workspaceId: String(entry.workspaceId || currentWebShellWorkspaceId() || '').trim(),
|
||||
sourceKind: 'local_folder',
|
||||
rootUri: String(entry.rootUri || '').trim(),
|
||||
resourceKind: 'markdown',
|
||||
paneRole: normalizePaneRole(entry.paneRole),
|
||||
});
|
||||
};
|
||||
|
||||
const insertLocalOcrLink = async (entry, job) => {
|
||||
const target = job || entry?.localOcrJob || null;
|
||||
const ocrPath = String(target?.ocrRootRelativePath || '').trim();
|
||||
if (!ocrPath) return false;
|
||||
const response = await fetch('/api/local-folder/ocr/insert', {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json', accept: 'application/json' },
|
||||
body: JSON.stringify({
|
||||
rootUri: String(entry.rootUri || '').trim(),
|
||||
documentId: String(entry.ownerDocumentId || entry.documentId || currentWebShellDocumentId() || '').trim(),
|
||||
ocrRootRelativePath: ocrPath,
|
||||
mode: 'link',
|
||||
}),
|
||||
});
|
||||
const payload = await response.json().catch(() => null);
|
||||
if (!response.ok || !payload || payload.ok !== true) {
|
||||
throw new Error(payload?.error?.message || `local_ocr_insert_failed_${response.status}`);
|
||||
}
|
||||
setLocalOcrStatus(entry, 'done', 'OCR 链接已插入正文', target);
|
||||
return true;
|
||||
};
|
||||
|
||||
const createLocalOcrJob = async (entry) => {
|
||||
if (!isLocalOcrSourceEntry(entry)) return null;
|
||||
setLocalOcrStatus(entry, 'running', 'OCR 处理中', entry.localOcrJob || null);
|
||||
const provider = localOcrProvider();
|
||||
const body = {
|
||||
rootUri: String(entry.rootUri || '').trim(),
|
||||
documentId: String(entry.ownerDocumentId || entry.documentId || currentWebShellDocumentId() || '').trim(),
|
||||
sourceRootRelativePath: String(entry.path || '').trim(),
|
||||
provider,
|
||||
};
|
||||
if (provider === 'mock') {
|
||||
body.mockMarkdown = `# OCR Result\n\n${entry.title || entry.path} OCR UI smoke text`;
|
||||
}
|
||||
const response = await fetch('/api/local-folder/ocr/jobs', {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json', accept: 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
const payload = await response.json().catch(() => null);
|
||||
if (!response.ok || !payload || payload.ok !== true) {
|
||||
const message = payload?.error?.message || `local_ocr_job_failed_${response.status}`;
|
||||
setLocalOcrStatus(entry, 'failed', message, entry.localOcrJob || null);
|
||||
throw new Error(message);
|
||||
}
|
||||
const job = payload.job || null;
|
||||
setLocalOcrStatus(entry, String(job?.status || 'done'), job?.stale ? 'OCR 需更新' : 'OCR 已完成', job);
|
||||
return job;
|
||||
};
|
||||
|
||||
const renderLocalOcrToolbar = (entry) => {
|
||||
if (!isLocalOcrSourceEntry(entry) || !(entry.panel instanceof HTMLElement)) return;
|
||||
const toolbar = entry.panel.querySelector('[data-testid="mnote-local-ocr-toolbar"]');
|
||||
if (!(toolbar instanceof HTMLElement)) return;
|
||||
const runButton = toolbar.querySelector('[data-mnote-local-ocr-action="run"]');
|
||||
const openButton = toolbar.querySelector('[data-mnote-local-ocr-action="open"]');
|
||||
const insertButton = toolbar.querySelector('[data-mnote-local-ocr-action="insert"]');
|
||||
if (runButton instanceof HTMLButtonElement) {
|
||||
runButton.addEventListener('click', async () => {
|
||||
runButton.disabled = true;
|
||||
try {
|
||||
await createLocalOcrJob(entry);
|
||||
} catch (error) {
|
||||
console.warn('mnote local OCR 生成失败', error);
|
||||
} finally {
|
||||
runButton.disabled = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (openButton instanceof HTMLButtonElement) {
|
||||
openButton.addEventListener('click', () => {
|
||||
void openLocalOcrSidecar(entry, entry.localOcrJob).catch((error) => {
|
||||
console.warn('mnote local OCR 打开失败', error);
|
||||
});
|
||||
});
|
||||
}
|
||||
if (insertButton instanceof HTMLButtonElement) {
|
||||
insertButton.addEventListener('click', () => {
|
||||
void insertLocalOcrLink(entry, entry.localOcrJob).catch((error) => {
|
||||
console.warn('mnote local OCR 插入失败', error);
|
||||
setLocalOcrStatus(entry, 'failed', error instanceof Error ? error.message : String(error), entry.localOcrJob || null);
|
||||
});
|
||||
});
|
||||
}
|
||||
setLocalOcrStatus(entry, 'idle', 'OCR 未生成', null);
|
||||
void readLocalOcrStatus(entry).then((job) => {
|
||||
if (!job) return;
|
||||
setLocalOcrStatus(entry, job.stale ? 'stale' : String(job.status || 'done'), job.stale ? 'OCR 需更新' : 'OCR 已完成', job);
|
||||
}).catch(() => undefined);
|
||||
};
|
||||
|
||||
const createResourceSession = (entry, input, readResult) => {
|
||||
const resourcePath = String(input.path || '');
|
||||
const tiptapDocument = localizeTiptapAssetUrls(
|
||||
@@ -1112,16 +1279,21 @@ export const createResourceTabRuntime = (dependencies = {}) => {
|
||||
const openPassiveResourceTab = (entry, input) => {
|
||||
const href = String(input.officeUrl || input.href || '').trim();
|
||||
if (entry.kind === 'image') {
|
||||
entry.panel.innerHTML = '<img class="mnote-resource-tab-image" alt="">';
|
||||
entry.panel.innerHTML = isLocalOcrSourceEntry(entry)
|
||||
? '<div class="mnote-resource-tab-passive-shell" data-mnote-local-ocr-source="true"><div class="mnote-local-ocr-toolbar" data-testid="mnote-local-ocr-toolbar"><span class="mnote-local-ocr-status" data-testid="mnote-local-ocr-status" data-mnote-local-ocr-status-text>OCR 未生成</span><button type="button" data-testid="mnote-local-ocr-run" data-mnote-local-ocr-action="run">生成 OCR</button><button type="button" data-testid="mnote-local-ocr-open" data-mnote-local-ocr-action="open" disabled>打开 OCR</button><button type="button" data-testid="mnote-local-ocr-insert" data-mnote-local-ocr-action="insert" disabled>插入正文</button></div><img class="mnote-resource-tab-image" alt=""></div>'
|
||||
: '<img class="mnote-resource-tab-image" alt="">';
|
||||
const img = entry.panel.querySelector('img');
|
||||
if (img instanceof HTMLImageElement) {
|
||||
img.src = href;
|
||||
img.alt = entry.title;
|
||||
}
|
||||
renderLocalOcrToolbar(entry);
|
||||
installPassiveResourceWatch(entry);
|
||||
return;
|
||||
}
|
||||
entry.panel.innerHTML = '<iframe class="mnote-resource-tab-frame" title=""></iframe>';
|
||||
entry.panel.innerHTML = isLocalOcrSourceEntry(entry)
|
||||
? '<div class="mnote-resource-tab-passive-shell" data-mnote-local-ocr-source="true"><div class="mnote-local-ocr-toolbar" data-testid="mnote-local-ocr-toolbar"><span class="mnote-local-ocr-status" data-testid="mnote-local-ocr-status" data-mnote-local-ocr-status-text>OCR 未生成</span><button type="button" data-testid="mnote-local-ocr-run" data-mnote-local-ocr-action="run">生成 OCR</button><button type="button" data-testid="mnote-local-ocr-open" data-mnote-local-ocr-action="open" disabled>打开 OCR</button><button type="button" data-testid="mnote-local-ocr-insert" data-mnote-local-ocr-action="insert" disabled>插入正文</button></div><iframe class="mnote-resource-tab-frame" title=""></iframe></div>'
|
||||
: '<iframe class="mnote-resource-tab-frame" title=""></iframe>';
|
||||
const frame = entry.panel.querySelector('iframe');
|
||||
if (frame instanceof HTMLIFrameElement) {
|
||||
frame.title = entry.title;
|
||||
@@ -1133,6 +1305,7 @@ export const createResourceTabRuntime = (dependencies = {}) => {
|
||||
}
|
||||
frame.src = href;
|
||||
}
|
||||
renderLocalOcrToolbar(entry);
|
||||
installPassiveResourceWatch(entry);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user