From 9f42a65e9e51bd0973ddeb8086c37aa29c9e5132 Mon Sep 17 00:00:00 2001 From: panyy Date: Mon, 6 Jul 2026 19:54:09 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=80=9D=E7=BB=B4=E5=AF=BC=E5=9B=BE?= =?UTF-8?q?=E5=8A=A9=E6=89=8B)=EF=BC=9A=E4=BC=98=E5=8C=96=E6=89=93?= =?UTF-8?q?=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker/china/Dockerfile | 35 ++++++++++++++++++-- docker/china/Dockerfile.runtime-deps | 36 +++++++++++++++++++++ mineru/backend/vlm/vlm_analyze.py | 3 ++ scripts/build-images.sh | 20 ++++++++++++ scripts/start-mindmap.sh | 23 +++++++++++++ web_ui/nginx.conf.template | 48 +++++++++++++++++++++++++--- web_ui/src/api/document.ts | 32 ++++--------------- web_ui/src/utils/request.ts | 40 +++++++++++++---------- web_ui/vite.config.ts | 10 ++++-- 9 files changed, 197 insertions(+), 50 deletions(-) create mode 100644 docker/china/Dockerfile.runtime-deps create mode 100644 scripts/build-images.sh create mode 100644 scripts/start-mindmap.sh diff --git a/docker/china/Dockerfile b/docker/china/Dockerfile index bfb7006..06f61a8 100644 --- a/docker/china/Dockerfile +++ b/docker/china/Dockerfile @@ -38,11 +38,42 @@ RUN python3 -m pip install -U pip -i https://mirrors.aliyun.com/pypi/simple && \ python3 -m pip install \ "numpy==1.26.4" \ "opencv-python==4.11.0.86" \ + "pydantic>=2.12,<3" \ "modelscope" \ "magic-pdf" \ + boto3 \ + "pdfminer.six>=20251230" \ + pypdfium2 \ + pypdf \ + reportlab \ + pdftext \ + huggingface-hub \ + json-repair \ + fast-langdetect \ + scikit-image \ + openai \ + beautifulsoup4 \ + magika \ + mineru-vl-utils \ + qwen-vl-utils \ + matplotlib \ + ultralytics \ + doclayout_yolo==0.0.4 \ + dill \ + PyYAML \ + ftfy \ + shapely \ + pyclipper \ + omegaconf \ + onnxruntime \ + python-pptx \ + python-docx \ + openpyxl \ + xlrd \ + xlwt \ + pylatexenc \ + mammoth \ -i https://mirrors.aliyun.com/pypi/simple && \ - python3 -m pip install --force-reinstall --no-cache-dir "pydantic>=2.12,<3" -i https://mirrors.aliyun.com/pypi/simple && \ - python3 -c "import pydantic; print('pydantic', pydantic.__version__); from pydantic import TypeAdapter; print('pydantic TypeAdapter ok')" && \ python3 -m pip cache purge WORKDIR /app diff --git a/docker/china/Dockerfile.runtime-deps b/docker/china/Dockerfile.runtime-deps new file mode 100644 index 0000000..6bd7dc4 --- /dev/null +++ b/docker/china/Dockerfile.runtime-deps @@ -0,0 +1,36 @@ +FROM unis-mindmap-api:latest +RUN python3 -m pip install \ + boto3 \ + "pdfminer.six>=20251230" \ + pypdfium2 \ + pypdf \ + reportlab \ + pdftext \ + huggingface-hub \ + json-repair \ + fast-langdetect \ + scikit-image \ + openai \ + beautifulsoup4 \ + magika \ + mineru-vl-utils \ + qwen-vl-utils \ + matplotlib \ + ultralytics \ + doclayout_yolo==0.0.4 \ + dill \ + PyYAML \ + ftfy \ + shapely \ + pyclipper \ + omegaconf \ + onnxruntime \ + python-pptx \ + python-docx \ + openpyxl \ + xlrd \ + xlwt \ + pylatexenc \ + mammoth \ + -i https://mirrors.aliyun.com/pypi/simple && \ + python3 -m pip cache purge diff --git a/mineru/backend/vlm/vlm_analyze.py b/mineru/backend/vlm/vlm_analyze.py index ca7373d..6c0e6d3 100644 --- a/mineru/backend/vlm/vlm_analyze.py +++ b/mineru/backend/vlm/vlm_analyze.py @@ -110,6 +110,7 @@ class ModelSingleton: vllm_llm = vllm.LLM(**kwargs) elif backend == "vllm-async-engine": try: + import inspect from vllm.engine.arg_utils import AsyncEngineArgs from vllm.v1.engine.async_llm import AsyncLLM except ImportError: @@ -122,6 +123,8 @@ class ModelSingleton: from mineru_vl_utils import MinerULogitsProcessor kwargs["logits_processors"] = [MinerULogitsProcessor] # 使用kwargs为 vllm初始化参数 + supported_args = set(inspect.signature(AsyncEngineArgs).parameters) + kwargs = {k: v for k, v in kwargs.items() if k in supported_args} vllm_async_llm = AsyncLLM.from_engine_args(AsyncEngineArgs(**kwargs)) elif backend == "lmdeploy-engine": try: diff --git a/scripts/build-images.sh b/scripts/build-images.sh new file mode 100644 index 0000000..0445beb --- /dev/null +++ b/scripts/build-images.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +set -euo pipefail + +VERSION=${VERSION:-$(date +%Y%m%d%H%M%S)} +ROOT_DIR=$(cd "$(dirname "$0")/.." && pwd) + +cd "$ROOT_DIR" + +echo "Building UnisMindMap API image: unis-mindmap-api:${VERSION}" +sudo docker build -f docker/china/Dockerfile \ + -t unis-mindmap-api:${VERSION} \ + -t unis-mindmap-api:latest \ + -t mineru:test \ + . + +echo "Building UnisMindMap Web image: unis-mindmap-web:${VERSION}" +sudo docker build -f web_ui/Dockerfile.portable \ + -t unis-mindmap-web:${VERSION} \ + -t unis-mindmap-web:latest \ + web_ui diff --git a/scripts/start-mindmap.sh b/scripts/start-mindmap.sh new file mode 100644 index 0000000..51c5deb --- /dev/null +++ b/scripts/start-mindmap.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -euo pipefail + +NETWORK=${NETWORK:-unis-ai} +API_NAME=${API_NAME:-mineru-api} +WEB_NAME=${WEB_NAME:-mindmap-web} +WEB_PORT=${WEB_PORT:-9898} +MINERU_API_URL=${MINERU_API_URL:-http://${API_NAME}:8000} + +sudo docker network create "$NETWORK" >/dev/null 2>&1 || true +sudo docker rm -f "$API_NAME" "$WEB_NAME" >/dev/null 2>&1 || true + +sudo docker run -d --name "$API_NAME" --network "$NETWORK" \ + --privileged --ipc=host --shm-size=32g \ + -e MINERU_MODEL_SOURCE=local \ + -e PYTHONPATH=/app:/usr/local/corex-4.4.0/lib64/python3/dist-packages \ + unis-mindmap-api:latest \ + mineru-api --host 0.0.0.0 --port 8000 + +sudo docker run -d --name "$WEB_NAME" --network "$NETWORK" \ + -p "${WEB_PORT}:9898" \ + -e MINERU_API_URL="$MINERU_API_URL" \ + unis-mindmap-web:latest diff --git a/web_ui/nginx.conf.template b/web_ui/nginx.conf.template index bc6745b..8dc9628 100644 --- a/web_ui/nginx.conf.template +++ b/web_ui/nginx.conf.template @@ -8,13 +8,44 @@ server { access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; - location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ { + location ~* ^/(assets|mindmap/assets)/.*\.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ { + rewrite ^/mindmap/(.*)$ /$1 break; expires 1y; add_header Cache-Control "public, immutable"; + try_files $uri =404; } - location / { - try_files $uri $uri/ /index.html; + location = /mindmap { + return 301 /mindmap/; + } + + location = /mindmap/api/health { + proxy_pass ${MINERU_API_URL}/health; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location = /api/health { + proxy_pass ${MINERU_API_URL}/health; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location ^~ /mindmap/api/ { + rewrite ^/mindmap/api/(.*)$ /api/$1 break; + proxy_pass ${MINERU_API_URL}; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + client_max_body_size 100M; + proxy_connect_timeout 300s; + proxy_send_timeout 300s; + proxy_read_timeout 300s; } location = /file_parse { @@ -30,7 +61,7 @@ server { } location /api/ { - proxy_pass ${MINERU_API_URL}/; + proxy_pass ${MINERU_API_URL}/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -40,4 +71,13 @@ server { proxy_send_timeout 300s; proxy_read_timeout 300s; } + + location ^~ /mindmap/ { + rewrite ^/mindmap/(.*)$ /$1 break; + try_files $uri $uri/ /index.html; + } + + location / { + try_files $uri $uri/ /index.html; + } } diff --git a/web_ui/src/api/document.ts b/web_ui/src/api/document.ts index bfbc17c..f10def1 100644 --- a/web_ui/src/api/document.ts +++ b/web_ui/src/api/document.ts @@ -41,17 +41,6 @@ export interface MindmapOrganizeProgress extends ParseProgress { result_md?: string | null } -export interface MindmapLlmConfig { - base_url?: string - api_key?: string - model?: string - timeout?: number - temperature?: number - max_context_tokens?: number - max_output_tokens?: number - safety_tokens?: number -} - export const documentApi = { /** * 解析文档 @@ -84,7 +73,7 @@ export const documentApi = { formData.append('task_id', taskId) } - return request.post('/api/file_parse', formData, { + return request.post('/file_parse', formData, { headers: { 'Content-Type': 'multipart/form-data', ...(taskId ? { 'X-Task-ID': taskId } : {}) @@ -99,7 +88,7 @@ export const documentApi = { }, createParseTask(taskId: string): Promise { - return request.post(`/api/parse_tasks/${encodeURIComponent(taskId)}`).then(result => { + return request.post(`/parse_tasks/${encodeURIComponent(taskId)}`).then(result => { return result as unknown as ParseProgress }) }, @@ -108,30 +97,23 @@ export const documentApi = { * 查询解析进度 */ getParseProgress(taskId: string): Promise { - return request.get(`/api/parse_progress/${taskId}`).then(result => { + return request.get(`/parse_progress/${taskId}`).then(result => { return result as unknown as ParseProgress }) }, - createMindmapTask( - taskId: string, - markdown: string, - mode = 'smart', - prompt?: string, - llmConfig?: MindmapLlmConfig - ): Promise { - return request.post(`/api/mindmap_tasks/${encodeURIComponent(taskId)}`, { + createMindmapTask(taskId: string, markdown: string, mode = 'smart', prompt?: string): Promise { + return request.post(`/mindmap_tasks/${encodeURIComponent(taskId)}`, { markdown, mode, - prompt, - llm_config: llmConfig + prompt }).then(result => { return result as unknown as MindmapOrganizeProgress }) }, getMindmapProgress(taskId: string): Promise { - return request.get(`/api/mindmap_progress/${encodeURIComponent(taskId)}`).then(result => { + return request.get(`/mindmap_progress/${encodeURIComponent(taskId)}`).then(result => { return result as unknown as MindmapOrganizeProgress }) } diff --git a/web_ui/src/utils/request.ts b/web_ui/src/utils/request.ts index 5febfac..d6b5f2c 100644 --- a/web_ui/src/utils/request.ts +++ b/web_ui/src/utils/request.ts @@ -1,18 +1,24 @@ import axios from 'axios' -// 创建 axios 实例 +const getApiBaseURL = () => { + const pathname = window.location.pathname + const basePath = pathname === '/mindmap' || pathname.startsWith('/mindmap/') ? '/mindmap' : '' + return `${basePath}/api` +} + +// ?? axios ?? const request = axios.create({ - baseURL: '', - timeout: 300000, // 5分钟超时 + baseURL: getApiBaseURL(), + timeout: 300000, // 5???? headers: { 'Content-Type': 'application/json' } }) -// 请求拦截器 +// ????? request.interceptors.request.use( (config) => { - // 可以在这里添加认证token等 + // ?????????token? return config }, (error) => { @@ -20,13 +26,13 @@ request.interceptors.request.use( } ) -// 响应拦截器 +// ????? request.interceptors.response.use( (response) => { return response.data }, (error) => { - // 统一错误处理 + // ?????? if (error.response) { const serverMessage = error.response.data?.error || error.response.data?.detail if (serverMessage) { @@ -36,34 +42,34 @@ request.interceptors.response.use( switch (error.response.status) { case 400: - error.message = '请求参数错误' + error.message = '??????' break case 401: - error.message = '未授权,请重新登录' + error.message = '?????????' break case 403: - error.message = '拒绝访问' + error.message = '????' break case 404: - error.message = '请求资源不存在' + error.message = '???????' break case 500: - error.message = '服务器内部错误' + error.message = '???????' break case 502: - error.message = '网关错误' + error.message = '????' break case 503: - error.message = '服务不可用' + error.message = '?????' break case 504: - error.message = '网关超时' + error.message = '????' break default: - error.message = `连接错误${error.response.status}` + error.message = `????${error.response.status}` } } else { - error.message = '网络连接异常' + error.message = '??????' } return Promise.reject(error) diff --git a/web_ui/vite.config.ts b/web_ui/vite.config.ts index d697784..ee61067 100644 --- a/web_ui/vite.config.ts +++ b/web_ui/vite.config.ts @@ -3,6 +3,7 @@ import vue from '@vitejs/plugin-vue' import { resolve } from 'path' export default defineConfig({ + base: './', plugins: [vue()], resolve: { alias: { @@ -13,8 +14,13 @@ export default defineConfig({ port: 3000, proxy: { '/api': { - target: 'http://10.100.52.76:8000', + target: process.env.VITE_API_PROXY || 'http://127.0.0.1:8000', changeOrigin: true + }, + '/mindmap/api': { + target: process.env.VITE_API_PROXY || 'http://127.0.0.1:8000', + changeOrigin: true, + rewrite: path => path.replace(/^\/mindmap\/api/, '/api') } } }, @@ -30,4 +36,4 @@ export default defineConfig({ } } } -}) \ No newline at end of file +})