chore: 工作台产品化进行中的改动

把工作区里其余在制品一并入库,主要是工作台产品化的推进:

  后端:新增 capability_definition / project / my_app_center / office_skill
        接口与 action_definition / skill_definition / project / user_app_center
        模型,config 加路由健康上报。
  前端:新增 frontend/src/skills(Office 技能与 workbuddy 复刻)、
        项目管理、应用中心、能力目录页,以及配套 api / store / config;
        聊天侧新增 SpecialistChip / SpecialistPanel / SkillStrip / AppChatRail
        等组件。
  清理:移除旧 views/tools 下的单页工具(已并入工作台)、_frozen 冻结组件、
        cmd/inspect_oa_debug 调试入口,以及两份调试笔记。
  其它:文档与启动脚本同步。

(这批改动与上一提交的 SY23 工作并行进行,此前已在同一工作区内交织。)

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
eaiadmin
2026-09-17 21:32:35 +08:00
co-authored by Claude Code
parent 8b136d4a10
commit 16d63de4e1
180 changed files with 22283 additions and 13850 deletions
@@ -0,0 +1,136 @@
#!/usr/bin/env python3
import json
import os
import subprocess
import sys
os.environ.setdefault("ORT_DISABLE_GPU", "1")
try:
from paddleocr import PaddleOCR
except Exception:
PaddleOCR = None
try:
from rapidocr_onnxruntime import RapidOCR
except Exception:
RapidOCR = None
def flatten_result(raw_result):
lines = []
text_parts = []
for page in raw_result or []:
if not page:
continue
for item in page:
if not item or len(item) < 2:
continue
points = item[0]
text_info = item[1] or []
text = str(text_info[0] or "").strip() if len(text_info) > 0 else ""
score = float(text_info[1] or 0) if len(text_info) > 1 else 0
if not text:
continue
lines.append({
"text": text,
"score": score,
"points": points,
})
text_parts.append(text)
return {
"text": "\n".join(text_parts).strip(),
"lines": lines,
}
def run_paddle_ocr(image_path):
if PaddleOCR is None:
return None
ocr = PaddleOCR(
use_angle_cls=True,
lang="ch",
show_log=False,
use_gpu=False,
enable_mkldnn=False,
cpu_threads=1,
)
result = ocr.ocr(image_path, cls=True)
payload = flatten_result(result)
payload["engine"] = "paddleocr"
return payload
def run_rapid_ocr(image_path):
if RapidOCR is None:
return None
engine = RapidOCR()
result, _ = engine(image_path)
payload = {"text": "", "lines": []}
for item in result or []:
if not item or len(item) < 3:
continue
points, text, score = item[0], str(item[1] or "").strip(), float(item[2] or 0)
if not text:
continue
payload["lines"].append({
"text": text,
"score": score,
"points": points,
})
payload["text"] += f"{text}\n"
payload["text"] = payload["text"].strip()
payload["engine"] = "rapidocr"
return payload
def try_paddle_ocr_subprocess(image_path):
cmd = [sys.executable, __file__, "--engine", "paddle-only", image_path]
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
check=False,
env=os.environ.copy(),
)
except Exception:
return None
if result.returncode != 0:
return None
try:
payload = json.loads((result.stdout or "").strip())
except Exception:
return None
if not isinstance(payload, dict) or payload.get("error"):
return None
payload["engine"] = "paddleocr"
return payload
def main():
args = sys.argv[1:]
engine_mode = "auto"
if len(args) >= 2 and args[0] == "--engine":
engine_mode = args[1].strip().lower() or "auto"
args = args[2:]
if len(args) < 1:
print(json.dumps({"error": "missing_image_path"}, ensure_ascii=False))
sys.exit(1)
image_path = args[0]
if engine_mode == "paddle-only":
payload = run_paddle_ocr(image_path)
elif engine_mode == "rapid-only":
payload = run_rapid_ocr(image_path)
else:
payload = try_paddle_ocr_subprocess(image_path) or run_rapid_ocr(image_path)
if payload is None:
print(json.dumps({"error": "ocr_engine_not_installed"}, ensure_ascii=False))
sys.exit(1)
print(json.dumps(payload, ensure_ascii=False))
if __name__ == "__main__":
main()