Compare commits
11 Commits
e1e5d4f30d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| f8e8017434 | |||
| 0c3a393d04 | |||
| 279b104434 | |||
| 8bb98dc2e1 | |||
| 5eebcb5e83 | |||
| 5850d37c48 | |||
| 570c0f3d61 | |||
| 00a50858f8 | |||
| e1c3e550c3 | |||
| 6337af9481 | |||
| 108022cebd |
3
.gitignore
vendored
@@ -57,3 +57,6 @@ Thumbs.db
|
||||
|
||||
# Claude
|
||||
.claude/
|
||||
|
||||
# PPTist 源码树(pptist-deploy 期间 git clone 下来的 323M 依赖,含 node_modules + .git)
|
||||
scripts/pptist-deploy/PPTist/
|
||||
|
||||
@@ -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 名称` 进入
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
#!/bin/bash
|
||||
# 启动 Java 后端。profile=yj。
|
||||
# MySQL 33306 / Redis 等统一见同目录 application-local.yml(与 scripts/backend-restart.sh 一致,勿再写 -D 数据源以免和 yml 冲突)。
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64
|
||||
export PATH="$JAVA_HOME/bin:$PATH"
|
||||
|
||||
PROJECT_DIR=/opt/download/oss_files/gangyan-deploy/gangyan/backend
|
||||
JAR_FILE="$PROJECT_DIR/chat_web_yj.jar"
|
||||
LOCAL_CFG="$PROJECT_DIR/application-local.yml"
|
||||
|
||||
if [[ ! -f "$JAR_FILE" ]]; then
|
||||
echo "Error: JAR file not found at $JAR_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
pkill -f "chat_web_yj.jar" 2>/dev/null || true
|
||||
sleep 2
|
||||
|
||||
# 日志目录(logback 写 /opt/apps/...);非 root 启动时需可写
|
||||
mkdir -p "$PROJECT_DIR/logs" /opt/apps/logs/chat-server-backend-cast 2>/dev/null || true
|
||||
|
||||
LOG_OUT="$PROJECT_DIR/nohup.out"
|
||||
if ! ( umask 022; : >>"$LOG_OUT" ) 2>/dev/null; then
|
||||
LOG_OUT="/tmp/chat_web_yj_nohup.log"
|
||||
fi
|
||||
|
||||
EXTRA_JAVA=()
|
||||
[[ -f "$LOCAL_CFG" ]] && EXTRA_JAVA=(-Dspring.config.additional-location="file:${LOCAL_CFG}")
|
||||
|
||||
nohup java \
|
||||
-Djava.net.useSystemProxies=false \
|
||||
-Dhttp.nonProxyHosts="localhost|127.*|[::1]|*.local" \
|
||||
-Xms512m \
|
||||
-Xmx2048m \
|
||||
"${EXTRA_JAVA[@]}" \
|
||||
-Dspring.profiles.active=yj \
|
||||
-jar "$JAR_FILE" >>"$LOG_OUT" 2>&1 &
|
||||
|
||||
sleep 10
|
||||
|
||||
if pgrep -f "chat_web_yj.jar" > /dev/null; then
|
||||
echo "OK PID $(pgrep -n -f 'java .*chat_web_yj\.jar' | head -1) log:$LOG_OUT"
|
||||
else
|
||||
tail -50 "$LOG_OUT" 2>/dev/null || true
|
||||
exit 1
|
||||
fi
|
||||
@@ -1 +0,0 @@
|
||||
nohup /home/gc/gangyan/backend/jdk1.8.0_161/bin/java -jar -Xms8g -Xmx8g -DserverUrlPrefix=http://localhost:3000 -DserverFrontUrlPrefix=/chat_web -Dspring.profiles.active=yj -Dspring.redis.database=3 -Dspring.redis.password=Redis_897653 -Dchat.modelName=zhipu-api /home/gc/gangyan/backend/chat_web_yj.jar > /home/gc/gangyan/backend/nohup.out 2>&1 &
|
||||
@@ -1 +0,0 @@
|
||||
ps -ef|grep chat_web_yj | grep -v grep | awk '{print $2}' | xargs kill -9
|
||||
@@ -1,3 +1,7 @@
|
||||
# 原始位置:/opt/download/oss_files/gangyan-deploy/gangyan/backend/application-local.yml
|
||||
# 归档时间:2026-04-20
|
||||
# 用途:yj profile 的本地覆盖(mysql 33306 Docker 映射),启动时通过 -Dspring.config.additional-location 叠加
|
||||
|
||||
# 覆盖 jar 内过期的 application-yj.yml(当前 jar 仍含远程库 123.57.146.97,不配本会长时间卡 Druid)。
|
||||
# 完整 spring.redis + spring.datasource 与源码 application-yj.yml 对齐(本地 Docker 33306)。
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.inspur.llm.chat.base.base.BaseController;
|
||||
import com.inspur.llm.chat.gpt.constant.SysLogTypeConstant;
|
||||
import com.inspur.llm.chat.gpt.enums.BusinessTypeEnum;
|
||||
import com.inspur.llm.chat.gpt.pojo.annotation.Log;
|
||||
import com.inspur.llm.chat.gpt.pojo.command.SysUserPasswordCommand;
|
||||
import com.inspur.llm.chat.gpt.pojo.command.UserCommand;
|
||||
import com.inspur.llm.chat.gpt.pojo.entity.IPageInfo;
|
||||
import com.inspur.llm.chat.gpt.pojo.entity.Query;
|
||||
@@ -105,6 +106,19 @@ public class UserController extends BaseController {
|
||||
return userService.updateUser(command);
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理员重置用户密码
|
||||
*
|
||||
* @author: Auto
|
||||
* @date: 2026-04-08
|
||||
* @version: 1.0.0
|
||||
*/
|
||||
@PutMapping("/password/reset")
|
||||
@Log(type = SysLogTypeConstant.DEFAULT, businessType = BusinessTypeEnum.UPDATE)
|
||||
public ResponseInfo resetPassword(@RequestBody SysUserPasswordCommand command) {
|
||||
return userService.resetPassword(command);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除会员用户
|
||||
*
|
||||
|
||||
@@ -104,6 +104,11 @@ public class User extends BaseEntity {
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 是否管理员 0否 1是
|
||||
*/
|
||||
private Boolean admind;
|
||||
|
||||
/**
|
||||
* 是否删除 0->未删除;1->已删除
|
||||
*/
|
||||
|
||||
@@ -108,4 +108,9 @@ public class UserVO implements Serializable {
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 是否管理员 0否 1是
|
||||
*/
|
||||
private Boolean admind;
|
||||
|
||||
}
|
||||
|
||||
@@ -119,4 +119,12 @@ public interface IUserService extends IService<User> {
|
||||
*/
|
||||
ResponseInfo removeUserById(Long id);
|
||||
|
||||
/**
|
||||
* 管理员重置用户密码
|
||||
*
|
||||
* @param command 包含用户id和新密码
|
||||
* @return 结果
|
||||
*/
|
||||
ResponseInfo resetPassword(SysUserPasswordCommand command);
|
||||
|
||||
}
|
||||
|
||||
@@ -51,14 +51,6 @@ public class GptServiceImpl implements IGptService {
|
||||
if (ValidatorUtil.isNull(user)) {
|
||||
throw new ProhibitVisitException();
|
||||
}
|
||||
|
||||
if (user.getNum() < 1) {
|
||||
throw new BusinessException("电量不足,请分享好友获取电量或开通会员");
|
||||
}
|
||||
// 扣电量
|
||||
UpdateWrapper<User> uw = new UpdateWrapper<>();
|
||||
uw.lambda().set(User::getNum, user.getNum() - 1).eq(BaseEntity::getId, user.getId());
|
||||
userMapper.update(null, uw);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -108,10 +108,17 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IU
|
||||
}
|
||||
user = DozerUtil.convertor(command, User.class);
|
||||
user.setCreateUser(command.getOperater());
|
||||
String name = "手机用户" + RandomUtil.randomString(6);
|
||||
user.setUid(UUID.fastUUID().toString());
|
||||
if (ValidatorUtil.isNull(user.getName()) || user.getName().isEmpty()) {
|
||||
String name = "手机用户" + RandomUtil.randomString(6);
|
||||
user.setName(name);
|
||||
user.setNickName(name);
|
||||
} else if (ValidatorUtil.isNull(user.getNickName()) || user.getNickName().isEmpty()) {
|
||||
user.setNickName(user.getName());
|
||||
}
|
||||
if (ValidatorUtil.isNotNull(command.getPassword()) && !command.getPassword().isEmpty()) {
|
||||
user.setPassword(JWTPasswordEncoder.bcryptEncode(command.getPassword()));
|
||||
}
|
||||
user.setType(UserTypeEnum.TEL.getValue());
|
||||
userMapper.insert(user);
|
||||
return ResponseInfo.success();
|
||||
@@ -185,4 +192,13 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IU
|
||||
return ResponseInfo.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class, transactionManager = "masterTransactionManager")
|
||||
public ResponseInfo resetPassword(SysUserPasswordCommand command) {
|
||||
User user = getUser(command.getId());
|
||||
user.setPassword(JWTPasswordEncoder.bcryptEncode(command.getNewPassword()));
|
||||
userMapper.updateById(user);
|
||||
return ResponseInfo.success();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -92,3 +92,4 @@ xss:
|
||||
# 词向量模型名称
|
||||
embedding-model-name: bge_m3
|
||||
|
||||
|
||||
|
||||
@@ -24,13 +24,14 @@
|
||||
<result column="share_id" property="shareId"/>
|
||||
<result column="type" property="type"/>
|
||||
<result column="status" property="status"/>
|
||||
<result column="admind" property="admind"/>
|
||||
<result column="deleted" property="deleted"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- 通用查询结果列 -->
|
||||
<sql id="Base_Column_List">
|
||||
t.id, t.create_user, t.create_time, t.update_user, t.update_time, t.login_time, t.uid, t.name, t.nick_name, t.tel, t.password,
|
||||
t.avatar, t.openid, t.unionid, t.ip, t.context, t.num, t.share_id, t.type, t.status, t.deleted
|
||||
t.avatar, t.openid, t.unionid, t.ip, t.context, t.num, t.share_id, t.type, t.status, t.admind, t.deleted
|
||||
</sql>
|
||||
|
||||
<!-- 通用查询条件 -->
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Java后端启动脚本
|
||||
|
||||
# 使用 Java 11 运行(兼容性更好)
|
||||
export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64
|
||||
export PATH=$JAVA_HOME/bin:$PATH
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
echo "=========================================="
|
||||
echo "启动 Chat Web Backend"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "Java版本:"
|
||||
java -version
|
||||
echo ""
|
||||
echo "启动端口: 8099"
|
||||
echo "访问地址: http://localhost:8099/chat_web_backend"
|
||||
echo ""
|
||||
echo "按 Ctrl+C 停止服务"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# 启动应用
|
||||
nohup java -jar target/chat_web_backend.jar --spring.profiles.active=dev > ./nohup.out 2>&1 &
|
||||
@@ -1,4 +0,0 @@
|
||||
#!/bin/bash
|
||||
echo "测试MySQL连接..."
|
||||
echo "密码: 1234567890"
|
||||
mysql -uroot -p1234567890 -h127.0.0.1 -e "SHOW DATABASES;" 2>&1 | head -20
|
||||
@@ -1,6 +1,6 @@
|
||||
# 知冶大模型
|
||||
# 战知
|
||||
|
||||
知冶大模型
|
||||
战知智能问答平台
|
||||
|
||||
# chat_web_front_new
|
||||
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.png">
|
||||
<link rel="apple-touch-icon" href="/favicon.png">
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
||||
<link rel="apple-touch-icon" href="/favicon.svg">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>知冶·大模型</title>
|
||||
<title>中国产业基础能力发展战略研究院战知大模型</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
13
chat_web_front/public/favicon.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="_图层_2" data-name="图层 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 31.79 26.46">
|
||||
<g id="_图层_1-2" data-name="图层 1">
|
||||
<g>
|
||||
<path d="M4.91,6.25c.18,0,.74-.6,.87-.71,.17,0,.65-.53,.79-.65,.32-.26,.63-.52,.95-.78,.56-.46,1.12-.92,1.69-1.38,.15,0,.72,1.25,.84,1.45,.45,.78,.9,1.56,1.35,2.35l-1.69,1.36c-1.6-.55-3.19-1.1-4.79-1.65Z"/>
|
||||
<path d="M13.06,5.43c-.5-1.29,.38-3.57,1.27-4.51C15.49-.3,17.51,0,19.03,.14c0,0-2.48,2.93-3.55,5.08-.82-.01-1.63,.02-2.42,.21Z"/>
|
||||
<path d="M17.66,4.96L25.6,.8s2.68,1.49,3.63,2.72c-1.4,.41-9.1,2.6-9.37,2.77-.88-.55-2.2-1.33-2.2-1.33Z"/>
|
||||
<polygon points="20.85 7.78 31.75 7.86 31.79 12.24 20.74 9.98 20.85 7.78"/>
|
||||
<path d="M19.56,13.33c-.17,0-.29,.12-.57,.33-.62,.47-1.24,.94-1.86,1.41,1.64,1.94,3.21,3.94,4.81,5.91,.62,.77,1.23,1.55,1.87,2.3,.97-.59,1.7-1.47,2.6-2.15,.32-.25,1.43-.76,1.45-1.22,0-.13-.86-.71-.99-.82-.42-.34-.84-.69-1.26-1.03-1.49-1.22-2.97-2.43-4.47-3.63-.4-.32-.81-.71-1.25-.98-.14-.08-.24-.12-.32-.13Z"/>
|
||||
<path d="M14.11,26.46l-.16-17.73c0-.06-2.36,.1-2.45,.11-1.79,.15-3.65,.33-5.45,.55-1.43,.18-3.03,.26-4.27,1.12C.46,11.43-.13,13.17,.02,14.73l.17,.55s3.27-1.55,3.58-1.7c.63-.3,1.26-.58,1.9-.84,.54-.22,1.32-.65,1.9-.64-.17,.37-.57,.73-.84,1.05l-1.51,1.81-3.24,3.89c-.48,.57-1.02,.92-.79,1.69,.43,1.47,1.54,2.63,2.77,3.5,1.21-1.91,4.39-7.62,6.15-10.35l-.89,12.76h4.89Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -23,17 +23,25 @@ watch(
|
||||
.content {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
// background: linear-gradient(180deg, #edf2ff 0%, #f7f9ff 100%),
|
||||
// url("./assets/images/chat/chatLogo.png") no-repeat;
|
||||
background-color: #edf2ffcc;
|
||||
background-image: url("./assets/images/chat/chatLogo.png");
|
||||
position: relative;
|
||||
display: flex;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-image: url("./assets/images/chat/chatLogo.svg");
|
||||
background-size: 45%;
|
||||
background-position: right bottom;
|
||||
background-blend-mode: unset;
|
||||
background-repeat: no-repeat;
|
||||
// background-size: 45%;
|
||||
// background-position: right bottom;
|
||||
display: flex;
|
||||
opacity: 0.04;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
.chatLogo {
|
||||
width: 764px;
|
||||
height: 700px;
|
||||
|
||||
@@ -997,3 +997,44 @@ export function fetchLoginStatus<T>() {
|
||||
url: '/app/api/auth/loginStatus'
|
||||
})
|
||||
}
|
||||
|
||||
// ****************************用户管理 - 开始********************************
|
||||
// 用户分页列表
|
||||
export function fetchUserPage<T>(data: { current: number; size: number; keyword?: string }) {
|
||||
return get<T>({
|
||||
url: '/gpt/user/page',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
// 新增用户
|
||||
export function createUser<T>(data: object) {
|
||||
return post<T>({
|
||||
url: '/gpt/user',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
// 修改用户
|
||||
export function editUser<T>(data: object) {
|
||||
return put<T>({
|
||||
url: '/gpt/user',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
// 删除用户
|
||||
export function deleteUser<T>(ids: string) {
|
||||
return post<T>({
|
||||
url: `/gpt/user/${ids}`,
|
||||
})
|
||||
}
|
||||
|
||||
// 管理员重置用户密码
|
||||
export function resetUserPassword<T>(data: { id: number; newPassword: string }) {
|
||||
return put<T>({
|
||||
url: '/gpt/user/password/reset',
|
||||
data,
|
||||
})
|
||||
}
|
||||
// ****************************用户管理 - 结束********************************
|
||||
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000">
|
||||
<rect width="1000" height="1000" rx="200" ry="200" fill="#fff" />
|
||||
<svg viewBox="0 0 107 101" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2">
|
||||
<path style="fill:none" d="M24 17h121v121H24z" transform="matrix(.8843 0 0 .83471 -21.223 -14.19)" />
|
||||
<path d="M119.81 105.98a.549.549 0 0 0-.53-.12c-4.19-6.19-9.52-12.06-14.68-17.73l-.85-.93c0-.11-.05-.21-.12-.3a.548.548 0 0 0-.34-.2l-.17-.18-.12-.09c-.15-.32-.53-.56-.95-.35-1.58.81-3 1.97-4.4 3.04-1.87 1.43-3.7 2.92-5.42 4.52-.7.65-1.39 1.33-1.97 2.09-.28.37-.07.72.27.87-1.22 1.2-2.45 2.45-3.68 3.74-.11.12-.17.28-.16.44.01.16.09.31.22.41l2.16 1.65s.01.03.03.04c3.09 3.05 8.51 7.28 14.25 11.76.85.67 1.71 1.34 2.57 2.01.39.47.76.94 1.12 1.4.19.25.55.3.8.11.13.1.26.21.39.31a.57.57 0 0 0 .8-.1c.07-.09.1-.2.11-.31.04 0 .07.03.1.03.15 0 .31-.06.42-.18l10.18-11.12a.56.56 0 0 0-.04-.8l.01-.01Zm-29.23-3.85c.07.09.14.17.21.25 1.16.98 2.4 2.04 3.66 3.12l-5.12-3.91s-.32-.22-.52-.36c-.11-.08-.21-.16-.31-.24l-.38-.32s.07-.07.1-.11l.35-.35c1.72-1.74 4.67-4.64 6.19-6.06-1.61 1.62-4.87 6.37-4.17 7.98h-.01Zm17.53 13.81-4.22-3.22c-1.65-1.71-3.43-3.4-5.24-5.03 2.28 1.76 4.23 3.25 4.52 3.51 2.21 1.97 2.11 1.61 3.63 2.91l1.83 1.33c-.18.16-.36.33-.53.49l.01.01Zm1.06.81-.08-.06c.16-.13.33-.25.49-.38l-.4.44h-.01ZM42.24 51.45c.14.72.27 1.43.4 2.11.69 3.7 1.33 7.03 2.55 9.56l.48 1.92c.19.73.46 1.64.71 1.83 2.85 2.52 7.22 6.28 11.89 9.82.21.16.5.15.7-.01.01.02.03.03.04.04.11.1.24.15.38.15.16 0 .31-.06.42-.19 5.98-6.65 10.43-12.12 13.6-16.7.2-.25.3-.54.29-.84.2-.24.41-.48.6-.68a.558.558 0 0 0-.1-.86.578.578 0 0 0-.17-.36c-1.39-1.34-2.42-2.31-3.46-3.28-1.84-1.72-3.74-3.5-7.77-7.51-.02-.02-.05-.04-.07-.06a.555.555 0 0 0-.22-.14c-1.11-.39-3.39-.78-6.26-1.28-4.22-.72-10-1.72-15.2-3.27h-.04v-.01s-.02 0-.03.02h-.01l.04-.02s-.31.01-.37.04c-.08.04-.14.09-.19.15-.05.06-.09.12-.47.2-.38.08.08 0 .11 0h-.11v.03c.07.34.05.58.16.97-.02.1.21 1.02.24 1.11l1.83 7.26h.03Zm30.95 6.54s-.03.04-.04.05l-.64-.71c.22.21.44.42.68.66Zm-7.09 9.39s-.07.08-.1.12l-.02-.02c.04-.03.08-.07.13-.1h-.01Zm-7.07 8.47Zm3.02-28.57c.35.35 1.74 1.65 2.06 1.97-1.45-.66-5.06-2.34-6.74-2.88 1.65.29 3.93.66 4.68.91Zm-19.18-2.77c.84 1.44 1.5 6.49 2.16 11.4-.37-1.58-.69-3.12-.99-4.6-.52-2.56-1-4.85-1.67-6.88.14.01.31.03.49.05 0 .01 0 .02.02.03h-.01Zm-.29-1.21c-.23-.02-.44-.04-.62-.05-.02-.04-.03-.08-.04-.12l.66.18v-.01Zm-2.22.45v-.02.02ZM118.9 42.57c.04-.23-1.1-1.24-.74-1.26.85-.04.86-1.35 0-1.31-1.13.06-2.27.32-3.37.53-1.98.37-3.95.78-5.92 1.21-4.39.94-8.77 1.93-13.1 3.11-1.36.37-2.86.7-4.11 1.36-.42.22-.4.67-.17.95-.09.05-.18.08-.28.09-.37.07-.74.13-1.11.19a.566.566 0 0 0-.39.86c-2.32 3.1-4.96 6.44-7.82 9.95-2.81 3.21-5.73 6.63-8.72 10.14-9.41 11.06-20.08 23.6-31.9 34.64-.23.21-.24.57-.03.8.05.06.12.1.19.13-.16.15-.32.3-.48.44-.1.09-.14.2-.16.32-.08.08-.16.17-.23.25-.21.23-.2.59.03.8.23.21.59.2.8-.03.04-.04.08-.09.12-.13a.84.84 0 0 1 1.22 0c.69.74 1.34 1.44 1.95 2.09l-1.38-1.15a.57.57 0 0 0-.8.07c-.2.24-.17.6.07.8l14.82 12.43c.11.09.24.13.37.13.15 0 .29-.06.4-.17l.36-.36a.56.56 0 0 0 .63-.12c20.09-20.18 36.27-35.43 54.8-49.06.17-.12.25-.32.23-.51a.57.57 0 0 0 .48-.39c3.42-10.46 4.08-19.72 4.28-24.27 0-.03.01-.05.02-.07.02-.05.03-.1.04-.14.03-.11.05-.19.05-.19.26-.78.17-1.53-.15-2.15v.02ZM82.98 58.94c.9-1.03 1.79-2.04 2.67-3.02-5.76 7.58-15.3 19.26-28.81 33.14 9.2-10.18 18.47-20.73 26.14-30.12Zm-32.55 52.81-.03-.03c.11.02.19.04.2.04a.47.47 0 0 0-.17 0v-.01Zm6.9 6.42-.05-.04.03-.03c.02 0 .03.02.04.02 0 .02-.02.03-.03.05h.01Zm8.36-7.21 1.38-1.44c.01.01.02.03.03.05-.47.46-.94.93-1.42 1.39h.01Zm2.24-2.21c.26-.3.56-.65.87-1.02.01-.01.02-.03.04-.04 3.29-3.39 6.68-6.82 10.18-10.25.02-.02.05-.04.07-.06.86-.66 1.82-1.39 2.72-2.08-4.52 4.32-9.11 8.78-13.88 13.46v-.01Zm21.65-55.88c-1.86 2.42-3.9 5.56-5.63 8.07-5.46 7.91-23.04 27.28-23.43 27.65-2.71 2.62-10.88 10.46-16.09 15.37-.14.13-.25.24-.34.35a.794.794 0 0 1 .03-1.13c24.82-23.4 39.88-42.89 46-51.38-.13.33-.24.69-.55 1.09l.01-.02Zm16.51 7.1-.01.02c0-.02-.02-.07.01-.02Zm-.91-5.13Zm-5.89 9.45c-2.26-1.31-3.32-3.27-2.71-5.25l.19-.66c.08-.19.17-.38.28-.57.59-.98 1.49-1.85 2.52-2.36.05-.02.1-.03.15-.04a.795.795 0 0 1-.04-.43c.05-.31.25-.58.66-.58.67 0 2.75.62 3.54 1.3.24.19.47.4.68.63.3.35.74.92.96 1.33.13.06.23.62.38.91.14.46.2.93.18 1.4 0 .02 0 .02.01.03-.03.07 0 .37-.04.4-.1.72-.36 1.43-.75 2.05-.04.05-.07.11-.11.16 0 .01-.02.02-.03.04-.3.43-.65.83-1.08 1.13-1.26.89-2.73 1.16-4.2.79a6.33 6.33 0 0 1-.57-.25l-.02-.03Zm16.27-1.63c-.49 2.05-1.09 4.19-1.8 6.38-.03.08-.03.16-.03.23-.1.01-.19.05-.27.11-4.44 3.26-8.73 6.62-12.98 10.11 3.67-3.32 7.39-6.62 11.23-9.95a6.409 6.409 0 0 0 2.11-3.74l.56-3.37.03-.1c.25-.71 1.34-.4 1.17.33h-.02Z" style="fill:#6965db;fill-rule:nonzero" transform="matrix(1 0 0 1 -26.41 -29.49)" />
|
||||
</svg>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.8 KiB |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48" height="48">
|
||||
<rect x="2" y="2" width="44" height="44" rx="8" fill="#1976d2"/>
|
||||
<text x="24" y="20" text-anchor="middle" font-size="14" font-weight="bold" fill="#ffffff" font-family="Arial, sans-serif">EN</text>
|
||||
<line x1="12" y1="25" x2="36" y2="25" stroke="#ffffff" stroke-width="1.5" opacity="0.5"/>
|
||||
<text x="24" y="39" text-anchor="middle" font-size="14" font-weight="bold" fill="#ffffff" font-family="Arial, sans-serif">中</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 519 B |
BIN
chat_web_front/src/assets/images/applications/pptist.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
1
chat_web_front/src/assets/images/applications/pptist.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 48 48"><g fill="none" stroke-linejoin="round" stroke-width="4"><path fill="#d14424" stroke="#333333" d="M8 6C8 4.89543 8.89543 4 10 4H30L40 14V42C40 43.1046 39.1046 44 38 44H10C8.89543 44 8 43.1046 8 42V6Z"/><path stroke="#ffffff" stroke-linecap="round" d="M16 20H32"/><path stroke="#ffffff" stroke-linecap="round" d="M16 28H32"/></g></svg>
|
||||
|
After Width: | Height: | Size: 418 B |
@@ -0,0 +1,34 @@
|
||||
<svg width="82" height="92" viewBox="0 0 82 92" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_dddd_3933_98709)">
|
||||
<path d="M56.3678 0.557543L9.07973 10.0315C4.2844 10.989 0.875 14.8441 0.875 19.3039V73.9053C0.875 77.7856 4.06265 81.0359 8.27589 81.4895L33.0841 84.4375L54.9327 64.6642L77.1014 44.0219L76.7133 12.778C76.6579 8.97326 72.7773 6.15122 68.7027 6.95752L63.2143 8.04098L63.2421 5.67248C63.2975 2.34651 59.9158 -0.147965 56.3678 0.557543Z" fill="#FF4B4B"/>
|
||||
<path d="M63.269 8.04043V57.5772L77.1561 43.9962L76.7681 11.7192C76.7403 8.44358 73.3864 6.02469 69.8661 6.7302L63.269 8.04043Z" fill="#545454"/>
|
||||
<path d="M33.0873 84.4375L70.2189 75.6383L77.1569 43.9963L33.0873 84.4375Z" fill="#D0DBDC"/>
|
||||
<path d="M44.1432 35.3049C44.1432 35.3049 39.9022 30.2907 36.77 28.9301C34.7743 28.0734 31.8084 27.7459 29.48 28.8041C26.0429 30.3663 25.6826 34.7506 28.8425 36.4891C29.5909 36.9175 30.5888 37.2702 31.8915 37.5474C38.8767 39.0088 50.4076 42.0324 48.994 54.2024C48.994 54.2024 48.1901 65.6166 34.9683 66.9268C32.6399 67.1536 30.2839 67.0024 28.0109 66.5236C24.8787 65.8937 19.335 64.6087 16.6185 63.0717L16.1196 50.045H17.4501C17.4501 50.045 20.0834 55.8402 27.5674 58.6371C28.9534 59.141 30.4779 59.3426 31.947 59.141C33.8041 58.889 35.9939 57.982 36.8809 55.2355C36.8809 55.2355 38.3777 51.2292 30.6165 49.0371C24.4075 47.2733 18.5589 45.4592 16.1473 39.5127C15.0109 36.6907 14.8168 33.5663 15.593 30.5679C16.6185 26.6624 19.5567 21.1695 27.8723 18.801C27.8723 18.801 38.7935 16.105 46.2776 19.7585L45.9449 35.3049H44.1432Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_dddd_3933_98709" x="-8.34465e-07" y="-4.17233e-07" width="81.0944" height="91.875" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="0.4375"/>
|
||||
<feGaussianBlur stdDeviation="0.4375"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_3933_98709"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dx="0.4375" dy="1.3125"/>
|
||||
<feGaussianBlur stdDeviation="0.656251"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.09 0"/>
|
||||
<feBlend mode="normal" in2="effect1_dropShadow_3933_98709" result="effect2_dropShadow_3933_98709"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dx="0.875001" dy="3.0625"/>
|
||||
<feGaussianBlur stdDeviation="0.875001"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0"/>
|
||||
<feBlend mode="normal" in2="effect2_dropShadow_3933_98709" result="effect3_dropShadow_3933_98709"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dx="1.75" dy="5.25"/>
|
||||
<feGaussianBlur stdDeviation="1.09375"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.01 0"/>
|
||||
<feBlend mode="normal" in2="effect3_dropShadow_3933_98709" result="effect4_dropShadow_3933_98709"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect4_dropShadow_3933_98709" result="shape"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
BIN
chat_web_front/src/assets/images/applications/trwebocr.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 5.3 KiB |
52
chat_web_front/src/assets/images/chat/chatLogo.svg
Normal file
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="_图层_2" data-name="图层 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 162.4 26.46">
|
||||
<g id="_图层_1-2" data-name="图层 1">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M119.14,11.98h-17.36v-1.49h18.11v.73c0,.42-.34,.75-.75,.75Z"/>
|
||||
<path d="M109.69,12.44s3.91,7.74,7.82,10.54c.39,.28,.92,.27,1.31-.01,1.96-1.45,2.1-1.86,2.1-1.86,0,0-3.8-1.82-9.5-9.58-1.73,.41-1.73,.91-1.73,.91Z"/>
|
||||
<path d="M109.23,5.22h3.14s.24,10.69-8.85,17.49c-.4,.3-.93,.37-1.39,.17-.52-.23-.99-.57-.99-.57,0,0,7.02-4.62,8.09-17.09Z"/>
|
||||
</g>
|
||||
<g fill="#636E77">
|
||||
<path d="M4.91,6.25c.18,0,.74-.6,.87-.71,.17,0,.65-.53,.79-.65,.32-.26,.63-.52,.95-.78,.56-.46,1.12-.92,1.69-1.38,.15,0,.72,1.25,.84,1.45,.45,.78,.9,1.56,1.35,2.35l-1.69,1.36c-1.6-.55-3.19-1.1-4.79-1.65Z"/>
|
||||
<path d="M13.06,5.43c-.5-1.29,.38-3.57,1.27-4.51C15.49-.3,17.51,0,19.03,.14c0,0-2.48,2.93-3.55,5.08-.82-.01-1.63,.02-2.42,.21Z"/>
|
||||
<path d="M17.66,4.96L25.6,.8s2.68,1.49,3.63,2.72c-1.4,.41-9.1,2.6-9.37,2.77-.88-.55-2.2-1.33-2.2-1.33Z"/>
|
||||
<polygon points="20.85 7.78 31.75 7.86 31.79 12.24 20.74 9.98 20.85 7.78"/>
|
||||
<path d="M19.56,13.33c-.17,0-.29,.12-.57,.33-.62,.47-1.24,.94-1.86,1.41,1.64,1.94,3.21,3.94,4.81,5.91,.62,.77,1.23,1.55,1.87,2.3,.97-.59,1.7-1.47,2.6-2.15,.32-.25,1.43-.76,1.45-1.22,0-.13-.86-.71-.99-.82-.42-.34-.84-.69-1.26-1.03-1.49-1.22-2.97-2.43-4.47-3.63-.4-.32-.81-.71-1.25-.98-.14-.08-.24-.12-.32-.13Z"/>
|
||||
<path fill="#FF2500" d="M14.11,26.46l-.16-17.73c0-.06-2.36,.1-2.45,.11-1.79,.15-3.65,.33-5.45,.55-1.43,.18-3.03,.26-4.27,1.12C.46,11.43-.13,13.17,.02,14.73l.17,.55s3.27-1.55,3.58-1.7c.63-.3,1.26-.58,1.9-.84,.54-.22,1.32-.65,1.9-.64-.17,.37-.57,.73-.84,1.05l-1.51,1.81-3.24,3.89c-.48,.57-1.02,.92-.79,1.69,.43,1.47,1.54,2.63,2.77,3.5,1.21-1.91,4.39-7.62,6.15-10.35l-.89,12.76h4.89Z"/>
|
||||
</g>
|
||||
<path d="M46.69,9.69v-1.09h-3.34v-3.7h-2.81V13.08h-2.44v7.1h8.57v-5.93c0-.61-.5-1.11-1.11-1.11h-2.21v-3.45h3.34Zm-3.32,4.42c.26,0,.46,.21,.46,.47v4.22c0,.26-.2,.46-.46,.46h-1.89c-.26,0-.47-.2-.47-.46v-4.22c0-.26,.21-.47,.47-.47h1.89Z"/>
|
||||
<path d="M47.63,4.92h2.83l3.11,16.59h3.2v1.19h-3.69c-.88,0-1.7-.42-2.22-1.13l-.05-.07c-.15-.21-.25-.44-.3-.69l-2.88-15.89Z"/>
|
||||
<path d="M46.25,11.17s3.66,.11,8.06-1.4c.4-.01,.8-.01,1.2,0,.2,0,.4,0,.59,0,.1,0,.2,0,.3,0,.09,0,.18-.01,.27,0,.07,.01,.13,.05,.16,.12,.01,.04,0,.06-.03,.08-.55,.41-1.13,.76-1.78,1-.02,0-.04,.01-.06,.02-.23,.08-.46,.15-.69,.21-.32,.09-.64,.16-.96,.23-.42,.09-.85,.16-1.27,.23-.54,.08-1.07,.15-1.61,.21-.66,.07-1.32,.12-1.99,.17-.34,.02-.68,.04-1.03,.06-.1,0-1.1,.06-1.1,.04-.11-.83-.06-.96-.06-.96Z"/>
|
||||
<polygon points="51.95 6.57 53.16 5.28 55.75 6.93 54.1 8.96 51.95 6.57"/>
|
||||
<path d="M53.99,12.13l2.92,1.18s-3.85,7.29-10.4,9.3c-1.16-1.13-1.27-1.51-1.27-1.51,0,0,4.76-2.01,8.75-8.97Z"/>
|
||||
<path d="M61.29,4.5l1.16,.36,1.99,.63s-3.52,5.89-3.99,6.11-1.84-.61-1.84-.61l2.68-6.5Z"/>
|
||||
<path d="M62.9,8.85c-.07,.01-.05,2.52-.06,2.78-.11,3.63-.83,7.26-3.6,9.85-.04,.04-.31,.33-.39,.25l1.13,1.07s3.29-1.64,5.12-6.3c.85-2.17,1.49-5.04,1.18-7.98,0,0-3.39,.33-3.39,.33Z"/>
|
||||
<path d="M67.33,21.71l2.48-1.6s-3.25-3.77-4.65-3.61-.77,1.32-.77,1.32l2.94,3.88Z"/>
|
||||
<rect x="59.13" y="13.28" width="9.91" height="1.29"/>
|
||||
<rect x="62.22" y="7.67" width="6.47" height="1.21"/>
|
||||
<path d="M76.24,6.59h-6.73v14.76h8.45V8.31c0-.95-.77-1.72-1.72-1.72Zm-1.14,13.02c0,.23-.18,.42-.41,.42h-1.9c-.23,0-.41-.19-.41-.42V8.11c0-.23,.18-.41,.41-.41h1.9c.23,0,.41,.18,.41,.41v11.5Z"/>
|
||||
<rect x="88.14" y="12.65" width="2.81" height="2.64"/>
|
||||
<path d="M122.59,8.83h6.72v.4c0,.53-.43,.97-.97,.97h-5.75v-1.36h0Z"/>
|
||||
<path d="M124.65,4.9h2.52V22.22c0,.33-.27,.6-.6,.6h-1.31c-.33,0-.6-.27-.6-.6V4.9h0Z"/>
|
||||
<rect x="129.08" y="6.29" width="12.55" height="1.03"/>
|
||||
<rect x="144.94" y="5.74" width="9.72" height="1.03"/>
|
||||
<rect x="144.16" y="10.2" width="11.03" height="1.03"/>
|
||||
<rect x="146.47" y="17.28" width="13.48" height="1.03"/>
|
||||
<path d="M144.02,20.69h18.37v.45c0,.43-.35,.79-.79,.79h-17.58v-1.24h0Z"/>
|
||||
<rect x="129.08" y="17.34" width="12.55" height="1.03"/>
|
||||
<path d="M131.16,4.89l.58,4.47c0,.06,.07,.09,.12,.07l2.4-.88c.06-.02,.09-.09,.07-.15l-1.29-3.51h-1.87Z"/>
|
||||
<path d="M136.51,4.9l-.07,1.44-.54,2.41,1.13,.7s2.31-1.43,2.3-4.56h-2.82Z"/>
|
||||
<path d="M133.33,15.84s.72,3.52-4.57,6.06c.11,.83,.33,.83,1.1,.72s3.36-.83,5.28-3.14,1.27-3.74,1.27-3.74l-3.08,.11Z"/>
|
||||
<path d="M135.92,17.06s1.27,3.19,5.39,3.69c.44,.11-.94,2.15-.94,2.15,0,0-3.69-1.05-5.61-4.46,.72-.61,1.16-1.38,1.16-1.38Z"/>
|
||||
<path d="M124.71,10.09s-1.5,3.33-2.66,5.37c.22,2.53,.66,2.92,.66,2.92l2.59-3.52-.28-4.84-.32,.08Z"/>
|
||||
<path d="M126.62,13.37l1.82,1.93,1.6-1.27s-2.97-3.08-3.36-2.81-.06,2.15-.06,2.15Z"/>
|
||||
<path d="M139.53,9.76h-9.6v6.19h10.87v-4.92c0-.7-.57-1.27-1.27-1.27Zm-1.26,4.67c0,.31-.26,.56-.57,.56h-5.13c-.32-.01-.32-.25-.32-.56v-.75c0-.31,.01-.58,.33-.58h5.35c.31,0,.34,.27,.34,.58v.75Zm0-2.55c0,.31-.26,.57-.57,.57h-5.17c-.32,0-.28-.26-.28-.57v-.71c-.01-.32,0-.56,.31-.56l5.38-.02c.31,0,.32,.26,.32,.58v.71Z"/>
|
||||
<path d="M146.55,6.43s.58,5.92-2.5,9.06c.63,.69,1.07,.99,1.07,.99,0,0,5.06-3.91,4.4-10.05-.77-.55-2.97,0-2.97,0Z"/>
|
||||
<polygon points="150.87 6.41 150.87 15.51 153.67 15.51 153.67 6.35 150.87 6.41"/>
|
||||
<path d="M162.4,4.92V13.59c0,1.28-1.04,2.31-2.31,2.31h-2.56v-1.03h2.07V4.99l2.8-.07Z"/>
|
||||
<path d="M155.68,6.42v6.25c0,.25,.2,.45,.45,.45h1.91c.25,0,.45-.2,.45-.45V6.39c0-.25-.21-.45-.46-.45l-1.91,.03c-.25,0-.44,.2-.44,.45Z"/>
|
||||
<rect x="151.77" y="15.47" width="3.01" height="5.33"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.5 KiB |
52
chat_web_front/src/assets/images/login/projectLogo-white.svg
Normal file
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="_图层_2" data-name="图层 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 162.4 26.46">
|
||||
<g id="_图层_1-2" fill="white" data-name="图层 1">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M119.14,11.98h-17.36v-1.49h18.11v.73c0,.42-.34,.75-.75,.75Z"/>
|
||||
<path d="M109.69,12.44s3.91,7.74,7.82,10.54c.39,.28,.92,.27,1.31-.01,1.96-1.45,2.1-1.86,2.1-1.86,0,0-3.8-1.82-9.5-9.58-1.73,.41-1.73,.91-1.73,.91Z"/>
|
||||
<path d="M109.23,5.22h3.14s.24,10.69-8.85,17.49c-.4,.3-.93,.37-1.39,.17-.52-.23-.99-.57-.99-.57,0,0,7.02-4.62,8.09-17.09Z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path d="M4.91,6.25c.18,0,.74-.6,.87-.71,.17,0,.65-.53,.79-.65,.32-.26,.63-.52,.95-.78,.56-.46,1.12-.92,1.69-1.38,.15,0,.72,1.25,.84,1.45,.45,.78,.9,1.56,1.35,2.35l-1.69,1.36c-1.6-.55-3.19-1.1-4.79-1.65Z"/>
|
||||
<path d="M13.06,5.43c-.5-1.29,.38-3.57,1.27-4.51C15.49-.3,17.51,0,19.03,.14c0,0-2.48,2.93-3.55,5.08-.82-.01-1.63,.02-2.42,.21Z"/>
|
||||
<path d="M17.66,4.96L25.6,.8s2.68,1.49,3.63,2.72c-1.4,.41-9.1,2.6-9.37,2.77-.88-.55-2.2-1.33-2.2-1.33Z"/>
|
||||
<polygon points="20.85 7.78 31.75 7.86 31.79 12.24 20.74 9.98 20.85 7.78"/>
|
||||
<path d="M19.56,13.33c-.17,0-.29,.12-.57,.33-.62,.47-1.24,.94-1.86,1.41,1.64,1.94,3.21,3.94,4.81,5.91,.62,.77,1.23,1.55,1.87,2.3,.97-.59,1.7-1.47,2.6-2.15,.32-.25,1.43-.76,1.45-1.22,0-.13-.86-.71-.99-.82-.42-.34-.84-.69-1.26-1.03-1.49-1.22-2.97-2.43-4.47-3.63-.4-.32-.81-.71-1.25-.98-.14-.08-.24-.12-.32-.13Z"/>
|
||||
<path d="M14.11,26.46l-.16-17.73c0-.06-2.36,.1-2.45,.11-1.79,.15-3.65,.33-5.45,.55-1.43,.18-3.03,.26-4.27,1.12C.46,11.43-.13,13.17,.02,14.73l.17,.55s3.27-1.55,3.58-1.7c.63-.3,1.26-.58,1.9-.84,.54-.22,1.32-.65,1.9-.64-.17,.37-.57,.73-.84,1.05l-1.51,1.81-3.24,3.89c-.48,.57-1.02,.92-.79,1.69,.43,1.47,1.54,2.63,2.77,3.5,1.21-1.91,4.39-7.62,6.15-10.35l-.89,12.76h4.89Z"/>
|
||||
</g>
|
||||
<path d="M46.69,9.69v-1.09h-3.34v-3.7h-2.81V13.08h-2.44v7.1h8.57v-5.93c0-.61-.5-1.11-1.11-1.11h-2.21v-3.45h3.34Zm-3.32,4.42c.26,0,.46,.21,.46,.47v4.22c0,.26-.2,.46-.46,.46h-1.89c-.26,0-.47-.2-.47-.46v-4.22c0-.26,.21-.47,.47-.47h1.89Z"/>
|
||||
<path d="M47.63,4.92h2.83l3.11,16.59h3.2v1.19h-3.69c-.88,0-1.7-.42-2.22-1.13l-.05-.07c-.15-.21-.25-.44-.3-.69l-2.88-15.89Z"/>
|
||||
<path d="M46.25,11.17s3.66,.11,8.06-1.4c.4-.01,.8-.01,1.2,0,.2,0,.4,0,.59,0,.1,0,.2,0,.3,0,.09,0,.18-.01,.27,0,.07,.01,.13,.05,.16,.12,.01,.04,0,.06-.03,.08-.55,.41-1.13,.76-1.78,1-.02,0-.04,.01-.06,.02-.23,.08-.46,.15-.69,.21-.32,.09-.64,.16-.96,.23-.42,.09-.85,.16-1.27,.23-.54,.08-1.07,.15-1.61,.21-.66,.07-1.32,.12-1.99,.17-.34,.02-.68,.04-1.03,.06-.1,0-1.1,.06-1.1,.04-.11-.83-.06-.96-.06-.96Z"/>
|
||||
<polygon points="51.95 6.57 53.16 5.28 55.75 6.93 54.1 8.96 51.95 6.57"/>
|
||||
<path d="M53.99,12.13l2.92,1.18s-3.85,7.29-10.4,9.3c-1.16-1.13-1.27-1.51-1.27-1.51,0,0,4.76-2.01,8.75-8.97Z"/>
|
||||
<path d="M61.29,4.5l1.16,.36,1.99,.63s-3.52,5.89-3.99,6.11-1.84-.61-1.84-.61l2.68-6.5Z"/>
|
||||
<path d="M62.9,8.85c-.07,.01-.05,2.52-.06,2.78-.11,3.63-.83,7.26-3.6,9.85-.04,.04-.31,.33-.39,.25l1.13,1.07s3.29-1.64,5.12-6.3c.85-2.17,1.49-5.04,1.18-7.98,0,0-3.39,.33-3.39,.33Z"/>
|
||||
<path d="M67.33,21.71l2.48-1.6s-3.25-3.77-4.65-3.61-.77,1.32-.77,1.32l2.94,3.88Z"/>
|
||||
<rect x="59.13" y="13.28" width="9.91" height="1.29"/>
|
||||
<rect x="62.22" y="7.67" width="6.47" height="1.21"/>
|
||||
<path d="M76.24,6.59h-6.73v14.76h8.45V8.31c0-.95-.77-1.72-1.72-1.72Zm-1.14,13.02c0,.23-.18,.42-.41,.42h-1.9c-.23,0-.41-.19-.41-.42V8.11c0-.23,.18-.41,.41-.41h1.9c.23,0,.41,.18,.41,.41v11.5Z"/>
|
||||
<rect x="88.14" y="12.65" width="2.81" height="2.64"/>
|
||||
<path d="M122.59,8.83h6.72v.4c0,.53-.43,.97-.97,.97h-5.75v-1.36h0Z"/>
|
||||
<path d="M124.65,4.9h2.52V22.22c0,.33-.27,.6-.6,.6h-1.31c-.33,0-.6-.27-.6-.6V4.9h0Z"/>
|
||||
<rect x="129.08" y="6.29" width="12.55" height="1.03"/>
|
||||
<rect x="144.94" y="5.74" width="9.72" height="1.03"/>
|
||||
<rect x="144.16" y="10.2" width="11.03" height="1.03"/>
|
||||
<rect x="146.47" y="17.28" width="13.48" height="1.03"/>
|
||||
<path d="M144.02,20.69h18.37v.45c0,.43-.35,.79-.79,.79h-17.58v-1.24h0Z"/>
|
||||
<rect x="129.08" y="17.34" width="12.55" height="1.03"/>
|
||||
<path d="M131.16,4.89l.58,4.47c0,.06,.07,.09,.12,.07l2.4-.88c.06-.02,.09-.09,.07-.15l-1.29-3.51h-1.87Z"/>
|
||||
<path d="M136.51,4.9l-.07,1.44-.54,2.41,1.13,.7s2.31-1.43,2.3-4.56h-2.82Z"/>
|
||||
<path d="M133.33,15.84s.72,3.52-4.57,6.06c.11,.83,.33,.83,1.1,.72s3.36-.83,5.28-3.14,1.27-3.74,1.27-3.74l-3.08,.11Z"/>
|
||||
<path d="M135.92,17.06s1.27,3.19,5.39,3.69c.44,.11-.94,2.15-.94,2.15,0,0-3.69-1.05-5.61-4.46,.72-.61,1.16-1.38,1.16-1.38Z"/>
|
||||
<path d="M124.71,10.09s-1.5,3.33-2.66,5.37c.22,2.53,.66,2.92,.66,2.92l2.59-3.52-.28-4.84-.32,.08Z"/>
|
||||
<path d="M126.62,13.37l1.82,1.93,1.6-1.27s-2.97-3.08-3.36-2.81-.06,2.15-.06,2.15Z"/>
|
||||
<path d="M139.53,9.76h-9.6v6.19h10.87v-4.92c0-.7-.57-1.27-1.27-1.27Zm-1.26,4.67c0,.31-.26,.56-.57,.56h-5.13c-.32-.01-.32-.25-.32-.56v-.75c0-.31,.01-.58,.33-.58h5.35c.31,0,.34,.27,.34,.58v.75Zm0-2.55c0,.31-.26,.57-.57,.57h-5.17c-.32,0-.28-.26-.28-.57v-.71c-.01-.32,0-.56,.31-.56l5.38-.02c.31,0,.32,.26,.32,.58v.71Z"/>
|
||||
<path d="M146.55,6.43s.58,5.92-2.5,9.06c.63,.69,1.07,.99,1.07,.99,0,0,5.06-3.91,4.4-10.05-.77-.55-2.97,0-2.97,0Z"/>
|
||||
<polygon points="150.87 6.41 150.87 15.51 153.67 15.51 153.67 6.35 150.87 6.41"/>
|
||||
<path d="M162.4,4.92V13.59c0,1.28-1.04,2.31-2.31,2.31h-2.56v-1.03h2.07V4.99l2.8-.07Z"/>
|
||||
<path d="M155.68,6.42v6.25c0,.25,.2,.45,.45,.45h1.91c.25,0,.45-.2,.45-.45V6.39c0-.25-.21-.45-.46-.45l-1.91,.03c-.25,0-.44,.2-.44,.45Z"/>
|
||||
<rect x="151.77" y="15.47" width="3.01" height="5.33"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.5 KiB |
52
chat_web_front/src/assets/images/login/projectLogo.svg
Normal file
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="_图层_2" data-name="图层 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 162.4 26.46">
|
||||
<g id="_图层_1-2" data-name="图层 1">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M119.14,11.98h-17.36v-1.49h18.11v.73c0,.42-.34,.75-.75,.75Z"/>
|
||||
<path d="M109.69,12.44s3.91,7.74,7.82,10.54c.39,.28,.92,.27,1.31-.01,1.96-1.45,2.1-1.86,2.1-1.86,0,0-3.8-1.82-9.5-9.58-1.73,.41-1.73,.91-1.73,.91Z"/>
|
||||
<path d="M109.23,5.22h3.14s.24,10.69-8.85,17.49c-.4,.3-.93,.37-1.39,.17-.52-.23-.99-.57-.99-.57,0,0,7.02-4.62,8.09-17.09Z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path d="M4.91,6.25c.18,0,.74-.6,.87-.71,.17,0,.65-.53,.79-.65,.32-.26,.63-.52,.95-.78,.56-.46,1.12-.92,1.69-1.38,.15,0,.72,1.25,.84,1.45,.45,.78,.9,1.56,1.35,2.35l-1.69,1.36c-1.6-.55-3.19-1.1-4.79-1.65Z"/>
|
||||
<path d="M13.06,5.43c-.5-1.29,.38-3.57,1.27-4.51C15.49-.3,17.51,0,19.03,.14c0,0-2.48,2.93-3.55,5.08-.82-.01-1.63,.02-2.42,.21Z"/>
|
||||
<path d="M17.66,4.96L25.6,.8s2.68,1.49,3.63,2.72c-1.4,.41-9.1,2.6-9.37,2.77-.88-.55-2.2-1.33-2.2-1.33Z"/>
|
||||
<polygon points="20.85 7.78 31.75 7.86 31.79 12.24 20.74 9.98 20.85 7.78"/>
|
||||
<path d="M19.56,13.33c-.17,0-.29,.12-.57,.33-.62,.47-1.24,.94-1.86,1.41,1.64,1.94,3.21,3.94,4.81,5.91,.62,.77,1.23,1.55,1.87,2.3,.97-.59,1.7-1.47,2.6-2.15,.32-.25,1.43-.76,1.45-1.22,0-.13-.86-.71-.99-.82-.42-.34-.84-.69-1.26-1.03-1.49-1.22-2.97-2.43-4.47-3.63-.4-.32-.81-.71-1.25-.98-.14-.08-.24-.12-.32-.13Z"/>
|
||||
<path d="M14.11,26.46l-.16-17.73c0-.06-2.36,.1-2.45,.11-1.79,.15-3.65,.33-5.45,.55-1.43,.18-3.03,.26-4.27,1.12C.46,11.43-.13,13.17,.02,14.73l.17,.55s3.27-1.55,3.58-1.7c.63-.3,1.26-.58,1.9-.84,.54-.22,1.32-.65,1.9-.64-.17,.37-.57,.73-.84,1.05l-1.51,1.81-3.24,3.89c-.48,.57-1.02,.92-.79,1.69,.43,1.47,1.54,2.63,2.77,3.5,1.21-1.91,4.39-7.62,6.15-10.35l-.89,12.76h4.89Z"/>
|
||||
</g>
|
||||
<path d="M46.69,9.69v-1.09h-3.34v-3.7h-2.81V13.08h-2.44v7.1h8.57v-5.93c0-.61-.5-1.11-1.11-1.11h-2.21v-3.45h3.34Zm-3.32,4.42c.26,0,.46,.21,.46,.47v4.22c0,.26-.2,.46-.46,.46h-1.89c-.26,0-.47-.2-.47-.46v-4.22c0-.26,.21-.47,.47-.47h1.89Z"/>
|
||||
<path d="M47.63,4.92h2.83l3.11,16.59h3.2v1.19h-3.69c-.88,0-1.7-.42-2.22-1.13l-.05-.07c-.15-.21-.25-.44-.3-.69l-2.88-15.89Z"/>
|
||||
<path d="M46.25,11.17s3.66,.11,8.06-1.4c.4-.01,.8-.01,1.2,0,.2,0,.4,0,.59,0,.1,0,.2,0,.3,0,.09,0,.18-.01,.27,0,.07,.01,.13,.05,.16,.12,.01,.04,0,.06-.03,.08-.55,.41-1.13,.76-1.78,1-.02,0-.04,.01-.06,.02-.23,.08-.46,.15-.69,.21-.32,.09-.64,.16-.96,.23-.42,.09-.85,.16-1.27,.23-.54,.08-1.07,.15-1.61,.21-.66,.07-1.32,.12-1.99,.17-.34,.02-.68,.04-1.03,.06-.1,0-1.1,.06-1.1,.04-.11-.83-.06-.96-.06-.96Z"/>
|
||||
<polygon points="51.95 6.57 53.16 5.28 55.75 6.93 54.1 8.96 51.95 6.57"/>
|
||||
<path d="M53.99,12.13l2.92,1.18s-3.85,7.29-10.4,9.3c-1.16-1.13-1.27-1.51-1.27-1.51,0,0,4.76-2.01,8.75-8.97Z"/>
|
||||
<path d="M61.29,4.5l1.16,.36,1.99,.63s-3.52,5.89-3.99,6.11-1.84-.61-1.84-.61l2.68-6.5Z"/>
|
||||
<path d="M62.9,8.85c-.07,.01-.05,2.52-.06,2.78-.11,3.63-.83,7.26-3.6,9.85-.04,.04-.31,.33-.39,.25l1.13,1.07s3.29-1.64,5.12-6.3c.85-2.17,1.49-5.04,1.18-7.98,0,0-3.39,.33-3.39,.33Z"/>
|
||||
<path d="M67.33,21.71l2.48-1.6s-3.25-3.77-4.65-3.61-.77,1.32-.77,1.32l2.94,3.88Z"/>
|
||||
<rect x="59.13" y="13.28" width="9.91" height="1.29"/>
|
||||
<rect x="62.22" y="7.67" width="6.47" height="1.21"/>
|
||||
<path d="M76.24,6.59h-6.73v14.76h8.45V8.31c0-.95-.77-1.72-1.72-1.72Zm-1.14,13.02c0,.23-.18,.42-.41,.42h-1.9c-.23,0-.41-.19-.41-.42V8.11c0-.23,.18-.41,.41-.41h1.9c.23,0,.41,.18,.41,.41v11.5Z"/>
|
||||
<rect x="88.14" y="12.65" width="2.81" height="2.64"/>
|
||||
<path d="M122.59,8.83h6.72v.4c0,.53-.43,.97-.97,.97h-5.75v-1.36h0Z"/>
|
||||
<path d="M124.65,4.9h2.52V22.22c0,.33-.27,.6-.6,.6h-1.31c-.33,0-.6-.27-.6-.6V4.9h0Z"/>
|
||||
<rect x="129.08" y="6.29" width="12.55" height="1.03"/>
|
||||
<rect x="144.94" y="5.74" width="9.72" height="1.03"/>
|
||||
<rect x="144.16" y="10.2" width="11.03" height="1.03"/>
|
||||
<rect x="146.47" y="17.28" width="13.48" height="1.03"/>
|
||||
<path d="M144.02,20.69h18.37v.45c0,.43-.35,.79-.79,.79h-17.58v-1.24h0Z"/>
|
||||
<rect x="129.08" y="17.34" width="12.55" height="1.03"/>
|
||||
<path d="M131.16,4.89l.58,4.47c0,.06,.07,.09,.12,.07l2.4-.88c.06-.02,.09-.09,.07-.15l-1.29-3.51h-1.87Z"/>
|
||||
<path d="M136.51,4.9l-.07,1.44-.54,2.41,1.13,.7s2.31-1.43,2.3-4.56h-2.82Z"/>
|
||||
<path d="M133.33,15.84s.72,3.52-4.57,6.06c.11,.83,.33,.83,1.1,.72s3.36-.83,5.28-3.14,1.27-3.74,1.27-3.74l-3.08,.11Z"/>
|
||||
<path d="M135.92,17.06s1.27,3.19,5.39,3.69c.44,.11-.94,2.15-.94,2.15,0,0-3.69-1.05-5.61-4.46,.72-.61,1.16-1.38,1.16-1.38Z"/>
|
||||
<path d="M124.71,10.09s-1.5,3.33-2.66,5.37c.22,2.53,.66,2.92,.66,2.92l2.59-3.52-.28-4.84-.32,.08Z"/>
|
||||
<path d="M126.62,13.37l1.82,1.93,1.6-1.27s-2.97-3.08-3.36-2.81-.06,2.15-.06,2.15Z"/>
|
||||
<path d="M139.53,9.76h-9.6v6.19h10.87v-4.92c0-.7-.57-1.27-1.27-1.27Zm-1.26,4.67c0,.31-.26,.56-.57,.56h-5.13c-.32-.01-.32-.25-.32-.56v-.75c0-.31,.01-.58,.33-.58h5.35c.31,0,.34,.27,.34,.58v.75Zm0-2.55c0,.31-.26,.57-.57,.57h-5.17c-.32,0-.28-.26-.28-.57v-.71c-.01-.32,0-.56,.31-.56l5.38-.02c.31,0,.32,.26,.32,.58v.71Z"/>
|
||||
<path d="M146.55,6.43s.58,5.92-2.5,9.06c.63,.69,1.07,.99,1.07,.99,0,0,5.06-3.91,4.4-10.05-.77-.55-2.97,0-2.97,0Z"/>
|
||||
<polygon points="150.87 6.41 150.87 15.51 153.67 15.51 153.67 6.35 150.87 6.41"/>
|
||||
<path d="M162.4,4.92V13.59c0,1.28-1.04,2.31-2.31,2.31h-2.56v-1.03h2.07V4.99l2.8-.07Z"/>
|
||||
<path d="M155.68,6.42v6.25c0,.25,.2,.45,.45,.45h1.91c.25,0,.45-.2,.45-.45V6.39c0-.25-.21-.45-.46-.45l-1.91,.03c-.25,0-.44,.2-.44,.45Z"/>
|
||||
<rect x="151.77" y="15.47" width="3.01" height="5.33"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.5 KiB |
28
chat_web_front/src/assets/images/operates/title.svg
Normal file
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="_图层_2" data-name="图层 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 31.79 46.77">
|
||||
<g id="_图层_1-2" data-name="图层 1">
|
||||
<g>
|
||||
<g fill="#636E77">
|
||||
<path d="M9.21,2.74c.13,.23,.72,1.25,.84,1.45,.45,.78,.9,1.56,1.35,2.35l-1.69,1.36c-1.6-.55-3.19-1.1-4.79-1.65"/>
|
||||
<path d="M13.06,5.43c-.5-1.29,.38-3.57,1.27-4.51C15.49-.3,17.51,0,19.03,.14c0,0-2.48,2.93-3.55,5.08-.82-.01-1.63,.02-2.42,.21Z"/>
|
||||
<path d="M17.66,4.96L25.6,.8s2.68,1.49,3.63,2.72c-1.4,.41-9.1,2.6-9.37,2.77-.88-.55-2.2-1.33-2.2-1.33Z"/>
|
||||
<polygon points="20.85 7.78 31.75 7.86 31.79 12.24 20.74 9.98 20.85 7.78"/>
|
||||
<path d="M19.56,13.33c-.17,0-.29,.12-.57,.33-.62,.47-1.24,.94-1.86,1.41,1.64,1.94,3.21,3.94,4.81,5.91,.62,.77,1.23,1.55,1.87,2.3,.97-.59,1.7-1.47,2.6-2.15,.32-.25,1.43-.76,1.45-1.22,0-.13-.86-.71-.99-.82-.42-.34-.84-.69-1.26-1.03-1.49-1.22-2.97-2.43-4.47-3.63-.4-.32-.81-.71-1.25-.98-.14-.08-.24-.12-.32-.13Z"/>
|
||||
<path fill="#FF2500" d="M14.11,26.46s-.16-17.73-.16-17.73c0-.06-2.36,.1-2.45,.11-1.79,.15-3.65,.33-5.45,.55-1.43,.18-3.03,.26-4.27,1.12C.46,11.43-.13,13.17,.02,14.73l.17,.55s3.27-1.55,3.58-1.7c.63-.3,1.26-.58,1.9-.84,.54-.22,1.32-.65,1.9-.64-.17,.37-.57,.73-.84,1.05l-1.51,1.81-3.24,3.89c-.48,.57-1.02,.92-.79,1.69,.43,1.47,1.54,2.63,2.77,3.5,1.21-1.91,4.39-7.62,6.15-10.35l-.89,12.76h4.89Z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path d="M7.06,36.55v-.85h-2.6v-2.88H2.27v6.37H.37v5.53H7.04v-4.62c0-.48-.39-.86-.86-.86h-1.72v-2.69h2.6Zm-2.59,3.44c.2,0,.36,.16,.36,.37v3.29c0,.2-.16,.36-.36,.36h-1.47c-.2,0-.37-.16-.37-.36v-3.29c0-.2,.16-.37,.37-.37h1.47Z"/>
|
||||
<path d="M7.79,32.83h2.21l2.42,12.92h2.49v.93h-2.88c-.68,0-1.33-.33-1.73-.88l-.04-.05c-.12-.16-.2-.35-.23-.54l-2.24-12.38Z"/>
|
||||
<path d="M6.72,37.7s2.85,.09,6.28-1.09c.31,0,.62,0,.94,0,.15,0,.31,0,.46,0,.08,0,.15,0,.23,0,.07,0,.14,0,.21,0,.06,.01,.1,.04,.13,.09,.01,.03,0,.04-.03,.06-.43,.32-.88,.59-1.39,.78-.02,0-.03,.01-.05,.02-.18,.06-.36,.11-.54,.16-.25,.07-.5,.13-.75,.18-.33,.07-.66,.13-.99,.18-.42,.06-.84,.12-1.26,.16-.52,.05-1.03,.1-1.55,.13-.27,.02-.53,.03-.8,.05-.07,0-.85,.05-.86,.03-.09-.64-.04-.75-.04-.75Z"/>
|
||||
<polygon points="11.16 34.12 12.1 33.11 14.12 34.39 12.83 35.98 11.16 34.12"/>
|
||||
<path d="M12.75,38.45l2.27,.92s-3,5.68-8.11,7.25c-.9-.88-.99-1.18-.99-1.18,0,0,3.71-1.57,6.82-6.99Z"/>
|
||||
<path d="M18.44,32.5l.91,.28,1.55,.49s-2.74,4.59-3.11,4.76-1.44-.47-1.44-.47l2.09-5.06Z"/>
|
||||
<path d="M19.69,35.9c-.06,0-.04,1.97-.05,2.16-.09,2.83-.64,5.66-2.8,7.68-.03,.03-.24,.25-.3,.2l.88,.84s2.56-1.28,3.99-4.91c.66-1.69,1.16-3.92,.92-6.22,0,0-2.64,.26-2.64,.26Z"/>
|
||||
<path d="M23.15,45.91l1.93-1.24s-2.53-2.94-3.62-2.81-.6,1.03-.6,1.03l2.29,3.02Z"/>
|
||||
<rect x="16.76" y="39.35" width="7.72" height="1.01"/>
|
||||
<rect x="19.16" y="34.97" width="5.04" height=".94"/>
|
||||
<path d="M30.08,34.13h-5.24v11.5h6.58v-10.16c0-.74-.6-1.34-1.34-1.34Zm-.89,10.14c0,.18-.14,.33-.32,.33h-1.48c-.18,0-.32-.15-.32-.33v-8.96c0-.18,.14-.32,.32-.32h1.48c.18,0,.32,.14,.32,.32v8.96Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
@@ -3,7 +3,7 @@
|
||||
<div class="operates">
|
||||
<img
|
||||
@click="goChat"
|
||||
src="../assets/images/operates/title.png"
|
||||
src="../assets/images/operates/title.svg"
|
||||
class="titleImg pointer"
|
||||
alt=""
|
||||
/>
|
||||
@@ -14,14 +14,39 @@
|
||||
<span :style="{ fontFamily:item.operateItemed ? 'PingFangSC-Blod' : '' }">{{item.name}}</span>
|
||||
</div>
|
||||
<el-divider />
|
||||
<div class="externalLinksBlock">
|
||||
<div
|
||||
v-for="link in externalLinks"
|
||||
:key="link.name"
|
||||
class="extItem"
|
||||
:title="link.name + ' (新窗口打开)'"
|
||||
@click="openExtLink(link.url)"
|
||||
>
|
||||
<span class="extName">{{ link.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<el-divider />
|
||||
<div class="operateBottom">
|
||||
<img src="../assets/images/operates/user.png" class="userImg" alt="" />
|
||||
<img
|
||||
src="../assets/images/operates/quit.png"
|
||||
@click="quit"
|
||||
class="quit"
|
||||
alt=""
|
||||
/>
|
||||
<el-popover placement="right" :width="140" trigger="click">
|
||||
<template #reference>
|
||||
<img src="../assets/images/operates/user.png" class="userImg pointer" alt="" />
|
||||
</template>
|
||||
<div class="userMenu">
|
||||
<div class="userMenuItem" @click="goProfile">
|
||||
<el-icon><User /></el-icon>
|
||||
<span>个人中心</span>
|
||||
</div>
|
||||
<div v-if="isAdmin" class="userMenuItem" @click="goUserManage">
|
||||
<el-icon><Setting /></el-icon>
|
||||
<span>用户管理</span>
|
||||
</div>
|
||||
<el-divider style="margin: 6px 0" />
|
||||
<div class="userMenuItem danger" @click="quit">
|
||||
<el-icon><SwitchButton /></el-icon>
|
||||
<span>退出登录</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-popover>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -29,10 +54,12 @@
|
||||
|
||||
<script setup lang='ts'>
|
||||
import { useAuthStore } from "@/store";
|
||||
import { reactive, watch } from "vue";
|
||||
import { reactive, watch, computed } from "vue";
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
import { User, Setting, SwitchButton, TopRight } from "@element-plus/icons-vue";
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const isAdmin = computed(() => authStore.session?.admind === true);
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const menuList = reactive([
|
||||
@@ -74,11 +101,30 @@ const menuList = reactive([
|
||||
|
||||
]);
|
||||
|
||||
// 外部系统快捷入口(侧栏底部)
|
||||
const externalLinks = [
|
||||
{ name: '钢研门户', url: 'http://192.168.203.15:8030/zk/index' },
|
||||
{ name: '项目管理', url: 'http://192.168.203.23:8080/' },
|
||||
{ name: '数据中心', url: 'http://192.168.203.21:8600/kd' },
|
||||
];
|
||||
|
||||
const openExtLink = (url: string) => {
|
||||
window.open(url, '_blank');
|
||||
};
|
||||
|
||||
const quit = () => {
|
||||
authStore.removeToken();
|
||||
router.push("/login");
|
||||
};
|
||||
|
||||
const goProfile = () => {
|
||||
router.push("/profile");
|
||||
};
|
||||
|
||||
const goUserManage = () => {
|
||||
router.push("/userManage");
|
||||
};
|
||||
|
||||
/**
|
||||
* 返回首页
|
||||
*/
|
||||
@@ -135,8 +181,9 @@ watch(
|
||||
margin-left: 20px;
|
||||
.operates {
|
||||
width: 76px;
|
||||
height: 657px;
|
||||
max-height: 100vh;
|
||||
min-height: 600px;
|
||||
max-height: calc(100vh - 40px);
|
||||
overflow-y: auto;
|
||||
background: #fafbff;
|
||||
box-shadow: 0px 0px 8px 1px rgba(180, 189, 221, 0.56);
|
||||
position: absolute;
|
||||
@@ -145,7 +192,8 @@ watch(
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
justify-content: flex-start;
|
||||
gap: 55px;
|
||||
z-index: 999;
|
||||
.titleImg {
|
||||
width: 44px;
|
||||
@@ -245,6 +293,29 @@ watch(
|
||||
background-size: 100%;
|
||||
}
|
||||
}
|
||||
.externalLinksBlock {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
.extItem {
|
||||
cursor: pointer;
|
||||
color: #606771;
|
||||
padding: 6px 6px;
|
||||
border-radius: 6px;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
transition: all 0.2s;
|
||||
background: rgba(0, 78, 160, 0.05);
|
||||
.extName {
|
||||
font-size: 12px;
|
||||
}
|
||||
&:hover {
|
||||
background: rgba(0, 78, 160, 0.14);
|
||||
color: #004ea0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.operateBottom {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -253,17 +324,33 @@ watch(
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
}
|
||||
.quit {
|
||||
margin-top: 36px;
|
||||
width: 18.85px;
|
||||
height: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.el-divider--horizontal {
|
||||
margin: 0px;
|
||||
margin: -40px;
|
||||
width: 28px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
.userMenu {
|
||||
.userMenuItem {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 12px;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
&:hover {
|
||||
background: #f0f2f5;
|
||||
color: #004ea0;
|
||||
}
|
||||
&.danger:hover {
|
||||
color: #f56c6c;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,3 +1,4 @@
|
||||
export const PROJECT_PROPERTIES = {
|
||||
projectName: '知冶模型'
|
||||
projectName: '战知',
|
||||
fullProjectName: '中国产业基础能力发展战略研究院战知大模型'
|
||||
}
|
||||
@@ -28,8 +28,8 @@ export default {
|
||||
success: '操作成功',
|
||||
failed: '操作失败',
|
||||
unauthorizedTips: '未经授权,请先进行验证。',
|
||||
logo: '知冶大模型',
|
||||
siteInfo: '© 2023 知冶模型 _ 浪潮软件科技有限公司',
|
||||
logo: '战知',
|
||||
siteInfo: '© 2026 战知 _ 浪潮软件科技有限公司',
|
||||
userXieyi: '用户协议',
|
||||
privacyZhengce: '隐私政策',
|
||||
cueWord1: '您好,我是你的报告撰写智能小助手,快来告诉我你的需要吧~',
|
||||
@@ -46,9 +46,9 @@ export default {
|
||||
noChatFlow: '无对话历史'
|
||||
},
|
||||
login: {
|
||||
title: '知海无涯搜万象',
|
||||
title2:'冶技卓越析微尘',
|
||||
subTitle: '支持多轮对话,集成冶金专业搜索,具备自建知识库、内容创作、信息归纳总结等能力',
|
||||
title: '聚尖端之力,创多维平台',
|
||||
title2:'',
|
||||
subTitle: '聚合科技动能,扩展创新疆界,引领行业跃迁升级',
|
||||
quickStart: '快速开始',
|
||||
telPlaceholder: '请输入手机号',
|
||||
passwordPlaceholder: '请输入密码',
|
||||
|
||||
@@ -39,6 +39,16 @@ const routes: RouteRecordRaw[] = [
|
||||
name: 'Application',
|
||||
component: () => import('@/views/applications/index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/profile',
|
||||
name: 'Profile',
|
||||
component: () => import('@/views/profile/index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/userManage',
|
||||
name: 'UserManage',
|
||||
component: () => import('@/views/userManage/index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/translate',
|
||||
name: 'Translate',
|
||||
|
||||
@@ -12,6 +12,7 @@ interface SessionResponse {
|
||||
password: string | null
|
||||
context: boolean | false
|
||||
num: number | 0
|
||||
admind: boolean | false
|
||||
}
|
||||
|
||||
export interface AuthState {
|
||||
|
||||
@@ -1,422 +1,221 @@
|
||||
<template>
|
||||
<div class="tools-page">
|
||||
<div class="tools-container">
|
||||
<div class="top-level" ref="topLevel">
|
||||
<template v-for="(item, index) in topSpecies">
|
||||
<div class="species-name" :class="activeTopSpecieIndex === index ? 'active' : ''"
|
||||
@click="changeTopSpecies(index)">{{ item.name }}
|
||||
</div>
|
||||
</template>
|
||||
<div class="tools-header">
|
||||
<span class="tools-title">实用工具</span>
|
||||
</div>
|
||||
|
||||
<div class="second-level" ref="secondLevel">
|
||||
<template v-for="(item, index) in secondSpecies">
|
||||
<div class="species-name" :class="activeSecondSpecieIndex === index ? 'active' : ''"
|
||||
@click="changeSecondSpecies(index)">{{ item.name }}
|
||||
<div v-for="category in toolCategories" :key="category.name" class="category-section">
|
||||
<div class="category-name">{{ category.name }}</div>
|
||||
<div class="tools-list">
|
||||
<div v-for="tool in category.tools" :key="tool.id" class="tool-item"
|
||||
:class="{ disabled: !tool.enabled }" @click="openTool(tool)">
|
||||
<div class="tool-icon">
|
||||
<img v-if="tool.logoImg" :src="tool.logoImg" class="tool-logo-img" alt="">
|
||||
<span v-else>{{ tool.icon }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="tools-info" :style="toolsHeight != '' ? 'max-height:'+toolsHeight:''">
|
||||
<template v-for="tool in tools">
|
||||
<div class="level-name" v-if="tool.topLevel">{{ tool.topLevel}}</div>
|
||||
<div class="tools">
|
||||
<template v-for="item in tool.data">
|
||||
<div class="tool-item" @click="goToLink(item)">
|
||||
<img :src="getToolsImg(item.imgUrl)" alt="">
|
||||
<div class="tool-info">
|
||||
<div class="top">
|
||||
<div class="name">{{ item.name }}</div>
|
||||
<div class="type" v-if="item.tag && activeTopSpecieIndex === 0">{{ item.tag }}</div>
|
||||
<div class="tool-top">
|
||||
<div class="tool-name">{{ tool.name }}</div>
|
||||
<div class="tool-badge" v-if="!tool.enabled">即将上线</div>
|
||||
</div>
|
||||
<div class="bottom">
|
||||
<div class="desc" :title="item.description"> {{ item.description }}</div>
|
||||
<img src="@/assets/images/applications/right.png" alt="">
|
||||
<div class="tool-desc">{{ tool.description }}</div>
|
||||
</div>
|
||||
<img v-if="tool.enabled" src="@/assets/images/applications/right.png" class="tool-arrow" alt="">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<page-footer></page-footer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {onMounted, onUnmounted, reactive, ref} from "vue";
|
||||
import {withLoading} from "@/utils/loading";
|
||||
import {getConfig, getTagConfig, viewUrl} from "@/api";
|
||||
import PageFooter from "@/components/pageFooter.vue";
|
||||
import {ElMessage} from "element-plus";
|
||||
import { reactive } from "vue";
|
||||
import stirlingLogo from "@/assets/images/applications/stirling-pdf.svg";
|
||||
import excalidrawLogo from "@/assets/images/applications/excalidraw.svg";
|
||||
import trwebocrLogo from "@/assets/images/applications/trwebocr.png";
|
||||
import pptistLogo from "@/assets/images/applications/pptist.png";
|
||||
import libretranslateLogo from "@/assets/images/applications/libretranslate.svg";
|
||||
|
||||
// 后端接口的上下文
|
||||
const baseURL = import.meta.env.VITE_GLOB_API_CTX;
|
||||
interface Tool {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
description: string;
|
||||
url?: string;
|
||||
port?: number;
|
||||
path?: string;
|
||||
logoImg?: string;
|
||||
enabled: boolean;
|
||||
type: 'newtab' | 'route';
|
||||
}
|
||||
|
||||
// 请求参数类型
|
||||
type RequestParams = {
|
||||
type?: string;
|
||||
sort?: string;
|
||||
tag?: string;
|
||||
className?: string;
|
||||
interface Category {
|
||||
name: string;
|
||||
tools: Tool[];
|
||||
}
|
||||
|
||||
const toolCategories = reactive<Category[]>([
|
||||
{
|
||||
name: '📄 文档处理',
|
||||
tools: [
|
||||
{ id: 'stirling-pdf', name: 'Stirling PDF', icon: '', logoImg: stirlingLogo, description: 'PDF 合并、拆分、压缩、转换、加水印、OCR 识别等 30+ 功能', path: '/pdf/', enabled: true, type: 'newtab' },
|
||||
{ id: 'trwebocr', name: '图片转文字 OCR', icon: '', logoImg: trwebocrLogo, description: '中文离线 OCR,识别图片中的文字和表格', port: 18083, enabled: true, type: 'newtab' },
|
||||
{ id: 'libretranslate', name: '智能翻译', icon: '', logoImg: libretranslateLogo, description: '中英互译,支持文本和文件翻译,数据不出内网', path: '/translate/', enabled: true, type: 'newtab' },
|
||||
]
|
||||
},
|
||||
{
|
||||
name: '🖼️ 图片处理',
|
||||
tools: [
|
||||
{ id: 'imgcompress', name: '图片压缩转换', icon: '🗜️', description: '图片压缩、格式转换、批量处理、AI 智能抠图去背景', path: '/imgcompress/', enabled: true, type: 'newtab' },
|
||||
{ id: 'lama-cleaner', name: 'AI 图像擦除', icon: '🧹', description: 'AI 智能擦除图片中的水印、路人、瑕疵,自动修复画面', path: '/lama/', enabled: true, type: 'newtab' },
|
||||
{ id: 'webp2jpg', name: '图片格式转换', icon: '🔄', description: '支持 PSD/HEIC/WebP/PNG/JPG 等格式批量互转,纯浏览器处理不上传', path: '/webp2jpg/', enabled: true, type: 'newtab' },
|
||||
]
|
||||
},
|
||||
{
|
||||
name: '🎨 创作绘图',
|
||||
tools: [
|
||||
{ id: 'excalidraw', name: 'Excalidraw', icon: '', logoImg: excalidrawLogo, description: '手绘风格白板,绘制流程图、架构图、示意图', path: '/draw/', enabled: true, type: 'newtab' },
|
||||
{ id: 'pptist', name: 'AI PPT 编辑器', icon: '', logoImg: pptistLogo, description: 'AI 生成 PPT 大纲和演示文稿,在线编辑演示', path: '/ppt/', enabled: true, type: 'newtab' },
|
||||
]
|
||||
},
|
||||
{
|
||||
name: '📝 科研写作',
|
||||
tools: [
|
||||
{ id: 'overleaf', name: 'LaTeX 论文编辑器', icon: '📐', description: '在线 LaTeX 编辑器,支持多人协作撰写论文,实时编译预览', path: '/overleaf/', enabled: true, type: 'newtab' },
|
||||
{ id: 'latex-formula', name: 'LaTeX 公式编辑器', icon: '∑', description: '在线编辑数学公式,支持希腊字母、矩阵、积分等,可复制公式或图片', path: '/latex/', enabled: true, type: 'newtab' },
|
||||
]
|
||||
},
|
||||
]);
|
||||
|
||||
const TOOL_PORT = 18000;
|
||||
|
||||
const openTool = (tool: Tool) => {
|
||||
if (!tool.enabled) return;
|
||||
let url = '';
|
||||
if (tool.path) {
|
||||
url = `${window.location.protocol}//${window.location.hostname}:${TOOL_PORT}${tool.path}`;
|
||||
} else if (tool.port) {
|
||||
url = `${window.location.protocol}//${window.location.hostname}:${tool.port}`;
|
||||
}
|
||||
if (url) {
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
};
|
||||
|
||||
// 一级导航
|
||||
const topSpecies = reactive<any[]>([]);
|
||||
const activeTopSpecieIndex = ref<number>(0);
|
||||
|
||||
// 二级导航
|
||||
const secondSpecies = reactive<any[]>([]);
|
||||
const activeSecondSpecieIndex = ref<number>(-1);
|
||||
|
||||
// 工具
|
||||
type ToolInfo = {
|
||||
imgUrl: string,
|
||||
name: string,
|
||||
description: string,
|
||||
tag: string
|
||||
}
|
||||
type Tool = {
|
||||
topLevel?: string;
|
||||
data: ToolInfo[];
|
||||
}
|
||||
const tools = reactive<Tool[]>([]);
|
||||
|
||||
// 工具模块高度自适应相关
|
||||
const topLevel = ref<any>(null);
|
||||
const secondLevel = ref<any>(null);
|
||||
const toolsHeight = ref('');
|
||||
|
||||
/**
|
||||
* 获取工具广场信息
|
||||
* 适用于获取一级导航,二级导航,以及实用工具下除项目管理、知识管理、研究实施、运营推广外的内容
|
||||
* @param params
|
||||
*/
|
||||
const getConfigList = async (params: RequestParams) => {
|
||||
try {
|
||||
let res = await withLoading(getConfig)(params);
|
||||
return res.code === 200 && res.data ? res.data : [];
|
||||
} catch (error: any) {
|
||||
ElMessage.error(error && error.message ? error.message : '未知错误');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取工具广场信息
|
||||
* 适用于获取实用工具下除项目管理、知识管理、研究实施、运营推广的内容
|
||||
* @param params
|
||||
*/
|
||||
const getTagConfigList = async (params: RequestParams) => {
|
||||
try {
|
||||
let res = await withLoading(getTagConfig)(params);
|
||||
return res.code === 200 && res.data ? res.data : [];
|
||||
} catch (error: any) {
|
||||
ElMessage.error(error && error.message ? error.message : '未知错误');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一级导航
|
||||
*/
|
||||
const getTopSpecies = async () => {
|
||||
topSpecies.length = 0;
|
||||
let params: RequestParams = {
|
||||
type: "广场",
|
||||
sort: '广场',
|
||||
tag: ''
|
||||
}
|
||||
topSpecies.push(...await getConfigList(params));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取二级导航
|
||||
*/
|
||||
const getSecondSpecies = async () => {
|
||||
secondSpecies.length = 0;
|
||||
if (activeTopSpecieIndex.value === 0) {
|
||||
let params: RequestParams = {
|
||||
type: "广场",
|
||||
sort: topSpecies[activeTopSpecieIndex.value].value,
|
||||
tag: 'tag'
|
||||
}
|
||||
secondSpecies.push(...await getConfigList(params));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取工具
|
||||
*/
|
||||
const getTools = async () => {
|
||||
tools.length = 0;
|
||||
|
||||
if (activeTopSpecieIndex.value === 0 && activeSecondSpecieIndex.value === 0) {
|
||||
await getTagToolList();
|
||||
return;
|
||||
}
|
||||
|
||||
await getToolList();
|
||||
}
|
||||
const getTagToolList = async () => {
|
||||
let params: RequestParams = {
|
||||
className: secondSpecies[activeSecondSpecieIndex.value].value,
|
||||
}
|
||||
tools.push(...await getTagConfigList(params));
|
||||
}
|
||||
const getToolList = async () => {
|
||||
let tag = '';
|
||||
if (activeSecondSpecieIndex.value === 0) {
|
||||
tag = "NoTag";
|
||||
} else if (activeSecondSpecieIndex.value != -1) {
|
||||
tag = secondSpecies[activeSecondSpecieIndex.value].value;
|
||||
}
|
||||
let params: RequestParams = {
|
||||
type: "广场",
|
||||
sort: topSpecies[activeTopSpecieIndex.value].value,
|
||||
tag: tag
|
||||
}
|
||||
let data = await getConfigList(params);
|
||||
tools.push({ data: data})
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换一级导航
|
||||
*/
|
||||
const changeTopSpecies = async (index: number) => {
|
||||
activeTopSpecieIndex.value = index;
|
||||
if (activeTopSpecieIndex.value === 0) {
|
||||
activeSecondSpecieIndex.value = 0;
|
||||
} else {
|
||||
activeSecondSpecieIndex.value = -1;
|
||||
}
|
||||
await getSecondSpecies();
|
||||
await getTools();
|
||||
getToolsHeight();
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换二级导航
|
||||
* @param index
|
||||
*/
|
||||
const changeSecondSpecies = (index: number) => {
|
||||
if (activeTopSpecieIndex.value !== 0) {
|
||||
return;
|
||||
}
|
||||
activeSecondSpecieIndex.value = index;
|
||||
getTools();
|
||||
}
|
||||
|
||||
/**
|
||||
* 拼接工具图片地址
|
||||
*/
|
||||
const getToolsImg = (imgUrl: string) => {
|
||||
return new URL(baseURL + '/file/' + imgUrl, import.meta.url).href
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具模块高度自适应
|
||||
*/
|
||||
const getToolsHeight = () => {
|
||||
if (activeTopSpecieIndex.value !== 0) {
|
||||
if (topLevel.value && topLevel.value.offsetHeight !== 0) {
|
||||
let otherHeight: number = topLevel.value.offsetHeight + 146;
|
||||
toolsHeight.value = "calc(100vh - " + otherHeight + "px)";
|
||||
}
|
||||
} else {
|
||||
if (topLevel.value && topLevel.value.offsetHeight !== 0 && secondLevel.value && secondLevel.value.offsetHeight !== 0) {
|
||||
let otherHeight: number = topLevel.value.offsetHeight + secondLevel.value.offsetHeight + 176;
|
||||
toolsHeight.value = "calc(100vh - " + otherHeight + "px)";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 链接跳转
|
||||
* @param item
|
||||
*/
|
||||
const goToLink = async (item: any) => {
|
||||
try {
|
||||
let res = await withLoading(viewUrl)(Number(item.id));
|
||||
|
||||
if (res.code === 200) {
|
||||
window.open(item.jumpUrl, '_blank');
|
||||
} else {
|
||||
ElMessage.error(res.msg);
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error(error && error.message ? error.message : '未知错误');
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await getTopSpecies();
|
||||
await changeTopSpecies(0);
|
||||
getToolsHeight();
|
||||
window.addEventListener("resize", getToolsHeight);
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener("resize", getToolsHeight);
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.tools-container {
|
||||
.tools-page {
|
||||
width: 100%;
|
||||
padding: 60px 160px 20px 236px;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
.top-level {
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.species-name {
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
color: #858A94;
|
||||
margin-right: 60px;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.active {
|
||||
color: #004EA0;
|
||||
}
|
||||
}
|
||||
|
||||
.second-level {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 8px;
|
||||
.species-name {
|
||||
width: 89px;
|
||||
height: 36px;
|
||||
text-align: center;
|
||||
line-height: 36px;
|
||||
background: rgba(133, 138, 150, 0.1);
|
||||
border-radius: 18px;
|
||||
border: 1px solid #E6EDFF;
|
||||
margin-right: 16px;
|
||||
font-size: 16px;
|
||||
margin-bottom: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.active {
|
||||
background: #004EA0;
|
||||
color: #FAFBFF;
|
||||
}
|
||||
}
|
||||
|
||||
.tools {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
.tools-container {
|
||||
padding: 24px 40px;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
|
||||
.tools-header {
|
||||
margin-bottom: 20px;
|
||||
.tools-title { font-size: 20px; font-weight: bold; color: #000; }
|
||||
}
|
||||
}
|
||||
|
||||
.category-section {
|
||||
margin-bottom: 24px;
|
||||
|
||||
.category-name {
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 12px;
|
||||
padding-left: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.tools-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.tool-item {
|
||||
display: flex;
|
||||
width: 325px;
|
||||
height: 105px;
|
||||
align-items: center;
|
||||
padding: 14px;
|
||||
background: #E6EDFF;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #E6EDFF;
|
||||
margin-right: 17px;
|
||||
margin-bottom: 20px;
|
||||
padding: 16px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
img {
|
||||
width: 73px;
|
||||
height: 73px;
|
||||
&:hover:not(.disabled) {
|
||||
border-color: #004EA0;
|
||||
.tool-name { color: #004EA0; }
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.55;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.tool-icon {
|
||||
font-size: 24px;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
flex-shrink: 0;
|
||||
|
||||
.tool-logo-img { width: 30px; height: 30px; object-fit: contain; }
|
||||
}
|
||||
|
||||
.tool-info {
|
||||
height: 73px;
|
||||
margin-left: 12px;
|
||||
flex: 1;
|
||||
margin-left: 10px;
|
||||
min-width: 0;
|
||||
|
||||
.top {
|
||||
.tool-top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 14px;
|
||||
|
||||
.name {
|
||||
width: 128px;
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
color: #000000;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.type {
|
||||
width: 64px;
|
||||
height: 23px;
|
||||
line-height: 23px;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
color: #1C73CE;
|
||||
background: rgba(28, 115, 206, 0.11);
|
||||
border-radius: 11px
|
||||
}
|
||||
}
|
||||
|
||||
.bottom {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.desc {
|
||||
width: 185px;
|
||||
height: 37px;
|
||||
.tool-name {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.tool-badge {
|
||||
font-size: 10px;
|
||||
color: #999;
|
||||
background: rgba(255,255,255,0.7);
|
||||
padding: 2px 8px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.tool-desc {
|
||||
font-size: 12px;
|
||||
color: #858A94;
|
||||
display: flex;
|
||||
line-height: 1.4;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
.tool-arrow {
|
||||
width: 7px;
|
||||
height: 14px;
|
||||
margin-left: 16px
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.tools-top-level {
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
color: #000000;
|
||||
}
|
||||
}
|
||||
.tools-info {
|
||||
overflow-y: auto;
|
||||
.level-name {
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
color: #000000;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
color: #B9B9B9;
|
||||
line-height: 30px;
|
||||
text-align: right;
|
||||
font-style: normal;
|
||||
text-transform: none;
|
||||
position: absolute;
|
||||
bottom: 2vh;
|
||||
right: 160px;
|
||||
a {
|
||||
color: inherit; /* 继承父元素的文字颜色 */
|
||||
text-decoration: none; /* 去除下划线 */
|
||||
cursor: pointer;
|
||||
}
|
||||
margin-left: 10px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -20,13 +20,10 @@
|
||||
<div class="chatAll" ref="chatDiv">
|
||||
<div class="content" v-if="!chatStatus">
|
||||
<div class="title">
|
||||
<span class="titleHead">知</span>
|
||||
<span>海无涯</span>
|
||||
<span class="titleHead" style="margin-left: 40px">冶</span>
|
||||
<span>技卓越</span>
|
||||
<span>聚尖端之力,创多维平台</span>
|
||||
</div>
|
||||
<div class="titleInfo">
|
||||
提升信息处理效率,促进科研创新,优化工艺流程,冶金行业AI助手!
|
||||
聚合科技动能,扩展创新疆界,引领行业跃迁升级
|
||||
</div>
|
||||
<div class="operate">
|
||||
<div class="scenario">
|
||||
|
||||
@@ -7,22 +7,16 @@
|
||||
<div class="loginContent">
|
||||
<div class="loginTitle">
|
||||
<div>
|
||||
知海无涯搜万象
|
||||
<br />
|
||||
冶技卓越析微尘
|
||||
聚尖端之力,创多维平台
|
||||
<br />
|
||||
<span class="loginInfo">
|
||||
支持多轮对话,集成冶金专业搜索,具备自建知识库、内容创作、信息归纳总结等能力
|
||||
</span>
|
||||
<br>
|
||||
<span class="loginInfo">
|
||||
已接入DeepSeek-R1满血版
|
||||
聚合科技动能,扩展创新疆界,引领行业跃迁升级
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="loginOperate">
|
||||
<img
|
||||
src="../../assets/images/login/projectLogo2.png"
|
||||
:src="projectLogo2"
|
||||
class="logoImg"
|
||||
alt=""
|
||||
/>
|
||||
@@ -69,8 +63,8 @@
|
||||
</template>
|
||||
<script setup lang='ts'>
|
||||
import { computed, ref, onMounted, reactive } from "vue";
|
||||
import projectLogo from "@/assets/images/login/projectLogo.png";
|
||||
import projectLogo2 from "@/assets/images/login/projectLogo2.png";
|
||||
import projectLogo from "@/assets/images/login/projectLogo-white.svg";
|
||||
import projectLogo2 from "@/assets/images/login/projectLogo.svg";
|
||||
import type { FormInstance, FormRules } from "element-plus";
|
||||
import { fetchVerify } from "@/api";
|
||||
import { useAuthStore } from "@/store";
|
||||
@@ -144,6 +138,7 @@ const handleVerify = async () => {
|
||||
} finally {
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.loginPage {
|
||||
|
||||
210
chat_web_front/src/views/profile/index.vue
Normal file
@@ -0,0 +1,210 @@
|
||||
<template>
|
||||
<div class="profileWrap">
|
||||
<div class="profileCard">
|
||||
<h2 class="profileTitle">个人中心</h2>
|
||||
|
||||
<div class="profileSection">
|
||||
<div class="avatarSection">
|
||||
<el-avatar :size="80" :src="userInfo.avatar || defaultAvatar" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-divider />
|
||||
|
||||
<div class="profileSection">
|
||||
<h3>基本信息</h3>
|
||||
<el-form :model="userForm" label-width="80px" style="max-width: 400px">
|
||||
<el-form-item label="手机号">
|
||||
<el-input :value="userInfo.tel" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="姓名">
|
||||
<el-input v-model="userForm.name" />
|
||||
</el-form-item>
|
||||
<el-form-item label="昵称">
|
||||
<el-input v-model="userForm.nickName" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSaveInfo" :loading="saving">保存</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<el-divider />
|
||||
|
||||
<div class="profileSection">
|
||||
<h3>修改密码</h3>
|
||||
<el-form
|
||||
ref="passwordFormRef"
|
||||
:model="passwordForm"
|
||||
:rules="passwordRules"
|
||||
label-width="80px"
|
||||
style="max-width: 400px"
|
||||
>
|
||||
<el-form-item label="旧密码" prop="oldPassword">
|
||||
<el-input v-model="passwordForm.oldPassword" type="password" show-password />
|
||||
</el-form-item>
|
||||
<el-form-item label="新密码" prop="newPassword">
|
||||
<el-input v-model="passwordForm.newPassword" type="password" show-password />
|
||||
</el-form-item>
|
||||
<el-form-item label="确认密码" prop="confirmPassword">
|
||||
<el-input v-model="passwordForm.confirmPassword" type="password" show-password />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleChangePassword" :loading="changingPwd">
|
||||
修改密码
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { fetchSession, updateUser, updatePassword } from '@/api'
|
||||
import { useAuthStore } from '@/store'
|
||||
import defaultAvatarImg from '@/assets/images/operates/user.png'
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const defaultAvatar = defaultAvatarImg
|
||||
|
||||
const userInfo = reactive({
|
||||
id: 0,
|
||||
tel: '',
|
||||
name: '',
|
||||
nickName: '',
|
||||
avatar: '',
|
||||
})
|
||||
|
||||
const userForm = reactive({
|
||||
name: '',
|
||||
nickName: '',
|
||||
})
|
||||
|
||||
const passwordForm = reactive({
|
||||
oldPassword: '',
|
||||
newPassword: '',
|
||||
confirmPassword: '',
|
||||
})
|
||||
|
||||
const passwordFormRef = ref<FormInstance>()
|
||||
const saving = ref(false)
|
||||
const changingPwd = ref(false)
|
||||
|
||||
const validateConfirm = (rule: any, value: any, callback: any) => {
|
||||
if (value !== passwordForm.newPassword) {
|
||||
callback(new Error('两次输入的密码不一致'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
const passwordRules = reactive<FormRules>({
|
||||
oldPassword: [{ required: true, message: '请输入旧密码', trigger: 'blur' }],
|
||||
newPassword: [
|
||||
{ required: true, message: '请输入新密码', trigger: 'blur' },
|
||||
{ min: 6, message: '密码长度至少6位', trigger: 'blur' },
|
||||
],
|
||||
confirmPassword: [
|
||||
{ required: true, message: '请确认新密码', trigger: 'blur' },
|
||||
{ validator: validateConfirm, trigger: 'blur' },
|
||||
],
|
||||
})
|
||||
|
||||
const loadUserInfo = async () => {
|
||||
const res = await fetchSession<any>()
|
||||
if (res.code === 200) {
|
||||
Object.assign(userInfo, res.data)
|
||||
userForm.name = res.data.name || ''
|
||||
userForm.nickName = res.data.nickName || ''
|
||||
}
|
||||
}
|
||||
|
||||
const handleSaveInfo = async () => {
|
||||
saving.value = true
|
||||
try {
|
||||
const res = await updateUser<any>({
|
||||
name: userForm.name,
|
||||
nickName: userForm.nickName,
|
||||
})
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('信息保存成功')
|
||||
userInfo.name = userForm.name
|
||||
userInfo.nickName = userForm.nickName
|
||||
} else {
|
||||
ElMessage.error(res.msg || '保存失败')
|
||||
}
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleChangePassword = async () => {
|
||||
const valid = await passwordFormRef.value?.validate().catch(() => false)
|
||||
if (!valid) return
|
||||
|
||||
changingPwd.value = true
|
||||
try {
|
||||
const res = await updatePassword<any>({
|
||||
oldPassword: passwordForm.oldPassword,
|
||||
newPassword: passwordForm.newPassword,
|
||||
})
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('密码修改成功,请重新登录')
|
||||
passwordForm.oldPassword = ''
|
||||
passwordForm.newPassword = ''
|
||||
passwordForm.confirmPassword = ''
|
||||
authStore.removeToken()
|
||||
window.location.hash = '#/login'
|
||||
} else {
|
||||
ElMessage.error(res.msg || '密码修改失败')
|
||||
}
|
||||
} finally {
|
||||
changingPwd.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadUserInfo()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.profileWrap {
|
||||
flex: 1;
|
||||
padding: 30px;
|
||||
overflow-y: auto;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.profileCard {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 30px 40px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.profileTitle {
|
||||
font-size: 20px;
|
||||
color: #333;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.profileSection {
|
||||
h3 {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.avatarSection {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
}
|
||||
</style>
|
||||
296
chat_web_front/src/views/userManage/index.vue
Normal file
@@ -0,0 +1,296 @@
|
||||
<template>
|
||||
<div class="userManageWrap">
|
||||
<div class="userManageCard">
|
||||
<div class="headerRow">
|
||||
<h2>用户管理</h2>
|
||||
<el-button type="primary" @click="handleAdd">新增用户</el-button>
|
||||
</div>
|
||||
|
||||
<el-table :data="tableData" v-loading="loading" stripe style="width: 100%">
|
||||
<el-table-column prop="id" label="ID" width="70" />
|
||||
<el-table-column prop="tel" label="手机号" width="140" />
|
||||
<el-table-column prop="name" label="姓名" width="140" />
|
||||
<el-table-column prop="nickName" label="昵称" width="140" />
|
||||
<el-table-column prop="status" label="状态" width="80">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === 1 ? 'success' : 'danger'" size="small">
|
||||
{{ row.status === 1 ? '正常' : '禁用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" width="170" />
|
||||
<el-table-column label="操作" min-width="200" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" size="small" @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button link type="warning" size="small" @click="handleResetPwd(row)">重置密码</el-button>
|
||||
<el-popconfirm title="确定删除该用户?" @confirm="handleDelete(row)">
|
||||
<template #reference>
|
||||
<el-button link type="danger" size="small">删除</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="paginationRow">
|
||||
<el-pagination
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:total="total"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
@size-change="loadUsers"
|
||||
@current-change="loadUsers"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 新增/编辑用户弹窗 -->
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="dialogType === 'add' ? '新增用户' : '编辑用户'"
|
||||
width="460px"
|
||||
destroy-on-close
|
||||
>
|
||||
<el-form
|
||||
ref="userFormRef"
|
||||
:model="userForm"
|
||||
:rules="userRules"
|
||||
label-width="80px"
|
||||
>
|
||||
<el-form-item label="手机号" prop="tel">
|
||||
<el-input v-model="userForm.tel" :disabled="dialogType === 'edit'" />
|
||||
</el-form-item>
|
||||
<el-form-item label="姓名" prop="name">
|
||||
<el-input v-model="userForm.name" />
|
||||
</el-form-item>
|
||||
<el-form-item label="昵称">
|
||||
<el-input v-model="userForm.nickName" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="dialogType === 'add'" label="密码" prop="password">
|
||||
<el-input v-model="userForm.password" type="password" show-password />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit" :loading="submitting">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 重置密码弹窗 -->
|
||||
<el-dialog v-model="resetPwdVisible" title="重置密码" width="400px" destroy-on-close>
|
||||
<el-form ref="resetPwdFormRef" :model="resetPwdForm" :rules="resetPwdRules" label-width="80px">
|
||||
<el-form-item label="用户">
|
||||
<el-input :value="resetPwdForm.tel" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="新密码" prop="newPassword">
|
||||
<el-input v-model="resetPwdForm.newPassword" type="password" show-password />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="resetPwdVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleResetPwdSubmit" :loading="resettingPwd">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { fetchUserPage, createUser, editUser, deleteUser, resetUserPassword } from '@/api'
|
||||
|
||||
const tableData = ref<any[]>([])
|
||||
const loading = ref(false)
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const total = ref(0)
|
||||
|
||||
// 新增/编辑
|
||||
const dialogVisible = ref(false)
|
||||
const dialogType = ref<'add' | 'edit'>('add')
|
||||
const userFormRef = ref<FormInstance>()
|
||||
const submitting = ref(false)
|
||||
const userForm = reactive({
|
||||
id: 0,
|
||||
tel: '',
|
||||
name: '',
|
||||
nickName: '',
|
||||
password: '',
|
||||
})
|
||||
|
||||
const userRules = reactive<FormRules>({
|
||||
tel: [{ required: true, message: '请输入手机号', trigger: 'blur' }],
|
||||
name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
|
||||
password: [
|
||||
{ required: true, message: '请输入密码', trigger: 'blur' },
|
||||
{ min: 6, message: '密码长度至少6位', trigger: 'blur' },
|
||||
],
|
||||
})
|
||||
|
||||
// 重置密码
|
||||
const resetPwdVisible = ref(false)
|
||||
const resetPwdFormRef = ref<FormInstance>()
|
||||
const resettingPwd = ref(false)
|
||||
const resetPwdForm = reactive({
|
||||
id: 0,
|
||||
tel: '',
|
||||
newPassword: '',
|
||||
})
|
||||
|
||||
const resetPwdRules = reactive<FormRules>({
|
||||
newPassword: [
|
||||
{ required: true, message: '请输入新密码', trigger: 'blur' },
|
||||
{ min: 6, message: '密码长度至少6位', trigger: 'blur' },
|
||||
],
|
||||
})
|
||||
|
||||
const loadUsers = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await fetchUserPage<any>({
|
||||
current: currentPage.value,
|
||||
size: pageSize.value,
|
||||
})
|
||||
if (res.code === 200) {
|
||||
tableData.value = res.data.records || []
|
||||
total.value = res.data.total || 0
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.error('加载用户列表失败:', e)
|
||||
ElMessage.error('加载用户列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleAdd = () => {
|
||||
dialogType.value = 'add'
|
||||
userForm.id = 0
|
||||
userForm.tel = ''
|
||||
userForm.name = ''
|
||||
userForm.nickName = ''
|
||||
userForm.password = ''
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
const handleEdit = (row: any) => {
|
||||
dialogType.value = 'edit'
|
||||
userForm.id = row.id
|
||||
userForm.tel = row.tel
|
||||
userForm.name = row.name || ''
|
||||
userForm.nickName = row.nickName || ''
|
||||
userForm.password = ''
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const valid = await userFormRef.value?.validate().catch(() => false)
|
||||
if (!valid) return
|
||||
|
||||
submitting.value = true
|
||||
try {
|
||||
let res: any
|
||||
if (dialogType.value === 'add') {
|
||||
res = await createUser<any>({
|
||||
tel: userForm.tel,
|
||||
name: userForm.name,
|
||||
nickName: userForm.nickName,
|
||||
password: userForm.password,
|
||||
})
|
||||
} else {
|
||||
res = await editUser<any>({
|
||||
id: userForm.id,
|
||||
name: userForm.name,
|
||||
nickName: userForm.nickName,
|
||||
})
|
||||
}
|
||||
if (res.code === 200) {
|
||||
ElMessage.success(dialogType.value === 'add' ? '新增成功' : '编辑成功')
|
||||
dialogVisible.value = false
|
||||
loadUsers()
|
||||
} else {
|
||||
ElMessage.error(res.msg || '操作失败')
|
||||
}
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleResetPwd = (row: any) => {
|
||||
resetPwdForm.id = row.id
|
||||
resetPwdForm.tel = row.tel
|
||||
resetPwdForm.newPassword = ''
|
||||
resetPwdVisible.value = true
|
||||
}
|
||||
|
||||
const handleResetPwdSubmit = async () => {
|
||||
const valid = await resetPwdFormRef.value?.validate().catch(() => false)
|
||||
if (!valid) return
|
||||
|
||||
resettingPwd.value = true
|
||||
try {
|
||||
const res = await resetUserPassword<any>({
|
||||
id: resetPwdForm.id,
|
||||
newPassword: resetPwdForm.newPassword,
|
||||
})
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('密码重置成功')
|
||||
resetPwdVisible.value = false
|
||||
} else {
|
||||
ElMessage.error(res.msg || '重置失败')
|
||||
}
|
||||
} finally {
|
||||
resettingPwd.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleDelete = async (row: any) => {
|
||||
const res = await deleteUser<any>(row.id)
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('删除成功')
|
||||
loadUsers()
|
||||
} else {
|
||||
ElMessage.error(res.msg || '删除失败')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadUsers()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.userManageWrap {
|
||||
flex: 1;
|
||||
padding: 30px;
|
||||
overflow-y: auto;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.userManageCard {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 24px 30px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.headerRow {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
|
||||
h2 {
|
||||
font-size: 20px;
|
||||
color: #333;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.paginationRow {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -35,7 +35,16 @@ export default defineConfig((env) => {
|
||||
[viteEnv.VITE_GLOB_API_CTX]: {
|
||||
target: viteEnv.VITE_GLOB_API_DEV_IP,
|
||||
changeOrigin: true,
|
||||
}
|
||||
},
|
||||
// 工具服务通过 Nginx(:18000) 反代,sub_filter 处理子资源路径
|
||||
'/pdf/': {
|
||||
target: 'http://localhost:18000',
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/draw/': {
|
||||
target: 'http://localhost:18000',
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
build: {
|
||||
|
||||
@@ -94,8 +94,8 @@ DUPLICATE_THRESHOLD = 0.98
|
||||
# 默认搜索引擎。可选:bing, duckduckgo, metaphor
|
||||
# DEFAULT_SEARCH_ENGINE = "duckduckgo"
|
||||
DEFAULT_SEARCH_ENGINE = "duckduckgo" # 本地未部署 KGO 搜索时用 duckduckgo;自建搜索后再改为 kgo
|
||||
kgo_search_url = r"http://127.0.0.1:10326/search/search" # 若部署 KGO 搜索服务可改端口
|
||||
kgo_professional_search_url = r"http://127.0.0.1:8327/search/professionalSearch"
|
||||
kgo_search_url = r"http://192.168.203.21:8326/search/search" # 若部署 KGO 搜索服务可改端口
|
||||
kgo_professional_search_url = r"http://192.168.203.21:8326/search/professionalSearch"
|
||||
|
||||
# 画图接口
|
||||
realistic_url = r"http://127.0.0.1:5000/generate"
|
||||
|
||||
@@ -2518,62 +2518,17 @@ PROMPT_TEMPLATES = {
|
||||
|
||||
PROMPT_ABSTRACT = {
|
||||
"llm_chat": {
|
||||
"policy_standard_chat": {"title": "政策问答",
|
||||
"desc": "提供各行业最新的政策、法规等信息。",
|
||||
"prompt": "您可以向我咨询行业的政策,我将为您提供信息,并给出解读建议。",
|
||||
"type": "行业专业问答"},
|
||||
|
||||
"Topic Recommend Assistant": {"title": "选题推荐",
|
||||
"desc": "聚焦十四五规划、国内前沿会议,提供项目选题推荐服务。",
|
||||
"prompt": "请给我提供一个方向或一个主题,我将为您推荐相关的选题。(默认推荐一个)",
|
||||
"type": "项目选题"},
|
||||
|
||||
"Policy History Assistant": {"title": "政策脉络",
|
||||
"desc": "梳理政策脉络,支撑立项和研究工作。",
|
||||
"prompt": "输入政策的具体名称或提供一个主题,我将为您提供其背后的历史背景和发展脉络。",
|
||||
"type": "政策分析挖掘"},
|
||||
|
||||
# "process_flow": {"title": "工艺流程问答",
|
||||
# "desc": "针对特定工艺的详细流程进行回答。",
|
||||
# "prompt": "您可以向我提问有关产品的产能、产量、进出口、技术经济指标等工艺流程的问题,我将给您提供相关的答案。",
|
||||
# "type": "行业专业问答"},
|
||||
|
||||
"concept_explain": {"title": "术语解释",
|
||||
"desc": "提供各领域专业术语的即时解释。",
|
||||
"prompt": "您可以直接输入任何领域的专业术语,我都将为您进行专业全面的解释",
|
||||
"type": "行业专业问答"},
|
||||
|
||||
"statistic_search": {"title": "智能问数",
|
||||
"desc": "将自然语言智能转换成数据库检索SQL,查询相关数据,并回答问题。",
|
||||
"prompt": "您可以直接问我问题,我将基于全球统计数据库的结构化数据来给您回答。",
|
||||
"type": "行业专业问答"},
|
||||
}
|
||||
,
|
||||
"knowledge_base_chat": {
|
||||
"policy_standard_chat": {"title": "政策问答",
|
||||
"desc": "提供各行业最新的政策、法规等信息。",
|
||||
"prompt": "您可以向我咨询行业的政策,我将为您提供信息,并给出解读建议。",
|
||||
"type": "行业专业问答"},
|
||||
|
||||
"Topic Recommend Assistant": {"title": "选题推荐",
|
||||
"desc": "聚焦十四五规划、国内前沿会议,提供项目选题推荐服务。",
|
||||
"prompt": "请给我提供一个方向或一个主题,我将为您推荐相关的选题。(默认推荐一个)",
|
||||
"type": "项目选题"},
|
||||
|
||||
"Policy History Assistant": {"title": "政策脉络",
|
||||
"desc": "梳理政策脉络,支撑立项和研究工作。",
|
||||
"prompt": "输入政策的具体名称或提供一个主题,我将为您提供其背后的历史背景和发展脉络。",
|
||||
"type": "政策分析挖掘"},
|
||||
|
||||
# "process_flow": {"title": "工艺流程问答",
|
||||
# "desc": "针对特定工艺的详细流程进行回答。",
|
||||
# "prompt": "您可以向我提问有关产品的产能、产量、进出口、技术经济指标等工艺流程的问题,我将给您提供相关的答案。",
|
||||
# "type": "行业专业问答"},
|
||||
|
||||
"concept_explain": {"title": "术语解释",
|
||||
"desc": "提供各领域专业术语的即时解释。",
|
||||
"prompt": "您可以直接输入任何领域的专业术语,我都将为您进行专业全面的解释",
|
||||
"type": "行业专业问答"},
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 确保使用 bash 运行
|
||||
if [ -z "$BASH_VERSION" ]; then
|
||||
exec bash "$0" "$@"
|
||||
fi
|
||||
|
||||
# 颜色定义
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 打印带颜色的消息
|
||||
print_yellow() { printf "${YELLOW}%s${NC}\n" "$1"; }
|
||||
print_green() { printf "${GREEN}%s${NC}\n" "$1"; }
|
||||
print_red() { printf "${RED}%s${NC}\n" "$1"; }
|
||||
|
||||
print_yellow "=== 停止 7861 和 8501 端口服务 ==="
|
||||
|
||||
for port in 7861 8501; do
|
||||
pids=$(lsof -t -i:"$port" 2>/dev/null)
|
||||
if [ -n "$pids" ]; then
|
||||
print_yellow "正在停止端口 $port 的进程: $pids"
|
||||
kill -9 $pids 2>/dev/null
|
||||
print_green "端口 $port 已停止"
|
||||
else
|
||||
echo "端口 $port 无运行中的服务"
|
||||
fi
|
||||
done
|
||||
|
||||
# 也停止所有 startup.py 进程
|
||||
pids=$(ps aux | grep "[p]ython.*startup.py -a" | awk '{print $2}')
|
||||
if [ -n "$pids" ]; then
|
||||
print_yellow "正在停止 startup.py 进程: $pids"
|
||||
kill -9 $pids 2>/dev/null
|
||||
print_green "startup.py 进程已停止"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
print_yellow "=== 启动服务 ==="
|
||||
|
||||
cd /home/gc/gangyan/langchain-chat
|
||||
|
||||
# 初始化 conda(确保 PATH 中包含 conda 路径)
|
||||
CONDA_INIT="/root/miniconda3/etc/profile.d/conda.sh"
|
||||
if [ -f "$CONDA_INIT" ]; then
|
||||
# 初始化 conda,将 conda 路径添加到 PATH
|
||||
. "$CONDA_INIT" 2>/dev/null || source "$CONDA_INIT" 2>/dev/null
|
||||
fi
|
||||
|
||||
# 查找 conda 可执行文件(按优先级)
|
||||
if command -v conda &> /dev/null; then
|
||||
CONDA_EXE="conda"
|
||||
elif [ -f "/root/miniconda3/bin/conda" ]; then
|
||||
CONDA_EXE="/root/miniconda3/bin/conda"
|
||||
elif [ -f "/root/miniconda3/condabin/conda" ]; then
|
||||
CONDA_EXE="/root/miniconda3/condabin/conda"
|
||||
else
|
||||
print_red "错误: 未找到 conda 命令"
|
||||
print_red "请检查 conda 是否已安装: /root/miniconda3"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 使用 conda 环境启动
|
||||
print_yellow "使用环境: gangyan"
|
||||
print_yellow "日志文件: nohup.out"
|
||||
print_yellow "Conda路径: $CONDA_EXE"
|
||||
|
||||
# 获取 python 的完整路径
|
||||
PYTHON_EXE="/root/miniconda3/envs/gangyan/bin/python"
|
||||
if [ ! -f "$PYTHON_EXE" ]; then
|
||||
# 尝试通过 conda run 获取路径
|
||||
PYTHON_EXE="$($CONDA_EXE run -n gangyan which python 2>/dev/null)"
|
||||
if [ -z "$PYTHON_EXE" ] || [ ! -f "$PYTHON_EXE" ]; then
|
||||
print_red "错误: 无法找到 python 可执行文件"
|
||||
print_red "请检查 conda 环境 gangyan 是否已安装"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
print_yellow "Python路径: $PYTHON_EXE"
|
||||
|
||||
# 直接使用 python 完整路径启动(不依赖 conda activate,更可靠)
|
||||
# 设置 PYTHONPATH 确保能正确导入模块
|
||||
export PYTHONPATH="/home/gc/gangyan/langchain-chat:$PYTHONPATH"
|
||||
cd /home/gc/gangyan/langchain-chat
|
||||
nohup "$PYTHON_EXE" startup.py -a >> nohup.out 2>&1 &
|
||||
PID=$!
|
||||
|
||||
print_green "服务已启动,PID: $PID"
|
||||
print_yellow "日志文件: /home/gc/gangyan/langchain-chat/nohup.out"
|
||||
print_yellow "查看日志: tail -f nohup.out"
|
||||
|
||||
# 等待几秒后显示日志
|
||||
sleep 2
|
||||
echo ""
|
||||
print_yellow "=== 最近日志 ==="
|
||||
tail -20 nohup.out
|
||||
@@ -1,100 +1,92 @@
|
||||
import asyncio
|
||||
import re
|
||||
import aiohttp
|
||||
import json
|
||||
import logging
|
||||
import requests
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from server.chat import utils
|
||||
|
||||
# 配置日志记录器
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
async def duckduckgo_search_iter(query: str, uuid: str = "",time: str = "", resource_type: str = None, limit: int = 3):
|
||||
# 定义三个API的URL
|
||||
text_url = 'http://43.251.225.121/inspur/search_text'
|
||||
video_url = 'http://43.251.225.121/inspur/search_video'
|
||||
news_url = 'http://43.251.225.121/inspur/search_new'
|
||||
# 新接口:内网 searxng 服务(原 43.251.225.121 已下线)
|
||||
# aiohttp 与该 searxng 配合会 30s 超时(疑似 header/UA 被拦),所以改用 requests。
|
||||
SEARXNG_URL = 'http://118.196.92.255/searxng/search'
|
||||
SEARXNG_HEADERS = {'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) gangyan-langchain'}
|
||||
|
||||
payload = {
|
||||
"query": query,
|
||||
"time": time
|
||||
}
|
||||
|
||||
async def fetch(session, url, json_payload,limit):
|
||||
logger.info(f"从 {url} 获取数据,请求参数: {json_payload}")
|
||||
def _searxng_results_to_items(results, mapping, limit):
|
||||
"""把 searxng 统一的 {url,title,content} 映射成老接口期望的字段格式"""
|
||||
out = []
|
||||
for r in results[:limit]:
|
||||
title = r.get('title', '') or ''
|
||||
url = r.get('url', '') or ''
|
||||
content = r.get('content', '') or ''
|
||||
item = {}
|
||||
for dst_key, src in mapping.items():
|
||||
if src == 'title':
|
||||
item[dst_key] = title
|
||||
elif src == 'url':
|
||||
item[dst_key] = url
|
||||
elif src == 'content':
|
||||
item[dst_key] = content
|
||||
out.append(item)
|
||||
return out
|
||||
|
||||
|
||||
def _sync_fetch(params, limit_n, kind):
|
||||
logger.info(f"searxng 请求 {kind}: params={params}")
|
||||
try:
|
||||
json_payload["limit"] = limit
|
||||
async with session.post(url, json=json_payload) as response:
|
||||
if response.status != 200:
|
||||
logger.error(f"向 {url} 请求失败,状态码 {response.status}")
|
||||
data = await response.json()
|
||||
logger.info(f"从 {url} 获取的资料数: {len(data) if isinstance(data, list) else '未知'}")
|
||||
return data
|
||||
r = requests.get(SEARXNG_URL, params=params, headers=SEARXNG_HEADERS, timeout=15)
|
||||
if r.status_code != 200:
|
||||
logger.error(f"searxng {kind} HTTP {r.status_code}")
|
||||
return []
|
||||
data = r.json()
|
||||
results = data.get('results', []) if isinstance(data, dict) else []
|
||||
logger.info(f"searxng {kind} 条数: {len(results)}")
|
||||
if kind == 'text':
|
||||
return _searxng_results_to_items(results, {'title': 'title', 'href': 'url', 'body': 'content'}, limit_n)
|
||||
if kind == 'video':
|
||||
return _searxng_results_to_items(results, {'title': 'title', 'content': 'url', 'description': 'content'}, limit_n)
|
||||
if kind == 'news':
|
||||
return _searxng_results_to_items(results, {'title': 'title', 'url': 'url', 'body': 'content'}, limit_n)
|
||||
return []
|
||||
except Exception as e:
|
||||
logger.error(f"获取 {url} 数据时发生错误: {e}")
|
||||
logger.error(f"searxng {kind} 请求异常: {type(e).__name__}: {e}")
|
||||
return []
|
||||
|
||||
# 根据 resource_type 确定要请求的 API
|
||||
# 默认并发请求三个API
|
||||
# 视频只请求 video_url
|
||||
# 新闻只请求 news_url
|
||||
# 其他类型只请求 text_url
|
||||
async with aiohttp.ClientSession() as session:
|
||||
logger.info("发起请求duckduckgo...")
|
||||
|
||||
async def duckduckgo_search_iter(query: str, uuid: str = "", time: str = "", resource_type: str = None, limit: int = 3):
|
||||
logger.info("发起 searxng 搜索请求...")
|
||||
|
||||
# 三类按 limit 平均分配
|
||||
n = limit % 3
|
||||
limit1 = 0
|
||||
limit2 = 0
|
||||
limit3 = 0
|
||||
match n:
|
||||
case 0:
|
||||
limit1 = limit//3
|
||||
limit2 = limit1
|
||||
limit3 = limit1
|
||||
case 1:
|
||||
if n == 0:
|
||||
limit1 = limit2 = limit3 = limit // 3
|
||||
elif n == 1:
|
||||
limit1 = limit // 3 + 1
|
||||
limit2 = limit//3
|
||||
limit3 = limit2
|
||||
case 2:
|
||||
limit1 = limit//3 +1
|
||||
limit2 = limit1
|
||||
limit2 = limit
|
||||
limit2 = limit3 = limit // 3
|
||||
else:
|
||||
limit1 = limit2 = limit // 3 + 1
|
||||
limit3 = limit // 3
|
||||
|
||||
if resource_type is None or not resource_type == 'video':
|
||||
text_task = asyncio.create_task(fetch(session, text_url, payload,limit1))
|
||||
video_task = asyncio.create_task(fetch(session, video_url, payload, limit3))
|
||||
news_task = asyncio.create_task(fetch(session, news_url, payload, limit2))
|
||||
text_result, video_result, news_result = await asyncio.gather(text_task, video_task, news_task)
|
||||
logger.info("合并结果...")
|
||||
|
||||
logger.info("合并结果完成")
|
||||
if resource_type is None or resource_type != 'video':
|
||||
text_task = asyncio.to_thread(_sync_fetch, {'q': query, 'format': 'json', 'categories': 'general'}, limit1, 'text')
|
||||
news_task = asyncio.to_thread(_sync_fetch, {'q': query, 'format': 'json', 'categories': 'news'}, limit2, 'news')
|
||||
video_task = asyncio.to_thread(_sync_fetch, {'q': query, 'format': 'json', 'categories': 'videos'}, limit3, 'video')
|
||||
text_result, news_result, video_result = await asyncio.gather(text_task, news_task, video_task)
|
||||
combined_result = {
|
||||
"text": text_result,
|
||||
"video": video_result,
|
||||
"news": news_result
|
||||
"news": news_result,
|
||||
}
|
||||
|
||||
else:
|
||||
video_result = await fetch(session, video_url, payload, limit)
|
||||
combined_result = {
|
||||
"video": video_result
|
||||
}
|
||||
del limit1,limit2,limit3
|
||||
# elif resource_type == 'news':
|
||||
# news_result = await fetch(session, news_url, payload)
|
||||
# combined_result = {
|
||||
# "news": news_result
|
||||
# }
|
||||
video_result = await asyncio.to_thread(_sync_fetch, {'q': query, 'format': 'json', 'categories': 'videos'}, limit, 'video')
|
||||
combined_result = {"video": video_result}
|
||||
|
||||
# else: # 其他类型
|
||||
# text_result = await fetch(session, text_url, payload)
|
||||
# combined_result = {
|
||||
# "text": text_result
|
||||
# }
|
||||
logger.info("searxng 请求已完成")
|
||||
|
||||
logger.info("请求已完成")
|
||||
res = []
|
||||
source = []
|
||||
info = utils.get_shared_variable(uuid)
|
||||
@@ -121,66 +113,36 @@ async def duckduckgo_search_iter(query: str, uuid: str = "",time: str = "", reso
|
||||
|
||||
def duckduckgo_search(query: str, time: str = "", resource_type: str = None):
|
||||
logger.info(f"模型输入: {query}")
|
||||
# 对传入的 query 字段进行解析
|
||||
# 判断 query 是否包含 "}{"
|
||||
# if "}{" in query:
|
||||
# # 将 query 分割为两个JSON字符串
|
||||
# split_index = query.find("}{")
|
||||
# json_part1 = query[:split_index+1]
|
||||
# json_part2 = query[split_index+1:]
|
||||
|
||||
# try:
|
||||
# obj1 = json.loads(json_part1)
|
||||
# obj2 = json.loads(json_part2)
|
||||
|
||||
# # 提取 query, resource_type, time, uuid
|
||||
# parsed_query = obj1.get("query", "")
|
||||
# parsed_resource_type = obj1.get("resource_type", None)
|
||||
# parsed_time = obj1.get("time", time) # 如obj1未包含time则使用传入的默认值
|
||||
# parsed_uuid = obj2.get("uuid", "")
|
||||
matches = re.findall(r'\{.*?\}', query)
|
||||
if len(matches) >= 2:
|
||||
query = matches[0]
|
||||
else:
|
||||
return "<关键指令>不需要再调用该工具了</关键指令>"
|
||||
parsed_uuid = ""
|
||||
parsed_limit = 3
|
||||
try:
|
||||
obj1 = json.loads(query)
|
||||
parsed_query = obj1.get("query", "")
|
||||
parsed_limit = obj1.get("limit", 3)
|
||||
parsed_resource_type = obj1.get("resource_type", None)
|
||||
parsed_time = obj1.get("time", time) # 如obj1未包含time则使用传入的默认值
|
||||
parsed_time = obj1.get("time", time)
|
||||
parsed_uuid = json.loads(matches[1])["uuid"]
|
||||
# 将解析到的值覆盖原有的参数
|
||||
query = parsed_query if parsed_query else query
|
||||
resource_type = parsed_resource_type if parsed_resource_type else resource_type
|
||||
time = parsed_time if parsed_time else time
|
||||
|
||||
logger.info(f"解析完成,query: {query}, uuid: {parsed_uuid}, time: {time}, resource_type: {resource_type}, parsed_limit: {parsed_limit}")
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error(f"解析JSON出错: {e}")
|
||||
|
||||
# 在同步环境中运行异步函数
|
||||
combined_result = asyncio.run(duckduckgo_search_iter(query, parsed_uuid, time, resource_type, parsed_limit))
|
||||
# 以标准json格式输出
|
||||
logger.info("返回JSON格式的结果给到模型...")
|
||||
return combined_result
|
||||
|
||||
|
||||
class DuckduckgoInput(BaseModel):
|
||||
location: str = Field(description="网络搜索查询")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 测试调用
|
||||
# 1. 默认请求三个API
|
||||
# result_default = duckduckgo_search("粉末冶金", "m", "default")
|
||||
# print("duckduckgo输出(默认):\n", result_default)
|
||||
|
||||
# # 2. 只请求视频
|
||||
# result_video = duckduckgo_search("粉末冶金", "m", "video")
|
||||
# print("duckduckgo输出(视频):\n", result_video)
|
||||
|
||||
# # 3. 只请求新闻
|
||||
# result_news = duckduckgo_search("粉末冶金", "m", "news")
|
||||
# print("duckduckgo输出(新闻):\n", result_news)
|
||||
|
||||
# 4. 其它类型只请求文本
|
||||
result_other = duckduckgo_search("粉末冶金", "m", "other")
|
||||
print("duckduckgo输出(其他):\n", result_other)
|
||||
result_other = duckduckgo_search('{"query":"粉末冶金","limit":3}{"uuid":"test-uuid"}', "m", "other")
|
||||
print("searxng输出(其他):\n", result_other)
|
||||
|
||||
@@ -70,7 +70,7 @@ class ZhipuSearchAPIWrapper:
|
||||
)
|
||||
|
||||
logging.info(f"Zhipu检索内容:{search_query}")
|
||||
url = "http://ywk3hvt4d:01Jp2V1tR9PdTsYSz919779Rb9_@134.122.191.214/search"
|
||||
url = "http://118.196.92.255/searxng/search"
|
||||
engines = "duckduckgo,bing"
|
||||
data = {
|
||||
"format":"json",
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
# mac设备上的grep命令可能不支持grep -P选项,请使用Homebrew安装;或使用ggrep命令
|
||||
ps -eo pid,user,cmd|grep -P 'server/api.py|webui.py|fastchat.serve|langchain_chat'|grep -v grep|awk '{print $1}'|xargs kill -9
|
||||
@@ -1,60 +0,0 @@
|
||||
#!/bin/bash
|
||||
# 启动 Python 后端服务脚本(使用 nohup)
|
||||
|
||||
# 颜色定义
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 检查是否已有进程在运行
|
||||
EXISTING=$(ps aux | grep "[p]ython.*startup.py -a" | awk '{print $2}')
|
||||
if [ -n "$EXISTING" ]; then
|
||||
echo -e "${YELLOW}警告: 检测到已有 Python 后端进程在运行 (PID: $EXISTING)${NC}"
|
||||
read -p "是否先停止现有进程? (y/n): " -n 1 -r
|
||||
echo
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
echo -e "${YELLOW}正在停止现有进程...${NC}"
|
||||
kill $EXISTING 2>/dev/null
|
||||
sleep 2
|
||||
# 如果还在运行,强制终止
|
||||
if ps -p $EXISTING > /dev/null 2>&1; then
|
||||
kill -9 $EXISTING 2>/dev/null
|
||||
fi
|
||||
echo -e "${GREEN}已停止现有进程${NC}"
|
||||
else
|
||||
echo -e "${RED}请先停止现有进程或使用 stop.sh${NC}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# 获取脚本所在目录
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
# 设置 PDF 转换服务环境变量(确保与 pdf-convert-service 一致)
|
||||
export PDF_CONVERT_KB_ROOT="${PDF_CONVERT_KB_ROOT:-$SCRIPT_DIR/knowledge_base}"
|
||||
|
||||
# 检查 conda 环境
|
||||
if ! command -v conda &> /dev/null; then
|
||||
echo -e "${RED}错误: 未找到 conda 命令${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 激活 conda 环境并启动
|
||||
echo -e "${YELLOW}正在启动 Python 后端...${NC}"
|
||||
echo -e "${YELLOW}使用环境: gangyan${NC}"
|
||||
echo -e "${YELLOW}日志文件: nohup.out${NC}"
|
||||
|
||||
# 使用 nohup 启动
|
||||
nohup conda run -n gangyan python startup.py -a > nohup.out 2>&1 &
|
||||
PID=$!
|
||||
|
||||
echo -e "${GREEN}后端已启动,PID: $PID${NC}"
|
||||
echo -e "${YELLOW}查看日志: tail -f nohup.out${NC}"
|
||||
echo -e "${YELLOW}停止服务: ./stop.sh${NC}"
|
||||
|
||||
# 等待几秒后显示日志
|
||||
sleep 2
|
||||
echo -e "\n${YELLOW}=== 最近日志 ===${NC}"
|
||||
tail -20 nohup.out
|
||||
@@ -1,70 +0,0 @@
|
||||
#!/bin/bash
|
||||
# 停止 Python 后端服务脚本
|
||||
|
||||
# 默认端口(从 server_config.py 中获取)
|
||||
DEFAULT_PORT=7861
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
echo -e "${YELLOW}正在查找运行中的 Python 后端进程...${NC}"
|
||||
|
||||
# 方法1: 通过进程名查找
|
||||
PIDS=$(ps aux | grep "[p]ython.*startup.py -a" | awk '{print $2}')
|
||||
|
||||
# 方法2: 如果方法1没找到,通过端口查找
|
||||
if [ -z "$PIDS" ]; then
|
||||
echo -e "${YELLOW}未找到 startup.py 进程,尝试通过端口 ${DEFAULT_PORT} 查找...${NC}"
|
||||
PIDS=$(lsof -ti:${DEFAULT_PORT} 2>/dev/null)
|
||||
fi
|
||||
|
||||
if [ -z "$PIDS" ]; then
|
||||
echo -e "${RED}未找到运行中的 Python 后端进程${NC}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}找到以下进程:${NC}"
|
||||
ps aux | grep "[p]ython.*startup.py -a" | grep -v grep
|
||||
if [ -n "$PIDS" ]; then
|
||||
echo -e "${YELLOW}进程 PID: $PIDS${NC}"
|
||||
fi
|
||||
|
||||
# 询问是否确认停止
|
||||
read -p "是否停止这些进程? (y/n): " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
echo -e "${YELLOW}已取消${NC}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 停止进程
|
||||
for PID in $PIDS; do
|
||||
if [ -n "$PID" ]; then
|
||||
echo -e "${YELLOW}正在停止进程 $PID...${NC}"
|
||||
kill $PID 2>/dev/null
|
||||
if [ $? -eq 0 ]; then
|
||||
echo -e "${GREEN}进程 $PID 已发送停止信号${NC}"
|
||||
else
|
||||
echo -e "${RED}无法停止进程 $PID${NC}"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# 等待进程结束
|
||||
echo -e "${YELLOW}等待进程结束...${NC}"
|
||||
sleep 3
|
||||
|
||||
# 检查是否还有进程在运行
|
||||
REMAINING=$(ps aux | grep "[p]ython.*startup.py -a" | awk '{print $2}')
|
||||
if [ -n "$REMAINING" ]; then
|
||||
echo -e "${YELLOW}仍有进程在运行,强制终止...${NC}"
|
||||
for PID in $REMAINING; do
|
||||
kill -9 $PID 2>/dev/null
|
||||
echo -e "${GREEN}已强制终止进程 $PID${NC}"
|
||||
done
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}所有进程已停止${NC}"
|
||||
@@ -1,53 +0,0 @@
|
||||
#!/bin/bash
|
||||
# 快速停止 Python 后端服务脚本(无需确认)
|
||||
|
||||
# 默认端口
|
||||
DEFAULT_PORT=7861
|
||||
|
||||
# 颜色定义
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m'
|
||||
|
||||
echo -e "${YELLOW}正在查找并停止 Python 后端进程...${NC}"
|
||||
|
||||
# 方法1: 通过进程名查找
|
||||
PIDS=$(ps aux | grep "[p]ython.*startup.py -a" | awk '{print $2}')
|
||||
|
||||
# 方法2: 如果方法1没找到,通过端口查找
|
||||
if [ -z "$PIDS" ]; then
|
||||
PIDS=$(lsof -ti:${DEFAULT_PORT} 2>/dev/null)
|
||||
fi
|
||||
|
||||
if [ -z "$PIDS" ]; then
|
||||
echo -e "${RED}未找到运行中的 Python 后端进程${NC}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 显示找到的进程
|
||||
echo -e "${GREEN}找到以下进程:${NC}"
|
||||
ps aux | grep "[p]ython.*startup.py -a" | grep -v grep
|
||||
|
||||
# 停止进程
|
||||
for PID in $PIDS; do
|
||||
if [ -n "$PID" ]; then
|
||||
echo -e "${YELLOW}正在停止进程 $PID...${NC}"
|
||||
kill $PID 2>/dev/null
|
||||
fi
|
||||
done
|
||||
|
||||
# 等待进程结束
|
||||
sleep 2
|
||||
|
||||
# 检查是否还有进程在运行,如果有则强制终止
|
||||
REMAINING=$(ps aux | grep "[p]ython.*startup.py -a" | awk '{print $2}')
|
||||
if [ -n "$REMAINING" ]; then
|
||||
echo -e "${YELLOW}强制终止剩余进程...${NC}"
|
||||
for PID in $REMAINING; do
|
||||
kill -9 $PID 2>/dev/null
|
||||
echo -e "${GREEN}已强制终止进程 $PID${NC}"
|
||||
done
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✓ 所有进程已停止${NC}"
|
||||
@@ -1,13 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
# 须用 bash 执行;若误用 sh/dash 会自动改用 bash 再跑一遍
|
||||
[ -n "${BASH_VERSION:-}" ] || exec /usr/bin/env bash "$0" ${1+"$@"}
|
||||
# 重启 Java 后端 chat_web_yj.jar(与 start_all.sh 一致)
|
||||
# 重启 Java 后端 chat_web_backend.jar(源码 mvn 编译产物,profile=dev)
|
||||
# 日志:gangyan/logs/backend.log
|
||||
# 勿在此加 -Dspring.datasource*:与 application-local.yml 打架易连错库。
|
||||
# 若 jar 未重打、内嵌 yml 仍是远程库,必须依赖 backend/application-local.yml(含完整 spring 数据源)。
|
||||
# 注:backend/chat_web_yj.jar(同一 jar 的改名副本)及其 application-local.yml 覆盖配置已于 2026-04-20 归档删除。
|
||||
set -u
|
||||
source "$(cd "$(dirname "$0")" && pwd)/common-restart.sh"
|
||||
# 默认 backend.log;若曾被 root 创建导致当前用户不可写,则改用同目录 backend-<用户>.log(仍在 gangyan/logs 下)
|
||||
# 默认 backend.log;若曾被 root 创建导致当前用户不可写,则改用同目录 backend-<用户>.log
|
||||
LOG_FILE="$LOG_DIR/backend.log"
|
||||
if ! ( umask 022; : >>"$LOG_FILE" ) 2>/dev/null; then
|
||||
ALT="$LOG_DIR/backend-$(id -un).log"
|
||||
@@ -25,34 +24,41 @@ fi
|
||||
export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64
|
||||
export PATH="$JAVA_HOME/bin:$PATH"
|
||||
|
||||
log_tee "======== 停止 chat_web_yj.jar ========"
|
||||
pkill -f "chat_web_yj.jar" 2>/dev/null && log_tee "已发送停止信号" || log_tee "未找到运行中的进程"
|
||||
sleep 2
|
||||
BACKEND_DIR="$GANGYAN_ROOT/chat_web_backend"
|
||||
JAR_REL="target/chat_web_backend.jar"
|
||||
|
||||
log_tee "======== 启动 chat_web_yj.jar ========"
|
||||
cd "$GANGYAN_ROOT/backend"
|
||||
LOCAL_CFG="$GANGYAN_ROOT/backend/application-local.yml"
|
||||
EXTRA_JAVA_ARGS=()
|
||||
if [ -f "$LOCAL_CFG" ]; then
|
||||
log_tee "加载本地配置: $LOCAL_CFG"
|
||||
EXTRA_JAVA_ARGS=(-Dspring.config.additional-location="file:${LOCAL_CFG}")
|
||||
if [ ! -f "$BACKEND_DIR/$JAR_REL" ]; then
|
||||
log_tee "错误: 未找到 $BACKEND_DIR/$JAR_REL。先执行 chat_web_backend/compile.sh 编译。"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_tee "======== 停止 chat_web_backend.jar ========"
|
||||
pkill -f "target/chat_web_backend.jar" 2>/dev/null && log_tee "已发送停止信号" || log_tee "未找到运行中的进程"
|
||||
sleep 2
|
||||
# 端口兜底(非 hawei 启的旧进程 lsof 可能看不到,用 ss 探测)
|
||||
if ss -tln 2>/dev/null | grep -qE ':8099[[:space:]]'; then
|
||||
log_tee "8099 仍被占用,再等 3 秒..."
|
||||
sleep 3
|
||||
fi
|
||||
|
||||
log_tee "======== 启动 chat_web_backend.jar (profile=dev) ========"
|
||||
cd "$BACKEND_DIR"
|
||||
nohup java -jar \
|
||||
-Xms512m -Xmx2048m \
|
||||
"${EXTRA_JAVA_ARGS[@]}" \
|
||||
-Dspring.profiles.active=yj \
|
||||
chat_web_yj.jar >> "$LOG_FILE" 2>&1 &
|
||||
"$JAR_REL" \
|
||||
--spring.profiles.active=dev >> "$LOG_FILE" 2>&1 &
|
||||
STARTED_PID=$!
|
||||
log_tee "已后台启动,PID=$STARTED_PID"
|
||||
log_tee "Java 标准输出/错误写入: $LOG_FILE"
|
||||
sleep 3
|
||||
if kill -0 "$STARTED_PID" 2>/dev/null; then
|
||||
if command -v ss >/dev/null 2>&1 && ss -tln 2>/dev/null | grep -q ':8099'; then
|
||||
log_tee "自检: 进程存活且 8099 已监听(后端已就绪或即将就绪)"
|
||||
if ss -tln 2>/dev/null | grep -qE ':8099[[:space:]]'; then
|
||||
log_tee "自检: 进程存活且 8099 已监听"
|
||||
else
|
||||
log_tee "自检: 进程存活,但 8099 尚未监听(可能仍在启动;若 10 秒后仍无,请 tail 上面日志路径)"
|
||||
log_tee "自检: 进程存活,但 8099 尚未监听(可能仍在启动;10 秒后若仍无请 tail $LOG_FILE)"
|
||||
fi
|
||||
else
|
||||
log_tee "自检失败: 进程已退出。请查看日志末尾:"
|
||||
log_tee "自检失败: 进程已退出。最近日志:"
|
||||
tail -n 40 "$LOG_FILE" 2>/dev/null | while IFS= read -r line || [ -n "$line" ]; do log_line "$line"; done
|
||||
exit 1
|
||||
fi
|
||||
|
||||
98
scripts/excalidraw-ai-proxy.py
Normal file
@@ -0,0 +1,98 @@
|
||||
"""
|
||||
Excalidraw AI 代理服务
|
||||
接收 Excalidraw 的 text-to-diagram 请求,转发给 deepseek-v3 生成 Mermaid 语法
|
||||
"""
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
import requests
|
||||
import json
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
LLM_API = "http://10.102.24.75:3000/v1/chat/completions"
|
||||
LLM_KEY = "sk-BlQIGRrotbVDWE5mXCPBFjVWIvJ83hldzz67xInNwzVo7pPb"
|
||||
|
||||
SYSTEM_PROMPT = """You are an expert at creating Mermaid diagrams.
|
||||
When the user describes a diagram, workflow, flowchart, or similar,
|
||||
generate ONLY valid Mermaid syntax. Do not include any explanation,
|
||||
just the Mermaid code. Do not wrap it in markdown code blocks.
|
||||
Always start with a diagram type declaration like: graph TD, flowchart LR, sequenceDiagram, classDiagram, etc."""
|
||||
|
||||
|
||||
@app.post("/v1/ai/text-to-diagram/generate")
|
||||
async def text_to_diagram(request: Request):
|
||||
body = await request.json()
|
||||
print(f"[AI] Received request: {json.dumps(body, ensure_ascii=False)[:500]}")
|
||||
prompt = body.get("prompt", body.get("text", body.get("message", "")))
|
||||
|
||||
try:
|
||||
resp = requests.post(LLM_API, json={
|
||||
"model": "deepseek-v3",
|
||||
"messages": [
|
||||
{"role": "system", "content": SYSTEM_PROMPT},
|
||||
{"role": "user", "content": prompt}
|
||||
],
|
||||
"temperature": 0.3,
|
||||
"max_tokens": 2048
|
||||
}, headers={"Authorization": f"Bearer {LLM_KEY}"}, timeout=60)
|
||||
|
||||
result = resp.json()
|
||||
print(f"[AI] LLM response status: {resp.status_code}")
|
||||
mermaid_code = result["choices"][0]["message"]["content"].strip()
|
||||
|
||||
# 清理可能的 markdown 包裹
|
||||
if mermaid_code.startswith("```"):
|
||||
mermaid_code = mermaid_code.split("\n", 1)[1] if "\n" in mermaid_code else mermaid_code[3:]
|
||||
if mermaid_code.endswith("```"):
|
||||
mermaid_code = mermaid_code[:-3].strip()
|
||||
|
||||
print(f"[AI] Mermaid output: {mermaid_code[:200]}")
|
||||
return {"generatedResponse": mermaid_code}
|
||||
except Exception as e:
|
||||
print(f"[AI] Error: {e}")
|
||||
return {"generatedResponse": f"graph TD\n A[Error: {str(e)[:50]}]"}
|
||||
|
||||
|
||||
@app.post("/v1/ai/diagram-to-code/generate")
|
||||
async def diagram_to_code(request: Request):
|
||||
body = await request.json()
|
||||
texts = body.get("texts", [])
|
||||
theme = body.get("theme", "light")
|
||||
print(f"[AI] diagram-to-code request, texts: {texts[:5]}, theme: {theme}")
|
||||
|
||||
prompt = f"Based on these UI element labels: {', '.join(texts) if texts else 'empty wireframe'}. Theme: {theme}. Generate a complete, beautiful HTML page with inline CSS that represents this wireframe layout. Only output the HTML code, nothing else."
|
||||
|
||||
try:
|
||||
resp = requests.post(LLM_API, json={
|
||||
"model": "deepseek-v3",
|
||||
"messages": [
|
||||
{"role": "system", "content": "You are an expert web developer. Generate complete HTML with inline CSS based on wireframe descriptions. Output ONLY the HTML code, no explanation, no markdown code blocks."},
|
||||
{"role": "user", "content": prompt}
|
||||
],
|
||||
"temperature": 0.3,
|
||||
"max_tokens": 4096
|
||||
}, headers={"Authorization": f"Bearer {LLM_KEY}"}, timeout=60)
|
||||
|
||||
result = resp.json()
|
||||
html_code = result["choices"][0]["message"]["content"].strip()
|
||||
if html_code.startswith("```"):
|
||||
html_code = html_code.split("\n", 1)[1] if "\n" in html_code else html_code[3:]
|
||||
if html_code.endswith("```"):
|
||||
html_code = html_code[:-3].strip()
|
||||
print(f"[AI] HTML output length: {len(html_code)}")
|
||||
return html_code
|
||||
except Exception as e:
|
||||
print(f"[AI] diagram-to-code error: {e}")
|
||||
return f"<html><body><h1>Generation Error</h1><p>{str(e)[:100]}</p></body></html>"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
uvicorn.run(app, host="0.0.0.0", port=18082)
|
||||
153
scripts/latex-editor/index.html
Normal file
@@ -0,0 +1,153 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>LaTeX 公式编辑器</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, "Microsoft YaHei", sans-serif; background: #f0f2f5; color: #333; }
|
||||
.header { background: #004EA0; color: #fff; padding: 16px 32px; font-size: 18px; font-weight: bold; }
|
||||
.container { max-width: 1200px; margin: 24px auto; padding: 0 24px; display: flex; gap: 20px; }
|
||||
.panel { background: #fff; border-radius: 10px; padding: 20px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); flex: 1; }
|
||||
.panel h3 { font-size: 15px; color: #333; margin-bottom: 12px; display: flex; justify-content: space-between; align-items: center; }
|
||||
textarea { width: 100%; height: 200px; border: 1px solid #ddd; border-radius: 8px; padding: 14px; font-family: 'Courier New', monospace; font-size: 15px; resize: vertical; outline: none; }
|
||||
textarea:focus { border-color: #004EA0; }
|
||||
.preview { min-height: 200px; border: 1px solid #eee; border-radius: 8px; padding: 20px; display: flex; align-items: center; justify-content: center; background: #fafbfc; overflow: auto; }
|
||||
.preview .katex { font-size: 24px; }
|
||||
.error { color: #dc2626; font-size: 13px; text-align: center; }
|
||||
.toolbar { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 14px; }
|
||||
.toolbar button { padding: 4px 10px; border: 1px solid #ddd; border-radius: 6px; background: #f5f7fa; cursor: pointer; font-size: 13px; transition: all 0.15s; }
|
||||
.toolbar button:hover { border-color: #004EA0; color: #004EA0; background: #e8f0fe; }
|
||||
.symbols { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 16px; }
|
||||
.symbols button { width: 42px; height: 36px; border: 1px solid #eee; border-radius: 6px; background: #fafbfc; cursor: pointer; font-size: 16px; display: flex; align-items: center; justify-content: center; }
|
||||
.symbols button:hover { background: #e8f0fe; border-color: #004EA0; }
|
||||
.actions { margin-top: 14px; display: flex; gap: 10px; }
|
||||
.actions button { padding: 8px 20px; border: none; border-radius: 8px; cursor: pointer; font-size: 14px; }
|
||||
.btn-primary { background: #004EA0; color: #fff; }
|
||||
.btn-primary:hover { background: #003a7a; }
|
||||
.btn-secondary { background: #f0f2f5; color: #333; border: 1px solid #ddd !important; }
|
||||
.btn-secondary:hover { background: #e5e7eb; }
|
||||
.templates { margin-top: 20px; }
|
||||
.templates h4 { font-size: 14px; color: #666; margin-bottom: 8px; }
|
||||
.tpl-list { display: flex; flex-wrap: wrap; gap: 6px; }
|
||||
.tpl-list button { padding: 4px 12px; border: 1px solid #e0e0e0; border-radius: 6px; background: #fff; cursor: pointer; font-size: 12px; color: #555; }
|
||||
.tpl-list button:hover { border-color: #004EA0; color: #004EA0; }
|
||||
.copied { position: fixed; top: 50%; left: 50%; transform: translate(-50%,-50%); background: rgba(0,0,0,0.7); color: #fff; padding: 10px 24px; border-radius: 8px; font-size: 14px; display: none; z-index: 999; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">LaTeX 公式编辑器</div>
|
||||
<div class="container">
|
||||
<div class="panel" style="flex:1.2">
|
||||
<h3>输入公式</h3>
|
||||
<div class="toolbar">
|
||||
<button onclick="ins('\\frac{a}{b}')">分数</button>
|
||||
<button onclick="ins('\\sqrt{x}')">根号</button>
|
||||
<button onclick="ins('\\sum_{i=1}^{n}')">求和</button>
|
||||
<button onclick="ins('\\int_{a}^{b}')">积分</button>
|
||||
<button onclick="ins('\\lim_{x \\to \\infty}')">极限</button>
|
||||
<button onclick="ins('\\prod_{i=1}^{n}')">连乘</button>
|
||||
<button onclick="ins('x^{2}')">上标</button>
|
||||
<button onclick="ins('x_{i}')">下标</button>
|
||||
<button onclick="ins('\\binom{n}{k}')">组合</button>
|
||||
<button onclick="ins('\\vec{a}')">向量</button>
|
||||
<button onclick="ins('\\hat{x}')">帽</button>
|
||||
<button onclick="ins('\\bar{x}')">横线</button>
|
||||
<button onclick="ins('\\dot{x}')">点</button>
|
||||
<button onclick="ins('\\overline{AB}')">上划线</button>
|
||||
<button onclick="ins('\\begin{matrix} a & b \\\\ c & d \\end{matrix}')">矩阵</button>
|
||||
<button onclick="ins('\\begin{cases} x & y \\\\ a & b \\end{cases}')">分段</button>
|
||||
<button onclick="ins('\\log')">log</button>
|
||||
<button onclick="ins('\\ln')">ln</button>
|
||||
</div>
|
||||
<textarea id="input" placeholder="在此输入 LaTeX 公式,如:E = mc^{2}" oninput="render()">E = mc^{2}</textarea>
|
||||
<div class="symbols" id="symbolBar"></div>
|
||||
<div class="actions">
|
||||
<button class="btn-primary" onclick="copyLatex()">复制公式</button>
|
||||
<button class="btn-secondary" onclick="downloadImg()">下载为图片</button>
|
||||
<button class="btn-secondary" onclick="document.getElementById('input').value='';render()">清空</button>
|
||||
</div>
|
||||
<div class="templates">
|
||||
<h4>常用公式模板</h4>
|
||||
<div class="tpl-list">
|
||||
<button onclick="setTpl('E = mc^{2}')">质能方程</button>
|
||||
<button onclick="setTpl('e^{i\\pi} + 1 = 0')">欧拉公式</button>
|
||||
<button onclick="setTpl('a^2 + b^2 = c^2')">勾股定理</button>
|
||||
<button onclick="setTpl('x = \\frac{-b \\pm \\sqrt{b^2 - 4ac}}{2a}')">求根公式</button>
|
||||
<button onclick="setTpl('f\'(x) = \\lim_{h \\to 0} \\frac{f(x+h) - f(x)}{h}')">导数定义</button>
|
||||
<button onclick="setTpl('\\int_{-\\infty}^{\\infty} e^{-x^2} dx = \\sqrt{\\pi}')">高斯积分</button>
|
||||
<button onclick="setTpl('\\nabla \\times \\vec{E} = -\\frac{\\partial \\vec{B}}{\\partial t}')">麦克斯韦</button>
|
||||
<button onclick="setTpl('\\sigma = \\sqrt{\\frac{1}{N}\\sum_{i=1}^{N}(x_i - \\mu)^2}')">标准差</button>
|
||||
<button onclick="setTpl('P(A|B) = \\frac{P(B|A) \\cdot P(A)}{P(B)}')">贝叶斯</button>
|
||||
<button onclick="setTpl('\\mathbf{A} = \\begin{pmatrix} a_{11} & a_{12} \\\\ a_{21} & a_{22} \\end{pmatrix}')">矩阵</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel">
|
||||
<h3>实时预览 <span style="font-weight:normal;font-size:12px;color:#999">公式渲染由 KaTeX 提供</span></h3>
|
||||
<div class="preview" id="preview"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="copied" id="toast">已复制!</div>
|
||||
<script>
|
||||
const symbols = ['\\alpha','\\beta','\\gamma','\\delta','\\epsilon','\\zeta','\\eta','\\theta','\\lambda','\\mu','\\nu','\\xi','\\pi','\\rho','\\sigma','\\tau','\\phi','\\psi','\\omega','\\Gamma','\\Delta','\\Theta','\\Lambda','\\Pi','\\Sigma','\\Phi','\\Psi','\\Omega','\\infty','\\partial','\\nabla','\\forall','\\exists','\\in','\\notin','\\subset','\\cup','\\cap','\\times','\\cdot','\\div','\\pm','\\mp','\\leq','\\geq','\\neq','\\approx','\\equiv','\\sim','\\rightarrow','\\leftarrow','\\Rightarrow','\\Leftarrow'];
|
||||
const bar = document.getElementById('symbolBar');
|
||||
symbols.forEach(s => {
|
||||
const b = document.createElement('button');
|
||||
try { katex.render(s, b, {throwOnError:false}); } catch(e) { b.textContent = s; }
|
||||
b.onclick = () => ins(s);
|
||||
bar.appendChild(b);
|
||||
});
|
||||
function render() {
|
||||
const input = document.getElementById('input').value.trim();
|
||||
const el = document.getElementById('preview');
|
||||
if (!input) { el.innerHTML = '<span style="color:#bbb">在左侧输入公式</span>'; return; }
|
||||
try { katex.render(input, el, { displayMode: true, throwOnError: true }); }
|
||||
catch(e) { el.innerHTML = '<span class="error">' + e.message + '</span>'; }
|
||||
}
|
||||
function ins(s) {
|
||||
const t = document.getElementById('input');
|
||||
const start = t.selectionStart, end = t.selectionEnd;
|
||||
t.value = t.value.slice(0, start) + s + t.value.slice(end);
|
||||
t.focus();
|
||||
t.selectionStart = t.selectionEnd = start + s.length;
|
||||
render();
|
||||
}
|
||||
function setTpl(s) { document.getElementById('input').value = s; render(); }
|
||||
function copyLatex() {
|
||||
navigator.clipboard.writeText(document.getElementById('input').value);
|
||||
showToast('公式已复制!');
|
||||
}
|
||||
function downloadImg() {
|
||||
const el = document.getElementById('preview');
|
||||
if (!el.querySelector('.katex')) { showToast('请先输入公式'); return; }
|
||||
// 直接用 KaTeX 的 mathml/svg 渲染结果导出为 SVG 文件下载
|
||||
const w = el.scrollWidth + 40, h = el.scrollHeight + 40;
|
||||
const svgStr = `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}">
|
||||
<rect width="100%" height="100%" fill="white"/>
|
||||
<foreignObject width="100%" height="100%">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display:flex;align-items:center;justify-content:center;width:${w}px;height:${h}px;font-size:24px">
|
||||
${el.innerHTML}
|
||||
</div>
|
||||
</foreignObject>
|
||||
</svg>`;
|
||||
const blob = new Blob([svgStr], {type: 'image/svg+xml'});
|
||||
const a = document.createElement('a');
|
||||
a.href = URL.createObjectURL(blob);
|
||||
a.download = 'formula.svg';
|
||||
a.click();
|
||||
URL.revokeObjectURL(a.href);
|
||||
showToast('图片已下载!');
|
||||
}
|
||||
function showToast(msg) {
|
||||
const t = document.getElementById('toast');
|
||||
t.textContent = msg; t.style.display = 'block';
|
||||
setTimeout(() => t.style.display = 'none', 1500);
|
||||
}
|
||||
render();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -31,7 +31,8 @@ fi
|
||||
|
||||
MAX_BYTES=$((MAX_MB * 1024 * 1024))
|
||||
TMP="${FILE}.trimtmp.$$"
|
||||
LOCK="${FILE}.trimlock"
|
||||
# LOCK 放 /tmp 并按用户命名,避免不同用户启动时 .trimlock owner 错配导致 Permission denied
|
||||
LOCK="/tmp/gangyan-trim-$(basename "$FILE").$(id -un).lock"
|
||||
|
||||
mkdir -p "$(dirname "$FILE")"
|
||||
touch "$FILE" 2>/dev/null || true
|
||||
|
||||
49
scripts/overleaf-deploy/docker-compose.yml
Normal file
@@ -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:
|
||||
128
scripts/overleaf-deploy/overleaf-register.py
Normal file
@@ -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 = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>注册 - LaTeX 论文编辑器</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, sans-serif; background: #f5f5f5; display: flex; justify-content: center; align-items: center; min-height: 100vh; }
|
||||
.card { background: #fff; border-radius: 12px; padding: 40px; width: 400px; box-shadow: 0 4px 20px rgba(0,0,0,0.1); }
|
||||
h2 { text-align: center; margin-bottom: 8px; color: #333; }
|
||||
.sub { text-align: center; color: #999; font-size: 14px; margin-bottom: 30px; }
|
||||
label { display: block; margin-bottom: 6px; color: #555; font-size: 14px; }
|
||||
input { width: 100%; padding: 10px 14px; border: 1px solid #ddd; border-radius: 8px; font-size: 14px; margin-bottom: 16px; }
|
||||
input:focus { outline: none; border-color: #004EA0; }
|
||||
button { width: 100%; padding: 12px; background: #004EA0; color: #fff; border: none; border-radius: 8px; font-size: 16px; cursor: pointer; }
|
||||
button:hover { background: #003a7a; }
|
||||
.msg { text-align: center; margin-top: 16px; font-size: 14px; }
|
||||
.msg.ok { color: #16a34a; }
|
||||
.msg.err { color: #dc2626; }
|
||||
a { color: #004EA0; text-decoration: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<h2>注册账号</h2>
|
||||
<p class="sub">LaTeX 论文编辑器</p>
|
||||
<form method="POST" action="/register">
|
||||
<label>邮箱</label>
|
||||
<input type="email" name="email" required placeholder="请输入邮箱">
|
||||
<button type="submit">注册</button>
|
||||
</form>
|
||||
<p class="sub" style="margin-top:20px">已有账号?<a href="OLURL">去登录</a></p>
|
||||
MSG_PLACEHOLDER
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
""".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'<p class="msg ok">注册成功!请点击下方链接设置密码:</p><p style="text-align:center;margin-top:10px"><a href="{set_url}" target="_blank" style="background:#004EA0;color:#fff;padding:10px 24px;border-radius:8px;text-decoration:none;font-size:14px">设置密码</a></p>'
|
||||
elif ok:
|
||||
msg = f'<p class="msg ok">注册成功!该邮箱可能已注册。请前往 <a href="{OL_URL}/login">登录页面</a> 登录。</p>'
|
||||
else:
|
||||
msg = f'<p class="msg err">注册失败,请稍后重试。</p>'
|
||||
except Exception as e:
|
||||
msg = f'<p class="msg err">注册失败:{str(e)[:100]}</p>'
|
||||
return REGISTER_HTML.replace("MSG_PLACEHOLDER", msg)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
uvicorn.run(app, host="0.0.0.0", port=18091)
|
||||
239
scripts/pptist-ai-backend.py
Normal file
@@ -0,0 +1,239 @@
|
||||
"""
|
||||
PPTist AI 后端服务
|
||||
接收 PPTist 的 AIPPT 请求,调用 deepseek-v3 生成大纲和PPT内容
|
||||
端口: 18086
|
||||
"""
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import StreamingResponse
|
||||
import httpx
|
||||
import json
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
LLM_API = "http://10.102.24.75:3000/v1/chat/completions"
|
||||
LLM_KEY = "sk-BlQIGRrotbVDWE5mXCPBFjVWIvJ83hldzz67xInNwzVo7pPb"
|
||||
LLM_MODEL = "deepseek-v3"
|
||||
|
||||
OUTLINE_SYSTEM_PROMPT = """你是一个专业的PPT大纲生成助手。用户会给你一个主题,你需要生成一份内容丰富、结构清晰的PPT大纲。
|
||||
|
||||
要求:
|
||||
1. 使用Markdown格式,用 # 表示演示文稿标题,## 表示章节标题,### 表示该章节下的内容页标题
|
||||
2. 必须包含6个主要章节(##)
|
||||
3. 每个章节下必须有2-3个内容页(###)
|
||||
4. 每个内容页标题要具体、有信息量,不要太笼统
|
||||
5. 内容要专业、逻辑清晰、层层递进
|
||||
6. 直接输出Markdown内容,不要有多余的解释
|
||||
|
||||
示例格式:
|
||||
# 人工智能技术发展
|
||||
## 人工智能概述
|
||||
### 定义与发展历史
|
||||
### 核心技术与分支
|
||||
## 机器学习基础
|
||||
### 监督学习与无监督学习
|
||||
### 深度学习与神经网络
|
||||
### 常用算法对比
|
||||
..."""
|
||||
|
||||
AIPPT_SYSTEM_PROMPT = """你是一个专业的PPT内容生成助手。用户会给你一份PPT大纲,你需要根据大纲生成完整、丰富、详实的PPT内容。
|
||||
|
||||
你必须严格按照以下JSON数组格式输出,不要输出任何其他内容。
|
||||
|
||||
结构规则:
|
||||
1. 第一页:cover类型,包含演示标题和副标题描述
|
||||
2. 第二页:contents类型,列出所有章节名称
|
||||
3. 每个章节:先一个transition过渡页,然后每个###子内容对应一个content页
|
||||
4. 每个content页必须包含4个items,每个item有title和text
|
||||
5. text内容必须详实具体,每条20-40字,包含实质性的数据、观点或分析,不要空泛
|
||||
6. 最后一页:end类型
|
||||
|
||||
输出格式(JSON数组,每个元素是一页slide):
|
||||
[
|
||||
{"type": "cover", "data": {"title": "演示标题", "text": "一句话描述演示主题和价值"}},
|
||||
{"type": "contents", "data": {"items": ["章节1名称", "章节2名称", "章节3名称", "章节4名称", "章节5名称", "章节6名称"]}},
|
||||
{"type": "transition", "data": {"title": "章节标题", "text": "本章将介绍什么内容,一句话概述"}},
|
||||
{"type": "content", "data": {"title": "内容页标题", "items": [
|
||||
{"title": "要点1标题", "text": "要点1的详细说明,包含具体信息、数据或分析"},
|
||||
{"title": "要点2标题", "text": "要点2的详细说明"},
|
||||
{"title": "要点3标题", "text": "要点3的详细说明"},
|
||||
{"title": "要点4标题", "text": "要点4的详细说明"}
|
||||
]}},
|
||||
{"type": "end"}
|
||||
]
|
||||
|
||||
重要:
|
||||
- 生成的总页数应在20-25页之间
|
||||
- 只输出JSON数组,不要markdown代码块,不要任何解释
|
||||
- 确保JSON格式完全正确,可被直接解析"""
|
||||
|
||||
|
||||
async def stream_llm(messages, temperature=0.7, max_tokens=4096):
|
||||
"""流式调用LLM并逐块返回文本"""
|
||||
async with httpx.AsyncClient(timeout=120) as client:
|
||||
async with client.stream(
|
||||
"POST",
|
||||
LLM_API,
|
||||
json={
|
||||
"model": LLM_MODEL,
|
||||
"messages": messages,
|
||||
"temperature": temperature,
|
||||
"max_tokens": max_tokens,
|
||||
"stream": True,
|
||||
},
|
||||
headers={"Authorization": f"Bearer {LLM_KEY}"},
|
||||
) as resp:
|
||||
async for line in resp.aiter_lines():
|
||||
if line.startswith("data: "):
|
||||
data = line[6:]
|
||||
if data.strip() == "[DONE]":
|
||||
break
|
||||
try:
|
||||
chunk = json.loads(data)
|
||||
delta = chunk["choices"][0].get("delta", {})
|
||||
content = delta.get("content", "")
|
||||
if content:
|
||||
yield content
|
||||
except (json.JSONDecodeError, KeyError, IndexError):
|
||||
continue
|
||||
|
||||
|
||||
@app.post("/tools/aippt_outline")
|
||||
async def aippt_outline(request: Request):
|
||||
data = await request.json()
|
||||
content = data.get("content", "")
|
||||
language = data.get("language", "中文")
|
||||
print(f"[AIPPT] Outline request: {content}, language: {language}")
|
||||
|
||||
user_prompt = f"请用{language}为以下主题生成PPT大纲:\n\n{content}"
|
||||
|
||||
async def generate():
|
||||
async for text in stream_llm(
|
||||
[
|
||||
{"role": "system", "content": OUTLINE_SYSTEM_PROMPT},
|
||||
{"role": "user", "content": user_prompt},
|
||||
],
|
||||
temperature=0.7,
|
||||
max_tokens=2048,
|
||||
):
|
||||
yield text
|
||||
|
||||
return StreamingResponse(generate(), media_type="text/event-stream")
|
||||
|
||||
|
||||
@app.post("/tools/aippt")
|
||||
async def aippt(request: Request):
|
||||
data = await request.json()
|
||||
content = data.get("content", "")
|
||||
language = data.get("language", "中文")
|
||||
style = data.get("style", "通用")
|
||||
print(f"[AIPPT] Generate request, style: {style}, language: {language}")
|
||||
|
||||
user_prompt = f"请根据以下大纲生成PPT内容,风格为「{style}」,语言为{language}:\n\n{content}"
|
||||
|
||||
async def generate():
|
||||
"""流式收集LLM输出,按JSON对象级别缓冲,每检测到一个完整对象就立即发送"""
|
||||
buf = ""
|
||||
brace_depth = 0
|
||||
in_string = False
|
||||
escape_next = False
|
||||
found_array = False # 是否已经跳过了数组开头的 [
|
||||
|
||||
try:
|
||||
async for chunk in stream_llm(
|
||||
[
|
||||
{"role": "system", "content": AIPPT_SYSTEM_PROMPT},
|
||||
{"role": "user", "content": user_prompt},
|
||||
],
|
||||
temperature=0.7,
|
||||
max_tokens=8192,
|
||||
):
|
||||
for ch in chunk:
|
||||
# 跳过数组开头的 [ 和结尾的 ] 以及逗号分隔符
|
||||
if not found_array:
|
||||
if ch == '[':
|
||||
found_array = True
|
||||
continue
|
||||
|
||||
if brace_depth == 0 and ch in (' ', '\n', '\r', '\t', ',', ']'):
|
||||
continue
|
||||
|
||||
buf += ch
|
||||
|
||||
if escape_next:
|
||||
escape_next = False
|
||||
continue
|
||||
if ch == '\\' and in_string:
|
||||
escape_next = True
|
||||
continue
|
||||
if ch == '"' and not escape_next:
|
||||
in_string = not in_string
|
||||
continue
|
||||
if in_string:
|
||||
continue
|
||||
|
||||
if ch == '{':
|
||||
brace_depth += 1
|
||||
elif ch == '}':
|
||||
brace_depth -= 1
|
||||
if brace_depth == 0:
|
||||
# 得到一个完整的JSON对象
|
||||
try:
|
||||
obj = json.loads(buf)
|
||||
yield json.dumps(obj, ensure_ascii=False) + "\n"
|
||||
print(f"[AIPPT] Sent slide: {obj.get('type', '?')}")
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"[AIPPT] JSON parse error: {e}, buf: {buf[:100]}")
|
||||
buf = ""
|
||||
|
||||
except Exception as e:
|
||||
print(f"[AIPPT] Error: {e}")
|
||||
yield json.dumps({"type": "cover", "data": {"title": "生成失败", "text": str(e)[:100]}}) + "\n"
|
||||
yield json.dumps({"type": "end"}) + "\n"
|
||||
|
||||
return StreamingResponse(generate(), media_type="text/event-stream")
|
||||
|
||||
|
||||
WRITING_COMMANDS = {
|
||||
"rewrite": "请改写以下文本,保持原意但使用不同的表达方式:",
|
||||
"expand": "请扩写以下文本,补充更多细节和内容:",
|
||||
"abbreviate": "请缩写以下文本,保留核心要点,使其更简洁:",
|
||||
"polish": "请润色以下文本,使其更流畅、专业:",
|
||||
"translate": "请将以下文本翻译为英文:",
|
||||
}
|
||||
|
||||
|
||||
@app.post("/tools/ai_writing")
|
||||
async def ai_writing(request: Request):
|
||||
data = await request.json()
|
||||
content = data.get("content", "")
|
||||
command = data.get("command", "rewrite")
|
||||
print(f"[AIPPT] Writing request, command: {command}")
|
||||
|
||||
instruction = WRITING_COMMANDS.get(command, WRITING_COMMANDS["rewrite"])
|
||||
user_prompt = f"{instruction}\n\n{content}"
|
||||
|
||||
async def generate():
|
||||
async for text in stream_llm(
|
||||
[
|
||||
{"role": "system", "content": "你是一个专业的文字助手,帮助用户改写、扩写、缩写、润色或翻译文本。直接输出结果,不要有额外解释。"},
|
||||
{"role": "user", "content": user_prompt},
|
||||
],
|
||||
temperature=0.7,
|
||||
max_tokens=2048,
|
||||
):
|
||||
yield text
|
||||
|
||||
return StreamingResponse(generate(), media_type="text/event-stream")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
uvicorn.run(app, host="0.0.0.0", port=18086)
|
||||
41
scripts/pptist-deploy/Dockerfile
Normal file
@@ -0,0 +1,41 @@
|
||||
# PPTist 构建 & 部署
|
||||
FROM docker.1ms.run/node:20 AS builder
|
||||
ENV http_proxy=http://219.234.197.247:53128
|
||||
ENV https_proxy=http://219.234.197.247:53128
|
||||
ENV no_proxy=localhost,127.0.0.1,10.0.0.0/8,192.168.0.0/16,172.17.0.0/16
|
||||
WORKDIR /app
|
||||
COPY PPTist/package*.json ./
|
||||
RUN npm config set registry https://registry.npmmirror.com && npm install
|
||||
COPY PPTist/ .
|
||||
|
||||
# 修改 SERVER_URL: 生产环境使用相对路径 /pptapi,由 nginx 反代到 AI 后端
|
||||
RUN sed -i "s|'https://server.pptist.cn'|'/pptapi'|g" src/services/index.ts
|
||||
|
||||
# 修改模型选项为 deepseek-v3
|
||||
RUN sed -i "s/glm-4\.7-flash/deepseek-v3/g" src/views/Editor/AIPPTDialog.vue && \
|
||||
sed -i "s/GLM-4\.7-Flash/DeepSeek-V3/g" src/views/Editor/AIPPTDialog.vue && \
|
||||
sed -i "s/doubao-seed-1\.6-flash/deepseek-v3/g" src/views/Editor/AIPPTDialog.vue && \
|
||||
sed -i "s/Doubao-Seed-1\.6-Flash/DeepSeek-V3/g" src/views/Editor/AIPPTDialog.vue && \
|
||||
sed -i "s/GLM-4\.5-Flash/DeepSeek-V3/g" src/services/index.ts
|
||||
|
||||
RUN npm run build
|
||||
|
||||
# 生产阶段
|
||||
FROM docker.1ms.run/nginx:alpine
|
||||
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||
RUN echo 'server { \
|
||||
listen 80; \
|
||||
root /usr/share/nginx/html; \
|
||||
index index.html; \
|
||||
location / { \
|
||||
try_files $uri $uri/ /index.html; \
|
||||
} \
|
||||
location /pptapi/ { \
|
||||
proxy_pass http://172.17.0.1:18086/; \
|
||||
proxy_http_version 1.1; \
|
||||
proxy_set_header Connection ""; \
|
||||
proxy_buffering off; \
|
||||
proxy_cache off; \
|
||||
} \
|
||||
}' > /etc/nginx/conf.d/default.conf
|
||||
EXPOSE 80
|
||||
41
scripts/pptist-deploy/deploy-all-tools.sh
Normal file
@@ -0,0 +1,41 @@
|
||||
#!/bin/bash
|
||||
# 一键部署三个工具: TrWebOCR, LibreTranslate, PPTist + AI后端
|
||||
set -e
|
||||
|
||||
echo "========== 1. 部署 TrWebOCR (端口 18083) =========="
|
||||
docker rm -f trwebocr 2>/dev/null || true
|
||||
docker run -d --name trwebocr --restart always -p 18083:8089 mmmz/trwebocr:latest
|
||||
echo "TrWebOCR 启动完成"
|
||||
|
||||
echo "========== 2. 部署 LibreTranslate (端口 18084) =========="
|
||||
docker rm -f libretranslate 2>/dev/null || true
|
||||
docker run -d --name libretranslate --restart always \
|
||||
-p 18084:5000 \
|
||||
-e LT_LOAD_ONLY=en,zh \
|
||||
-v lt-data:/home/libretranslate/.local \
|
||||
libretranslate/libretranslate
|
||||
echo "LibreTranslate 启动完成(语言模型下载中,约5分钟后可用)"
|
||||
|
||||
echo "========== 3. 启动 PPTist AI 后端 (端口 18086) =========="
|
||||
# 先安装依赖
|
||||
pip install fastapi uvicorn httpx 2>/dev/null || pip3 install fastapi uvicorn httpx
|
||||
# 杀掉旧进程
|
||||
pkill -f "pptist-ai-backend" 2>/dev/null || true
|
||||
sleep 1
|
||||
# 用 screen 启动后台运行
|
||||
screen -dmS pptist-ai python3 /root/gangyan/scripts/pptist-ai-backend.py
|
||||
echo "PPTist AI 后端启动完成"
|
||||
|
||||
echo "========== 4. 构建并部署 PPTist (端口 18085) =========="
|
||||
cd /root/gangyan/scripts/pptist-deploy
|
||||
docker rm -f pptist 2>/dev/null || true
|
||||
docker build -t pptist:latest .
|
||||
docker run -d --name pptist --restart always -p 18085:80 pptist:latest
|
||||
echo "PPTist 启动完成"
|
||||
|
||||
echo ""
|
||||
echo "========== 部署完成 =========="
|
||||
echo "TrWebOCR: http://localhost:18083"
|
||||
echo "LibreTranslate: http://localhost:18084"
|
||||
echo "PPTist: http://localhost:18085"
|
||||
echo "PPTist AI后端: http://localhost:18086"
|
||||
83
scripts/start-all.sh
Executable file
@@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env bash
|
||||
# 须用 bash 执行;若误用 sh/dash 会自动改用 bash 再跑一遍
|
||||
[ -n "${BASH_VERSION:-}" ] || exec /usr/bin/env bash "$0" ${1+"$@"}
|
||||
# 一键启动全部 gangyan 服务:基础设施 -> 后端 -> 前端。
|
||||
# 每一步直接 source 已有的 *-restart.sh,保持单一事实来源。
|
||||
# 日志:gangyan/logs/start-all.log
|
||||
set -u
|
||||
source "$(cd "$(dirname "$0")" && pwd)/common-restart.sh"
|
||||
LOG_FILE="$LOG_DIR/start-all.log"
|
||||
|
||||
run_step() {
|
||||
local label="$1" script="$2"
|
||||
log_tee "━━━━━━━━ ${label} ━━━━━━━━"
|
||||
if [ ! -x "$SCRIPT_DIR/$script" ]; then
|
||||
log_tee "跳过: $script 不存在或不可执行"
|
||||
return 0
|
||||
fi
|
||||
bash "$SCRIPT_DIR/$script" 2>&1 | tee -a "$LOG_FILE"
|
||||
local rc=${PIPESTATUS[0]}
|
||||
[ "$rc" -eq 0 ] || log_tee "警告: $script 返回 $rc"
|
||||
return 0
|
||||
}
|
||||
|
||||
port_check() {
|
||||
local port="$1" name="$2"
|
||||
if ss -tln 2>/dev/null | grep -qE ":${port}[[:space:]]"; then
|
||||
log_tee " [OK] ${name} :${port}"
|
||||
else
|
||||
log_tee " [!!] ${name} :${port} 未监听"
|
||||
fi
|
||||
}
|
||||
|
||||
log_tee "╔══════════════════════════════════════╗"
|
||||
log_tee "║ gangyan 全量启动 $(date '+%F %H:%M:%S') ║"
|
||||
log_tee "╚══════════════════════════════════════╝"
|
||||
|
||||
# 1) 基础设施(Docker 容器:MySQL / Redis / Milvus)
|
||||
run_step "[1/5] MySQL" mysql-restart.sh
|
||||
run_step "[2/5] Redis" redis-restart.sh
|
||||
run_step "[3/5] Milvus" milvus-restart.sh
|
||||
|
||||
# 2) 应用层(langchain-chat 会顺带起 pdf-convert-service + log-trim daemon)
|
||||
run_step "[4/6] langchain-chat + pdf-convert + log-trim" langchain-restart.sh
|
||||
run_step "[5/6] Java 后端" backend-restart.sh
|
||||
run_step "[6/6] 前端 vite" frontend-restart.sh
|
||||
|
||||
# 3) 辅助进程状态(excalidraw-ai / pptist-ai 当前以 screen -dmS 管理,非脚本化;本脚本仅做健康检查)
|
||||
log_tee "━━━━━━━━ 辅助进程检查 ━━━━━━━━"
|
||||
if pgrep -f 'excalidraw-ai-proxy\.py' >/dev/null; then
|
||||
log_tee " [OK] excalidraw-ai-proxy.py (screen -dmS aiproxy)"
|
||||
else
|
||||
log_tee " [!!] excalidraw-ai-proxy.py 未运行。手动启: screen -dmS aiproxy bash -c 'cd $GANGYAN_ROOT/scripts && /opt/software/miniconda3/envs/langchain-chat/bin/python excalidraw-ai-proxy.py'"
|
||||
fi
|
||||
if pgrep -f 'pptist-ai-backend\.py' >/dev/null; then
|
||||
log_tee " [OK] pptist-ai-backend.py (screen -dmS pptist-ai)"
|
||||
else
|
||||
log_tee " [!!] pptist-ai-backend.py 未运行。手动启: screen -dmS pptist-ai bash -c 'cd $GANGYAN_ROOT/scripts && /opt/software/miniconda3/envs/langchain-chat/bin/python pptist-ai-backend.py'"
|
||||
fi
|
||||
|
||||
# 4) 系统级 tools-nginx(systemd 管理,不在本脚本中重启)
|
||||
if systemctl is-active --quiet nginx 2>/dev/null; then
|
||||
log_tee " [OK] nginx (systemd, tools-nginx.conf -> :18000)"
|
||||
else
|
||||
log_tee " [!!] nginx 未运行。sudo systemctl start nginx"
|
||||
fi
|
||||
|
||||
# 5) 端口自检
|
||||
log_tee "━━━━━━━━ 端口自检(等 5 秒) ━━━━━━━━"
|
||||
sleep 5
|
||||
port_check 3306 "MySQL (3306 映射)"
|
||||
port_check 33306 "MySQL (33306 映射)"
|
||||
port_check 6379 "Redis"
|
||||
port_check 19530 "Milvus gRPC"
|
||||
port_check 9091 "Milvus HTTP"
|
||||
port_check 7861 "langchain-chat API"
|
||||
port_check 6006 "pdf-convert-service"
|
||||
port_check 8099 "Java 后端"
|
||||
port_check 3000 "vite 前端"
|
||||
port_check 18000 "tools-nginx (工具集合)"
|
||||
|
||||
log_tee ""
|
||||
log_tee "完成。完整日志: $LOG_FILE"
|
||||
log_tee "访问: http://<server-ip>:3000/metalinfo"
|
||||
147
scripts/tools-nginx.conf
Normal file
@@ -0,0 +1,147 @@
|
||||
server {
|
||||
listen 18000;
|
||||
client_max_body_size 500M;
|
||||
|
||||
# ===== 1. Stirling PDF =====
|
||||
# <base href="/"> 导致相对路径从根开始,用sub_filter改成/pdf/
|
||||
# context-path=/pdf,所有PDF资源都在 /pdf/ 下,无冲突
|
||||
location /pdf/ {
|
||||
proxy_pass http://127.0.0.1:18080/pdf/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_buffering off;
|
||||
client_max_body_size 500M;
|
||||
}
|
||||
|
||||
# ===== 2. Excalidraw =====
|
||||
location /draw/ {
|
||||
proxy_pass http://127.0.0.1:18081/;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
location /assets/ { proxy_pass http://127.0.0.1:18081/assets/; }
|
||||
location /v1/ai/ {
|
||||
proxy_pass http://127.0.0.1:18082/v1/ai/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_read_timeout 60s;
|
||||
proxy_buffering off;
|
||||
}
|
||||
|
||||
# ===== 3. TrWebOCR =====
|
||||
# 不用 sub_filter(后端返回 gzip 无法改写),直接用精确文件名 location
|
||||
location /ocr/ {
|
||||
proxy_pass http://127.0.0.1:18083/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_buffering off;
|
||||
}
|
||||
# OCR 的 JS/CSS(精确文件名,不会和 Overleaf 冲突)
|
||||
location /js/app.7dd3e457.js { proxy_pass http://127.0.0.1:18083/js/app.7dd3e457.js; }
|
||||
location /js/chunk-vendors.ae13d15d.js { proxy_pass http://127.0.0.1:18083/js/chunk-vendors.ae13d15d.js; }
|
||||
location /css/app.77d50329.css { proxy_pass http://127.0.0.1:18083/css/app.77d50329.css; }
|
||||
location /css/chunk-vendors.9d96bc97.css { proxy_pass http://127.0.0.1:18083/css/chunk-vendors.9d96bc97.css; }
|
||||
# OCR 的 API
|
||||
location /api/tr-run/ {
|
||||
proxy_pass http://127.0.0.1:18083/api/tr-run/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_buffering off;
|
||||
client_max_body_size 50M;
|
||||
}
|
||||
location /tools/ocr_text/ {
|
||||
proxy_pass http://127.0.0.1:18083/tools/ocr_text/;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
|
||||
# ===== 4. LibreTranslate =====
|
||||
location /translate/ {
|
||||
proxy_pass http://127.0.0.1:18084/;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
location /static/ { proxy_pass http://127.0.0.1:18084/static/; }
|
||||
location /languages { proxy_pass http://127.0.0.1:18084/languages; }
|
||||
location /frontend/settings { proxy_pass http://127.0.0.1:18084/frontend/settings; }
|
||||
location /detect { proxy_pass http://127.0.0.1:18084/detect; }
|
||||
|
||||
# ===== 5. PPTist =====
|
||||
location /ppt/ {
|
||||
proxy_pass http://127.0.0.1:18085/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_buffering off;
|
||||
}
|
||||
location /pptapi/ {
|
||||
proxy_pass http://127.0.0.1:18086/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection "";
|
||||
proxy_buffering off;
|
||||
proxy_cache off;
|
||||
}
|
||||
|
||||
# ===== 6. imgcompress =====
|
||||
location /imgcompress/ {
|
||||
proxy_pass http://127.0.0.1:18087/;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
location /_next/ { proxy_pass http://127.0.0.1:18087/_next/; }
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:18087/api/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_buffering off;
|
||||
client_max_body_size 500M;
|
||||
}
|
||||
|
||||
# ===== 7. Lama Cleaner =====
|
||||
# sub_filter 不生效(后端 gzip),用精确 location 匹配所有 API 和静态资源
|
||||
location /lama/ {
|
||||
proxy_pass http://127.0.0.1:18088/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_buffering off;
|
||||
}
|
||||
# Lama 静态资源(精确文件名)
|
||||
location = /static/js/main.1bd455bc.js { proxy_pass http://127.0.0.1:18088/static/js/main.1bd455bc.js; }
|
||||
location = /static/css/main.c28d98ca.css { proxy_pass http://127.0.0.1:18088/static/css/main.c28d98ca.css; }
|
||||
# Lama API(全部用精确路径)
|
||||
location = /inpaint {
|
||||
proxy_pass http://127.0.0.1:18088/inpaint;
|
||||
proxy_set_header Host $host;
|
||||
proxy_buffering off;
|
||||
proxy_read_timeout 300s;
|
||||
client_max_body_size 500M;
|
||||
}
|
||||
location = /inputimage { proxy_pass http://127.0.0.1:18088/inputimage; proxy_set_header Host $host; proxy_buffering off; }
|
||||
location = /model { proxy_pass http://127.0.0.1:18088/model; proxy_set_header Host $host; }
|
||||
location = /is_desktop { proxy_pass http://127.0.0.1:18088/is_desktop; }
|
||||
location = /is_disable_model_switch { proxy_pass http://127.0.0.1:18088/is_disable_model_switch; }
|
||||
location = /is_enable_file_manager { proxy_pass http://127.0.0.1:18088/is_enable_file_manager; }
|
||||
location = /interactive_seg { proxy_pass http://127.0.0.1:18088/interactive_seg; proxy_set_header Host $host; }
|
||||
location = /save_image { proxy_pass http://127.0.0.1:18088/save_image; proxy_set_header Host $host; client_max_body_size 500M; }
|
||||
|
||||
# ===== 8. webp2jpg =====
|
||||
location /webp2jpg/ {
|
||||
proxy_pass http://127.0.0.1:18089/;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
location /cdn/ { proxy_pass http://127.0.0.1:18089/cdn/; }
|
||||
|
||||
# ===== 9. Overleaf =====
|
||||
location /overleaf/ {
|
||||
proxy_pass http://127.0.0.1:18090/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_buffering off;
|
||||
}
|
||||
|
||||
# ===== 10. LaTeX 公式编辑器 =====
|
||||
location /latex/ {
|
||||
proxy_pass http://127.0.0.1:18091/;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
|
||||
# ===== 默认回落到 Overleaf =====
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:18090;
|
||||
proxy_set_header Host $host;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_buffering off;
|
||||
}
|
||||
}
|
||||
58
start_all.sh
@@ -1,58 +0,0 @@
|
||||
#!/bin/bash
|
||||
# 启动 gangyan 项目所有服务(与 /opt/start_all.sh 同步维护时可覆盖过去)
|
||||
|
||||
echo "启动 gangyan 项目服务..."
|
||||
|
||||
# 1. 启动 Docker 服务
|
||||
echo "[1/4] 启动 Docker 服务..."
|
||||
cd /opt/download/oss_files/gangyan-deploy/gangyan/milvus && docker compose up -d
|
||||
cd /opt/download/oss_files/gangyan-deploy/gangyan/mysql/mysql-8.4.4 && docker compose up -d
|
||||
docker start redis-server 2>/dev/null || docker run -d --name redis-server -p 6379:6379 redis:7-alpine
|
||||
|
||||
# 2. 启动 Java 后端(MySQL 33306 / Redis 见 jar 内 application-yj.yml;勿在此加 -Dspring.datasource* 以免覆盖端口)
|
||||
echo "[2/4] 启动 Java 后端..."
|
||||
export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64
|
||||
export PATH=$JAVA_HOME/bin:$PATH
|
||||
pkill -f "chat_web_yj.jar" 2>/dev/null
|
||||
sleep 2
|
||||
cd /opt/download/oss_files/gangyan-deploy/gangyan/backend
|
||||
LOCAL_CFG="/opt/download/oss_files/gangyan-deploy/gangyan/backend/application-local.yml"
|
||||
EXTRA_JAVA=()
|
||||
[ -f "$LOCAL_CFG" ] && EXTRA_JAVA=(-Dspring.config.additional-location="file:${LOCAL_CFG}")
|
||||
nohup java -jar \
|
||||
-Xms512m -Xmx2048m \
|
||||
"${EXTRA_JAVA[@]}" \
|
||||
-Dspring.profiles.active=yj \
|
||||
chat_web_yj.jar > nohup.out 2>&1 &
|
||||
|
||||
# 3. 启动前端
|
||||
echo "[3/4] 启动前端..."
|
||||
if ! pgrep -f "vite" > /dev/null; then
|
||||
cd /opt/download/oss_files/gangyan-deploy/gangyan/chat_web_front
|
||||
nohup npm run dev > nohup.out 2>&1 &
|
||||
fi
|
||||
|
||||
# 4. 启动 langchain-chat(可选)
|
||||
# --all-api:仅 API(7861)+依赖进程,不启 Streamlit WebUI;-a 需要 pip install streamlit
|
||||
echo "[4/4] 启动 langchain-chat(可选)..."
|
||||
read -p "是否启动 langchain-chat? (y/n) " -n 1 -r
|
||||
echo
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
source /opt/software/miniconda3/etc/profile.d/conda.sh
|
||||
conda activate langchain-chat
|
||||
cd /opt/download/oss_files/gangyan-deploy/gangyan/langchain-chat
|
||||
export PYTHONPATH="/opt/download/oss_files/gangyan-deploy/gangyan/langchain-chat"
|
||||
nohup python startup.py --all-api > langchain.log 2>&1 &
|
||||
echo "langchain-chat 启动中(API :7861),查看日志: tail -f langchain.log"
|
||||
# PDF 预览依赖的本地转换微服务(:6006,PyMuPDF 抽文本→Markdown)
|
||||
bash /opt/download/oss_files/gangyan-deploy/gangyan/scripts/pdf-convert-service.sh
|
||||
echo "pdf-convert-service 已尝试启动(:6006),日志: tail -f /opt/download/oss_files/gangyan-deploy/gangyan/logs/pdf-convert-service.log"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "服务启动完成!"
|
||||
echo " 前端: http://localhost:3000/metalinfo"
|
||||
echo " 后端: http://localhost:8099/chat_web_backend"
|
||||
echo " MySQL: localhost:33306 (Docker 映射,勿用 3306 连宿主)"
|
||||
echo " Redis: localhost:6379"
|
||||
echo " Milvus: localhost:19530"
|
||||