diff --git a/.harness/CLAUDE.md b/.harness/CLAUDE.md index b501441..25c70d4 100644 --- a/.harness/CLAUDE.md +++ b/.harness/CLAUDE.md @@ -1,6 +1,6 @@ -# 钢研院智能问答平台(gangyan) +# 钢研院智能问答平台(战知) -面向冶金行业的 LLM 智能问答系统,提供智能对话、文档撰写、知识库管理、文献检索、翻译等功能。 +面向科研单位的 LLM 智能问答系统,提供智能对话、文档撰写、知识库管理、文献研读、实用工具广场等功能。 ## 项目结构 @@ -20,14 +20,104 @@ | MinIO | 9002(console)/9003(API) | 对象存储(Milvus 依赖) | | Embedding API | 10.102.24.75:3000 | bge-m3 向量模型 + LLM 网关 | +## LLM 配置 + +- 网关: `http://10.102.24.75:3000/v1/chat/completions` +- API Key: `sk-BlQIGRrotbVDWE5mXCPBFjVWIvJ83hldzz67xInNwzVo7pPb` +- 主力模型: `deepseek-v3`(所有场景统一使用,r1 已弃用) +- Embedding: `bge-m3` +- 上下文限制: 32K tokens + +## 服务器访问 + +- SSH 直连: `ssh target-203-8`(已配公钥,自动通过 jump-203-17 跳转) +- 跳板机: `huawei@192.168.203.17` +- 目标机: `hawei@192.168.203.8` +- 项目路径: `/opt/download/oss_files/gangyan-deploy/gangyan/` +- Python: `/opt/software/miniconda3/envs/langchain-chat/bin/python` +- 网络代理: `http://219.234.197.247:53128` +- Docker 镜像源: `docker.1ms.run`(Docker Hub 直连不通,必须用镜像源) + +## 工具广场 - 已部署工具 + +### 文档处理 +| 工具 | 端口 | 容器名 | 镜像 | +|------|------|--------|------| +| Stirling PDF | 18080 | stirling-pdf | `docker.1ms.run/frooodle/s-pdf` | +| TrWebOCR (中文OCR) | 18083 | trwebocr | `docker.1ms.run/mmmz/trwebocr` | +| LibreTranslate (翻译) | 18084 | libretranslate | `docker.1ms.run/libretranslate/libretranslate` | + +LibreTranslate 配置: `LT_LOAD_ONLY=en,zh`、`LT_HIDE_API=true`,需设 HTTP_PROXY 代理下载语言包。 + +### 图片处理 +| 工具 | 端口 | 容器名 | 镜像 | +|------|------|--------|------| +| imgcompress (压缩/转换/抠图) | 18087 | imgcompress | `imgcompress-nofooter:latest`(commit 版,隐藏了 footer) | +| Lama Cleaner (AI擦除) | 18088 | lama-cleaner | `docker.1ms.run/cwq1913/lama-cleaner:cpu-0.33.0` | +| webp2jpg-online (格式转换) | 18089 | webp2jpg | `docker.1ms.run/wbsu2003/webp2jpg-online:v1` | + +Lama Cleaner 启动命令: `lama-cleaner --model=lama --device=cpu --host=0.0.0.0 --port=8080`,需代理下载模型(约196MB),模型缓存在 volume `lama-models`。 + +### 创作绘图 +| 工具 | 端口 | 容器名 | 镜像 | +|------|------|--------|------| +| Excalidraw (白板) | 18081 | excalidraw | `docker.1ms.run/excalidraw/excalidraw` | +| PPTist (AI PPT) | 18085 | pptist | `pptist:latest`(自建镜像) | + +PPTist 自建镜像: Dockerfile 在 `scripts/pptist-deploy/`,构建时需代理 + npm 淘宝镜像源。容器内 nginx 反代 `/pptapi/` 到宿主机 18086 端口的 AI 后端。 + +### 科研写作 +| 工具 | 端口 | 容器名 | 镜像 | +|------|------|--------|------| +| Overleaf (LaTeX论文) | 18090 | overleaf | `docker.1ms.run/sharelatex/sharelatex` | +| LaTeX 公式编辑器 | 18091 | latex-editor | `docker.1ms.run/nginx:alpine`(纯前端) | + +Overleaf 配置: +- docker-compose 在 `scripts/overleaf-deploy/docker-compose.yml` +- 依赖 MongoDB 8.0(需 replica set)+ Redis +- 管理员: `admin@company.com` / `qwerQWER1234` +- 注册: 管理员在 `/admin/register` 手动添加,不支持自助注册 +- 没有邮件服务,新用户通过管理员生成的链接设置密码 + +## AI 代理服务 + +以 screen 会话运行,重启服务器后需手动恢复。 + +| 服务 | 端口 | screen 名 | 文件 | 功能 | +|------|------|-----------|------|------| +| Excalidraw AI | 18082 | aiproxy | `scripts/excalidraw-ai-proxy.py` | text-to-diagram(Mermaid)、wireframe-to-code | +| PPTist AI | 18086 | pptist-ai | `scripts/pptist-ai-backend.py` | 大纲生成、PPT 生成(JSONL流式)、AI 写作 | + +启动方式: +```bash +PYTHON=/opt/software/miniconda3/envs/langchain-chat/bin/python +screen -dmS aiproxy $PYTHON scripts/excalidraw-ai-proxy.py +screen -dmS pptist-ai $PYTHON scripts/pptist-ai-backend.py +``` + +## 前端应用广场 + +- 文件: `chat_web_front/src/views/applications/index.vue` +- 布局: 所有分类平铺展示,CSS Grid 自适应 `repeat(auto-fill, minmax(280px, 1fr))` +- 工具配置是前端静态数据,不走后端数据库 +- 点击工具卡片 `window.open` 新标签打开 `${protocol}//${hostname}:${port}` + +## 品牌 + +- 名称: 战知(原"知冶") +- 标语: 聚尖端之力,创多维平台 +- 副标语: 聚合科技动能,扩展创新疆界,引领行业跃迁升级 + ## 项目位置 -- 服务器路径:`/opt/download/oss_files/gangyan-deploy/gangyan` -- Git 仓库:`http://123.57.146.97:3000/liuguancen/gangyan.git` -- 分支:`main` +- Git 仓库: `http://123.57.146.97:3000/liuguancen/gangyan.git` +- 分支: `main` -## 详细文档索引 +## 开发注意事项 -- [系统架构与运行配置](architecture.md) — 服务拓扑、各模块详细配置、包结构、API 分组 -- [开发构建指南](development-guide.md) — 启动顺序、构建命令、环境要求、日志与健康检查 -- [Agent 协同规则](agent-coordination.md) — 多 Agent 并行开发的分支、接口变更、提交规范 +- 功能未完全验证通过前不要 commit,避免无用提交 +- 前端用 Vite dev server 跑,scp 文件到服务器后自动热更新 +- Docker Hub 不通,拉镜像必须加 `docker.1ms.run/` 前缀 +- 容器内需要网络时(下载模型/语言包)必须设 `HTTP_PROXY` 和 `HTTPS_PROXY` 环境变量 +- 跳板机 SSH 经常断连,保持命令简短,避免长时间占用连接 +- screen 会话管理 AI 代理服务,`screen -ls` 查看,`screen -r 名称` 进入 diff --git a/chat_web_front/src/views/applications/index.vue b/chat_web_front/src/views/applications/index.vue index 187d294..315a753 100644 --- a/chat_web_front/src/views/applications/index.vue +++ b/chat_web_front/src/views/applications/index.vue @@ -1,42 +1,36 @@ + + + +
LaTeX 公式编辑器
+
+
+

