v0.2.0: CRM/ERP 系统升级 - 清理 .gitignore 并移除误提交的 venv/env/db 文件
- 更新 .gitignore:全面覆盖环境变量、数据库、日志、缓存、上传文件 - 移除误跟踪的 server/venv/、crm_data.db、.env 文件 - 新增 server/.env.example 模板 - 新增合同管理、利润核算、AI教练等功能模块 - 新增 Playwright e2e 测试套件 - 前后端多项功能升级和 bug 修复
This commit is contained in:
@@ -72,11 +72,12 @@ async def ocr_image(
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "/no_think\n" + prompt,
|
||||
"content": prompt,
|
||||
"images": [image_base64], # Ollama vision 格式
|
||||
},
|
||||
],
|
||||
"stream": False,
|
||||
"think": False, # 关闭思考模式:稳定输出、避免死循环、提速 2-5x
|
||||
"options": {
|
||||
"temperature": 0.1,
|
||||
"num_predict": 2000,
|
||||
@@ -87,19 +88,18 @@ async def ocr_image(
|
||||
async with httpx.AsyncClient(timeout=120.0) as client:
|
||||
resp = await client.post(url, json=payload)
|
||||
if resp.status_code != 200:
|
||||
print(f"[OCR] 3090 返回 {resp.status_code}: {resp.text[:200]}")
|
||||
return {"success": False, "data": {}, "error": f"VL 模型返回 {resp.status_code}"}
|
||||
detail = resp.text[:200]
|
||||
print(f"[OCR] 3090 返回 {resp.status_code}: {detail}")
|
||||
if "model runner" in detail:
|
||||
return {"success": False, "data": {}, "error": "AI OCR 模型进程崩溃,请联系管理员重启 Ollama 服务"}
|
||||
return {"success": False, "data": {}, "error": f"AI OCR 服务异常 (HTTP {resp.status_code}),请稍后重试"}
|
||||
|
||||
data = resp.json()
|
||||
# Qwen3.5 的 CoT 推理放在 message.thinking,最终结果在 message.content
|
||||
content = data.get("message", {}).get("content", "")
|
||||
thinking = data.get("message", {}).get("thinking", "")
|
||||
|
||||
# 优先从 content 提取 JSON,回退到 thinking
|
||||
for text_source in [content, thinking]:
|
||||
if not text_source:
|
||||
continue
|
||||
cleaned = re.sub(r'<think>.*?</think>', '', text_source, flags=re.DOTALL).strip()
|
||||
# 关闭思考模式后,结果直接在 content(无 thinking 字段)
|
||||
if content:
|
||||
cleaned = re.sub(r'<think>.*?</think>', '', content, flags=re.DOTALL).strip()
|
||||
json_match = re.search(r'\{[\s\S]*\}', cleaned)
|
||||
if json_match:
|
||||
try:
|
||||
@@ -107,16 +107,14 @@ async def ocr_image(
|
||||
print(f"[OCR] 解析成功: {list(result.keys())}")
|
||||
return {"success": True, "data": result}
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
pass
|
||||
|
||||
# 没有提取到 JSON,返回原始文本
|
||||
raw = content or thinking
|
||||
print(f"[OCR] 未能提取 JSON, 内容长度: content={len(content)}, thinking={len(thinking)}")
|
||||
return {"success": True, "data": {"raw_text": raw[:2000]}}
|
||||
print(f"[OCR] 未能提取 JSON, content 长度: {len(content)}")
|
||||
return {"success": True, "data": {"raw_text": content[:2000]}}
|
||||
|
||||
except httpx.TimeoutException:
|
||||
print("[OCR] 3090 超时(60s)")
|
||||
return {"success": False, "data": {}, "error": "VL 模型响应超时"}
|
||||
print("[OCR] 3090 超时(120s)")
|
||||
return {"success": False, "data": {}, "error": "AI OCR 响应超时(120s),模型可能负载过高,请稍后重试"}
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"[OCR] JSON 解析失败: {e}")
|
||||
return {"success": False, "data": {}, "error": f"JSON 解析失败: {e}"}
|
||||
@@ -172,11 +170,11 @@ async def extract_invoice_from_text(
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": f"/no_think\n{prompt}\n\n--- 以下是发票文本内容 ---\n\n{truncated}",
|
||||
# 不传 images —— 纯文本模式
|
||||
"content": f"{prompt}\n\n--- 以下是发票文本内容 ---\n\n{truncated}",
|
||||
},
|
||||
],
|
||||
"stream": False,
|
||||
"think": False, # 关闭思考模式
|
||||
"options": {
|
||||
"temperature": 0.1,
|
||||
"num_predict": 2000,
|
||||
@@ -192,12 +190,9 @@ async def extract_invoice_from_text(
|
||||
|
||||
data = resp.json()
|
||||
content = data.get("message", {}).get("content", "")
|
||||
thinking = data.get("message", {}).get("thinking", "")
|
||||
|
||||
for text_source in [content, thinking]:
|
||||
if not text_source:
|
||||
continue
|
||||
cleaned = re.sub(r'<think>.*?</think>', '', text_source, flags=re.DOTALL).strip()
|
||||
if content:
|
||||
cleaned = re.sub(r'<think>.*?</think>', '', content, flags=re.DOTALL).strip()
|
||||
json_match = re.search(r'\{[\s\S]*\}', cleaned)
|
||||
if json_match:
|
||||
try:
|
||||
@@ -205,11 +200,10 @@ async def extract_invoice_from_text(
|
||||
print(f"[TextExtract] AI 提取成功: {list(result.keys())}")
|
||||
return {"success": True, "data": result}
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
pass
|
||||
|
||||
raw = content or thinking
|
||||
print(f"[TextExtract] 未能提取 JSON, 内容: {raw[:200]}")
|
||||
return {"success": True, "data": {"raw_text": raw[:2000]}}
|
||||
print(f"[TextExtract] 未能提取 JSON, content: {content[:200]}")
|
||||
return {"success": True, "data": {"raw_text": content[:2000]}}
|
||||
|
||||
except httpx.TimeoutException:
|
||||
print("[TextExtract] 3090 超时")
|
||||
|
||||
Reference in New Issue
Block a user