feat(server): add workbench API, DSH planner worker and RAG pipeline

This commit is contained in:
yi
2026-09-16 19:06:20 +08:00
parent 72fc5b47c7
commit d4b5b3f43a
122 changed files with 31906 additions and 0 deletions
+116
View File
@@ -0,0 +1,116 @@
"""导图图片 OCR:PowerShell WinRT Windows.Media.Ocr(zh-Hans-CN),坐标还原阅读顺序"""
import re
import subprocess
import tempfile
from pathlib import Path
from .config import OCR_DIR
# WinRT StorageFile 无法处理含中文的路径 → 中间文件一律用纯 ASCII 临时目录
ASCII_TMP = Path(tempfile.gettempdir()) / "yawen_ocr"
PS_TEMPLATE = r"""$ErrorActionPreference = 'Stop'
Add-Type -AssemblyName System.Runtime.WindowsRuntime
$null = [Windows.Storage.StorageFile, Windows.Storage, ContentType = WindowsRuntime]
$null = [Windows.Media.Ocr.OcrEngine, Windows.Foundation, ContentType = WindowsRuntime]
$null = [Windows.Foundation.IAsyncOperation`1, Windows.Foundation, ContentType = WindowsRuntime]
$null = [Windows.Graphics.Imaging.BitmapDecoder, Windows.Foundation, ContentType = WindowsRuntime]
$asTaskGeneric = ([System.WindowsRuntimeSystemExtensions].GetMethods() | Where-Object { $_.Name -eq 'AsTask' -and $_.GetParameters().Count -eq 1 -and $_.GetParameters()[0].ParameterType.Name -eq 'IAsyncOperation`1' })[0]
function Await($WinRtTask, $ResultType) {
$asTask = $asTaskGeneric.MakeGenericMethod($ResultType)
$netTask = $asTask.Invoke($null, @($WinRtTask))
try { $netTask.GetAwaiter().GetResult() }
catch {
$ex = $_.Exception
while ($ex.InnerException) { $ex = $ex.InnerException }
Write-Output "AWAIT-ERR: $($ex.GetType().FullName): $($ex.Message)"
throw
}
}
$imgPath = '{{IMG}}'
$outFile = '{{OUT}}'
$file = Await ([Windows.Storage.StorageFile]::GetFileFromPathAsync($imgPath)) ([Windows.Storage.StorageFile])
$stream = Await ($file.OpenAsync([Windows.Storage.FileAccessMode]::Read)) ([Windows.Storage.Streams.IRandomAccessStream])
$decoder = Await ([Windows.Graphics.Imaging.BitmapDecoder]::CreateAsync($stream)) ([Windows.Graphics.Imaging.BitmapDecoder])
$bitmap = Await ($decoder.GetSoftwareBitmapAsync()) ([Windows.Graphics.Imaging.SoftwareBitmap])
$engine = [Windows.Media.Ocr.OcrEngine]::TryCreateFromUserProfileLanguages()
if ($null -eq $engine) {
$engine = [Windows.Media.Ocr.OcrEngine]::TryCreateFromLanguage([Windows.Globalization.Language]::new('zh-Hans-CN'))
}
$result = Await ($engine.RecognizeAsync($bitmap)) ([Windows.Media.Ocr.OcrResult])
$sb = New-Object System.Text.StringBuilder
foreach ($line in $result.Lines) {
$firstWord = $line.Words | Select-Object -First 1
$x = [int]$firstWord.BoundingRect.X
$y = [int]$firstWord.BoundingRect.Y
$text = $line.Text
$null = $sb.AppendLine("[$x,$y] $text")
}
[System.IO.File]::WriteAllText($outFile, $sb.ToString(), [System.Text.Encoding]::UTF8)
Write-Host "DONE lines=$($result.Lines.Count)"
"""
def _reorder(lines: list[tuple[int, int, str]]) -> str:
"""按坐标还原导图阅读顺序:行优先(y 容差 60px),行内按 x"""
if not lines:
return ""
lines.sort(key=lambda t: t[1])
rows: list[list[tuple[int, int, str]]] = []
cur_y, cur_row = None, []
for x, y, text in lines:
if cur_y is None or abs(y - cur_y) <= 60:
cur_row.append((x, y, text))
cur_y = y if cur_y is None else min(cur_y, y)
else:
rows.append(sorted(cur_row, key=lambda t: t[0]))
cur_row, cur_y = [(x, y, text)], y
if cur_row:
rows.append(sorted(cur_row, key=lambda t: t[0]))
return "\n".join(" ".join(t[2] for t in row) for row in rows)
def _prepare(img_path: str) -> str:
"""预处理:EXIF 转正 + 缩放到 OCR 引擎可处理尺寸(≤2000px),存纯 ASCII 路径"""
try:
from PIL import Image, ImageOps
im = Image.open(img_path)
im = ImageOps.exif_transpose(im)
w, h = im.size
if max(w, h) > 2000:
r = 2000 / max(w, h)
im = im.resize((int(w * r), int(h * r)), Image.LANCZOS)
ASCII_TMP.mkdir(parents=True, exist_ok=True)
small = ASCII_TMP / f"{Path(img_path).stem}_small.jpg"
im.convert("RGB").save(small, quality=92)
return str(small)
except Exception:
return img_path # PIL 失败就用原图
def ocr_image(img_path: str) -> dict:
"""OCR 一张图片,返回排序后的文本"""
target = _prepare(img_path)
img = Path(target)
OCR_DIR.mkdir(parents=True, exist_ok=True)
out = OCR_DIR / f"{img.stem}_ocr.txt"
ps = OCR_DIR / f"{img.stem}_ocr.ps1"
ps.write_text(PS_TEMPLATE.replace("{{IMG}}", str(img))
.replace("{{OUT}}", str(out)),
encoding="utf-8-sig") # BOM:PowerShell 5.1 按 UTF-8 解析脚本
try:
r = subprocess.run(["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass",
"-File", str(ps)], capture_output=True, text=True,
encoding="gbk", errors="replace", timeout=180)
except Exception as e:
return {"ok": False, "error": f"OCR 进程失败: {e}"}
if not out.exists():
return {"ok": False, "error": (r.stderr or r.stdout or "OCR 无输出")[:300]}
raw = out.read_text(encoding="utf-8", errors="ignore")
lines = []
for ln in raw.splitlines():
m = re.match(r"\[(\d+),(\d+)\] (.*)", ln)
if m:
lines.append((int(m.group(1)), int(m.group(2)), m.group(3).strip()))
text = _reorder(lines)
if not text.strip():
return {"ok": False, "error": "OCR 未识别到文字(图片可能不清晰)"}
return {"ok": True, "text": text, "lines": len(lines)}