输入公式

+
+ + + + + + + + + + + + + + + + + + +
+ +
+
+ + + +
+
+

常用公式模板

+
+ + + + + + + + + + +
+
+
+
+

实时预览 公式渲染由 KaTeX 提供

+
+
+
+
已复制!
+ + + diff --git a/scripts/overleaf-deploy/docker-compose.yml b/scripts/overleaf-deploy/docker-compose.yml new file mode 100644 index 0000000..e434a96 --- /dev/null +++ b/scripts/overleaf-deploy/docker-compose.yml @@ -0,0 +1,49 @@ +version: '3' +services: + sharelatex: + image: docker.1ms.run/sharelatex/sharelatex:latest + container_name: overleaf + restart: always + depends_on: + - mongo + - redis + ports: + - 18090:80 + volumes: + - overleaf-data:/var/lib/overleaf + environment: + OVERLEAF_APP_NAME: "LaTeX 论文编辑器" + OVERLEAF_MONGO_URL: "mongodb://mongo/overleaf?replicaSet=overleaf" + OVERLEAF_REDIS_HOST: "redis" + REDIS_HOST: "redis" + ENABLED_LINKED_FILE_TYPES: "project_file,project_output_file" + ENABLE_CONVERSIONS: "true" + EMAIL_CONFIRMATION_DISABLED: "true" + OVERLEAF_SITE_URL: "http://localhost:18090" + OVERLEAF_LEFT_OPEN_REGISTRATION: "true" + + mongo: + image: docker.1ms.run/mongo:8.0 + container_name: overleaf-mongo + restart: always + command: --replSet overleaf + volumes: + - overleaf-mongo:/data/db + healthcheck: + test: > + mongosh --eval "try { rs.status().ok } catch(e) { rs.initiate({_id:'overleaf',members:[{_id:0,host:'mongo:27017'}]}).ok }" --quiet + interval: 10s + timeout: 10s + retries: 5 + + redis: + image: docker.1ms.run/redis:7-alpine + container_name: overleaf-redis + restart: always + volumes: + - overleaf-redis:/data + +volumes: + overleaf-data: + overleaf-mongo: + overleaf-redis: diff --git a/scripts/overleaf-deploy/overleaf-register.py b/scripts/overleaf-deploy/overleaf-register.py new file mode 100644 index 0000000..79d53f8 --- /dev/null +++ b/scripts/overleaf-deploy/overleaf-register.py @@ -0,0 +1,128 @@ +""" +Overleaf 自助注册服务 +提供简单的注册页面,用户输入邮箱和密码即可注册 +内部通过管理员账号调用 Overleaf API 创建用户 +""" +from fastapi import FastAPI, Request, Form +from fastapi.responses import HTMLResponse +import requests +import os + +app = FastAPI() + +OL_URL = os.getenv("OL_INSTANCE", "http://overleaf") +OL_ADMIN_EMAIL = os.getenv("OL_ADMIN_EMAIL", "") +OL_ADMIN_PASSWORD = os.getenv("OL_ADMIN_PASSWORD", "") + +REGISTER_HTML = """ + + + + +注册 - LaTeX 论文编辑器 + + + +
+

注册账号

+

LaTeX 论文编辑器

+
+ + + +
+

已有账号?去登录

+ MSG_PLACEHOLDER +
+ + +""".replace("OLURL", OL_URL) + + +class OverleafAdmin: + def __init__(self): + self.session = requests.Session() + self.logged_in = False + + def _get_csrf(self, path="/login"): + resp = self.session.get(f"{OL_URL}{path}") + # 从页面中提取 CSRF token + import re + match = re.search(r'name="_csrf"[^>]*value="([^"]+)"', resp.text) + if not match: + match = re.search(r'ol-csrfToken["\s]*content="([^"]+)"', resp.text) + if not match: + match = re.search(r'csrfToken["\s]*:\s*"([^"]+)"', resp.text) + return match.group(1) if match else "" + + def login(self): + csrf = self._get_csrf("/login") + resp = self.session.post(f"{OL_URL}/login", data={ + "_csrf": csrf, + "email": OL_ADMIN_EMAIL, + "password": OL_ADMIN_PASSWORD, + }, allow_redirects=False) + self.logged_in = resp.status_code in (200, 302) + return self.logged_in + + def register_user(self, email): + if not self.logged_in: + self.login() + csrf = self._get_csrf("/admin/register") + resp = self.session.post(f"{OL_URL}/admin/register", data={ + "_csrf": csrf, + "email": email, + }) + # 从响应中提取设置密码的链接 + import re + set_password_url = "" + match = re.search(r'(https?://[^\s"<>]*user/password/set\?[^\s"<>]+)', resp.text) + if match: + set_password_url = match.group(1) + return resp.status_code == 200, set_password_url + + +admin = OverleafAdmin() + + +@app.get("/", response_class=HTMLResponse) +async def index(): + return REGISTER_HTML.replace("MSG_PLACEHOLDER", "") + + +@app.post("/register", response_class=HTMLResponse) +async def register(email: str = Form(...)): + try: + ok, set_url = admin.register_user(email) + if ok and set_url: + # 把链接中的 localhost 替换为实际访问地址 + import re + set_url = re.sub(r'https?://[^/]+', OL_URL, set_url) + msg = f'

注册成功!请点击下方链接设置密码:

设置密码

' + elif ok: + msg = f'

注册成功!该邮箱可能已注册。请前往 登录页面 登录。

' + else: + msg = f'

注册失败,请稍后重试。

' + except Exception as e: + msg = f'

注册失败:{str(e)[:100]}

' + return REGISTER_HTML.replace("MSG_PLACEHOLDER", msg) + + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=18091)