推进 OCR UI 与 provider 删除验收

This commit is contained in:
lix-2026
2026-06-01 10:07:42 +08:00
parent 488f9a0937
commit 1481de2018
13 changed files with 548 additions and 55 deletions
+56 -26
View File
@@ -199,8 +199,13 @@ pub(crate) async fn create_job(
match run_mineru_ocr(&root, &plan, token.unwrap_or_default()).await {
Ok(markdown) => markdown,
Err(error) => {
let failed_entry =
build_index_entry(&plan, "failed", now, "", Some(redact_error(error.message())));
let failed_entry = build_index_entry(
&plan,
"failed",
now,
"",
Some(redact_error(error.message())),
);
upsert_ocr_index_entry(&root, failed_entry)?;
return Err(error.with_context(&context));
}
@@ -457,7 +462,11 @@ pub(crate) fn strip_ocr_frontmatter(markdown: &str) -> &str {
trimmed[end + 4..].trim_start_matches('\n')
}
async fn run_mineru_ocr(root: &Path, plan: &OcrSidecarPlan, token: String) -> Result<String, WebError> {
async fn run_mineru_ocr(
root: &Path,
plan: &OcrSidecarPlan,
token: String,
) -> Result<String, WebError> {
let config = MineruClientConfig {
api_base_url: mineru_api_base_url(),
token,
@@ -515,7 +524,10 @@ async fn create_mineru_upload_task(
.map_err(|error| {
WebError::bad_gateway_code(
"mineru_upload_task_failed",
format!("MinerU 上传任务创建失败: {}", redact_error(&error.to_string())),
format!(
"MinerU 上传任务创建失败: {}",
redact_error(&error.to_string())
),
)
})?;
let status = response.status();
@@ -527,8 +539,8 @@ async fn create_mineru_upload_task(
));
}
let value = parse_mineru_json(&body, "mineru_upload_task_json_invalid")?;
let batch_id = find_json_string_by_keys(&value, &["batch_id", "batchId", "id"])
.ok_or_else(|| {
let batch_id =
find_json_string_by_keys(&value, &["batch_id", "batchId", "id"]).ok_or_else(|| {
WebError::bad_gateway_code(
"mineru_upload_task_batch_missing",
"MinerU 上传任务响应缺少 batch_id",
@@ -559,7 +571,10 @@ async fn upload_mineru_source(
.map_err(|error| {
WebError::bad_gateway_code(
"mineru_source_upload_failed",
format!("MinerU 源文件上传失败: {}", redact_error(&error.to_string())),
format!(
"MinerU 源文件上传失败: {}",
redact_error(&error.to_string())
),
)
})?;
if !response.status().is_success() {
@@ -610,7 +625,14 @@ async fn poll_mineru_result_zip_url(
}
if let Some(zip_url) = find_json_string_by_keys(
&value,
&["full_zip_url", "fullZipUrl", "zip_url", "zipUrl", "result_url", "resultUrl"],
&[
"full_zip_url",
"fullZipUrl",
"zip_url",
"zipUrl",
"result_url",
"resultUrl",
],
) {
return Ok(zip_url);
}
@@ -629,7 +651,10 @@ async fn download_mineru_result_zip(
let response = client.get(zip_url).send().await.map_err(|error| {
WebError::bad_gateway_code(
"mineru_result_download_failed",
format!("MinerU 结果包下载失败: {}", redact_error(&error.to_string())),
format!(
"MinerU 结果包下载失败: {}",
redact_error(&error.to_string())
),
)
})?;
if !response.status().is_success() {
@@ -638,12 +663,19 @@ async fn download_mineru_result_zip(
format!("MinerU 结果包下载失败: HTTP {}", response.status().as_u16()),
));
}
response.bytes().await.map(|bytes| bytes.to_vec()).map_err(|error| {
WebError::bad_gateway_code(
"mineru_result_download_failed",
format!("MinerU 结果包读取失败: {}", redact_error(&error.to_string())),
)
})
response
.bytes()
.await
.map(|bytes| bytes.to_vec())
.map_err(|error| {
WebError::bad_gateway_code(
"mineru_result_download_failed",
format!(
"MinerU 结果包读取失败: {}",
redact_error(&error.to_string())
),
)
})
}
fn extract_mineru_markdown_from_zip(bytes: &[u8]) -> Result<String, WebError> {
@@ -678,7 +710,8 @@ fn extract_mineru_markdown_from_zip(bytes: &[u8]) -> Result<String, WebError> {
candidates
.into_iter()
.max_by_key(|(name, markdown)| {
let preferred = name.ends_with("/full.md") || name == "full.md" || name.ends_with("/result.md");
let preferred =
name.ends_with("/full.md") || name == "full.md" || name.ends_with("/result.md");
(preferred, markdown.len())
})
.map(|(_, markdown)| markdown)
@@ -1307,9 +1340,7 @@ mod tests {
writer
.start_file("full.md", zip::write::SimpleFileOptions::default())
.expect("zip start file");
writer
.write_all(markdown.as_bytes())
.expect("zip markdown");
writer.write_all(markdown.as_bytes()).expect("zip markdown");
writer.finish().expect("zip finish");
}
bytes.into_inner()
@@ -1570,7 +1601,9 @@ mod tests {
}),
);
let mock_handle = tokio::spawn(async move {
axum::serve(listener, mock_mineru).await.expect("mock mineru server");
axum::serve(listener, mock_mineru)
.await
.expect("mock mineru server");
});
let old_token = std::env::var("MNOTE_MINERU_API_TOKEN").ok();
@@ -1643,12 +1676,9 @@ mod tests {
assert_eq!(payload["job"]["provider"].as_str(), Some("mineru"));
assert_eq!(upload_count.load(Ordering::SeqCst), 1);
assert_eq!(poll_count.load(Ordering::SeqCst), 1);
let sidecar = fs::read_to_string(
root.join("docs")
.join("Page.ocr")
.join("photo.png.ocr.md"),
)
.expect("sidecar");
let sidecar =
fs::read_to_string(root.join("docs").join("Page.ocr").join("photo.png.ocr.md"))
.expect("sidecar");
assert!(sidecar.contains("provider: mineru"));
assert!(sidecar.contains("识别文本"));