Compare commits

..

56 Commits

Author SHA1 Message Date
wyxfzgg 7f7001e10d fix(devops): helpcenter流水线同步3001端口与env-file参数,避免部署丢失AI代理 2026-07-08 00:30:48 +08:00
wyxfzgg c7173aa2f7 fix(ai): 改进中文检索(bigram+标题加分)并优化提示词,修复'找到了文档却答不出步骤' 2026-07-08 00:28:44 +08:00
wyxfzgg 6a1f97c8a4 Merge remote-tracking branch 'origin/master' 2026-07-07 23:50:55 +08:00
wyxfzgg a087222a45 feat: 新增可拖拽AI助手(服务器共享Key模式)
- 可拖拽浮动AI助手组件(src/components/AIAssistant)
- 客户端BM25检索 + 知识库(scripts/build-knowledge.js, docs->knowledge.json)
- 后端代理(ai-proxy/server.js): Key存服务器环境变量, 流式转发, 不进代码
- Dockerfile同时启动站点(3000)+代理(3001), 自动生成知识库
- 流水线wyx_helpcenter.yml: docker run用--env-file注入Key, 暴露3001
- 回答只来自帮助中心文档, 无关问题回'无关'
2026-07-07 23:46:10 +08:00
lindiwen23 f33df5e56b Update 创建第一个开源项目.md 2026-05-14 15:28:29 +08:00
lindiwen23 d4ee113de4 Update 创建第一个开源项目.md 2026-05-14 14:41:13 +08:00
lindiwen23 8a6b8d13ea Update 创建第一个开源项目.md 2026-05-14 14:27:19 +08:00
lindiwen23 6aa2466fb1 Merge pull request '完成新功能 F5+F6+F8+F9+F10+F11+F12' (#6) from ldw into master 2026-05-13 22:46:07 +08:00
林迪文 2b1fa0a531 feat: 完成新功能 F5+F6+F8+F9+F10+F11+F12 及 DevOps 流水线
F5: 新增 FAQ 常见问题页面
F6: 新增 Git 命令速查表
F8: 页内目录导航增强 (TOC 配置 + 样式)
F9: 搜索快捷键提示(搜索框 placeholder)
F10: 新增贡献指南页面
F11: 启用国际化英文支持(localeDropdown + 英文首页)
F12: PWA 离线阅读支持(plugin-pwa + manifest.json)
DevOps: 更新 wyx_helpcenter.yml 增加 nginx 重启步骤
修复: 补充缺失的 ReadingProgress 组件

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 22:37:52 +08:00
蒋天翔 42015bf56d 完成F2F4F5F7任务 2026-05-13 22:08:29 +08:00
wyxttn 7988de5fe0 Merge pull request '新增功能——Git 命令速查页面,返回顶部按钮,文档页面反馈组件' (#5) from fix/wyx-errors into master 2026-05-13 21:07:58 +08:00
wyxfzgg f9436dcda3 merge: 合并master,解决Dockerfile冲突 2026-05-13 21:07:29 +08:00
wyxfzgg 5474210fe4 feat: 新增Git命令速查页面 2026-05-13 21:06:13 +08:00
wyxfzgg 2a1dd93127 feat: 新增文档反馈组件、返回顶部按钮、Git命令速查页面 2026-05-13 20:36:42 +08:00
wyxfzgg 78b4908075 feat: 新增文档反馈组件、返回顶部按钮、Git命令速查页面 2026-05-13 20:35:09 +08:00
jiangtx f446e83fd3 Update 注册GitLink账号.md 2026-05-13 20:07:10 +08:00
jiangtx 5d98b2ef7a Merge pull request '完成一些任务' (#3) from jtx_branch into master 2026-05-13 16:48:40 +08:00
蒋天翔 e415b20bf3 完成一些任务 2026-05-13 16:44:02 +08:00
wyxttn 8af4f9604a Update 注册GitLink账号.md 2026-05-13 12:27:24 +08:00
wyxttn e4531b35d7 Update 注册GitLink账号.md 2026-05-13 10:58:04 +08:00
wyxttn 8d29e574e6 refactor: .devops/wyx_helpcenter.yml 2026-05-13 10:50:51 +08:00
wyxfzgg ad49dc1818 彻底修复:服务器直接clone + 修补ProgressPlugin兼容性 2026-05-13 10:49:22 +08:00
wyxfzgg 68b20ab6be 修复Docker构建:npm install + 修复webpack ProgressPlugin兼容性 2026-05-13 10:34:49 +08:00
wyxttn 8adcfce2c8 feat: .devops/wyx_helpcenter.yml 2026-05-13 10:23:31 +08:00
wyxttn a31672a812 refactor: delete .devops/wyx_helpcenter.yml 2026-05-13 10:22:59 +08:00
wyxfzgg ecafcd557d 修复Docker构建:改用npm install替代yarn install 2026-05-13 10:10:36 +08:00
wyxfzgg 1899276587 更新流水线:SCP+Docker本地构建方式 2026-05-13 10:00:46 +08:00
wyxttn 898206287a refactor: .devops/wyx_helpcenter.yml 2026-05-13 10:00:18 +08:00
wyxttn c040eecb75 feat: .devops/wyx_helpcenter.yml 2026-05-13 09:59:23 +08:00
wyxttn d74dc50617 refactor: delete .devops/wyx_helpcenter.yml 2026-05-13 09:59:11 +08:00
wyxfzgg 6a66a84c40 更新流水线:SCP+Docker本地构建方式 2026-05-13 09:58:30 +08:00
wyxfzgg 4800fdd2f0 更新流水线:SCP+Docker本地构建方式 2026-05-13 09:56:46 +08:00
wyxttn e505f51d01 refactor: .devops/wyx_helpcenter.yml 2026-05-13 09:44:45 +08:00
wyxttn 8bc8dc639f feat: .devops/wyx_helpcenter.yml 2026-05-13 09:43:14 +08:00
wyxttn 57cb1b8f39 refactor: delete .devops/helpcenter-docker.yml 2026-05-13 09:42:31 +08:00
wyxttn 9a938c7d03 feat: .devops/helpcenter-docker.yml 2026-05-13 09:39:08 +08:00
wyxfzgg 5fbd50a4c5 修复密钥引用格式 2026-05-13 09:37:15 +08:00
wyxttn cfc94177f8 refactor: delete .devops/helpcenter-docker.yml 2026-05-13 09:33:33 +08:00
wyxfzgg 6654e48286 更新流水线:使用Docker方式构建部署 2026-05-13 09:29:00 +08:00
wyxfzgg 34dc1b8015 更新流水线:使用Docker方式构建部署 2026-05-13 09:27:25 +08:00
wyxttn f53cdf47e8 feat: .devops/helpcenter-docker.yml 2026-05-13 09:24:39 +08:00
wyxttn c742cf592d refactor: delete .devops/helpcenter.yml 2026-05-13 09:24:32 +08:00
wyxttn e5b018a6d5 Merge pull request '文档修复' (#2) from ldw into master 2026-05-13 09:21:30 +08:00
wyxttn c0e2f21eb7 feat: .devops/helpcenter.yml 2026-05-13 09:16:32 +08:00
wyxttn 0d5853a79a refactor: delete .devops/helpcenter.yml 2026-05-13 09:16:20 +08:00
wyxttn 9ddf5dd6e2 refactor: delete .devops/未命名项目.yml 2026-05-13 09:15:58 +08:00
wyxfzgg 79feddd606 feat: 改用nodejs构建+SCP部署流水线 2026-05-13 09:13:41 +08:00
林迪文 ded69412eb fix: 完成同学C文档修复任务 C-1 到 C-18
基于 ldw工作.md 完成所有18项文档修复,包含:
- 错别字修正(的的→的, 左测→左侧, 进→仅, 接受→接收等)
- 图片路径修复(重晴鸟→重睛鸟, 建站工具路径修正)
- H1标题修正(确实开源帮助中心→正确的标题)
- Bot组件移动到 src/components/ 并更新引用路径
- 平台公告补充内容
- 标签和发行版管理删除失效图片引用
- 搜索插件升级至 0.55.1

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 09:04:29 +08:00
wyxfzgg f4f714b927 fix: Dockerfile升级Node.js到v20以兼容Docusaurus 3.9 2026-05-12 23:04:40 +08:00
wyxfzgg e85a95cc01 fix: Dockerfile改用docker.1ms.run镜像源 2026-05-12 22:43:54 +08:00
wyxfzgg 19be2a3d5b fix: Dockerfile基础镜像改为阿里云镜像源 2026-05-12 22:31:10 +08:00
wyxfzgg 2405b3c497 fix: Dockerfile基础镜像改为阿里云镜像源 2026-05-12 22:17:41 +08:00
wyxttn 792740c3af feat: .devops/helpcenter.yml 2026-05-12 21:19:24 +08:00
wyxttn 1b013c546f Merge pull request 'wyx修改的错误' (#1) from fix/wyx-errors into master 2026-05-12 20:22:09 +08:00
wyxfzgg 1ea850b884 fix:wyx_error 2026-05-12 16:54:27 +08:00
wyxttn 602bf0ddb7 refactor: .devops/未命名项目.yml 2026-05-12 16:50:26 +08:00
149 changed files with 5386 additions and 5104 deletions

40
.devops/helpcenter.yml Normal file
View File

@ -0,0 +1,40 @@
version: 2
name: wyx_helpcenter
description: "代码提交时自动构建并部署到服务器"
trigger:
webhook: gitlink@1.0.0
event:
- ref: push
ruleset-operator: AND
global:
concurrent: 1
workflow:
- ref: start
name: 开始
task: start
- ref: git_clone_0
name: git clone
task: git_clone@1.2.9
input:
remote_url: '"https://gitlink.org.cn/wyxttn/gitlink_help_center.git"'
ref: '"refs/heads/master"'
commit_id: '""'
depth: 1
needs:
- start
- ref: ssh_cmd_0
name: 部署到服务器
task: ssh_cmd@1.1.1
input:
ssh_pass: ((wyx_helpcenter.ssh_pass))
ssh_ip: '"121.41.212.97"'
ssh_port: '"22"'
ssh_user: '"root"'
ssh_cmd: '"cd /root && rm -rf gitlink_help_center && git clone --depth=1 https://gitlink.org.cn/wyxttn/gitlink_help_center.git && cd gitlink_help_center && docker build --no-cache -t help_center . && docker stop help_center || true && docker rm help_center || true && (test -f /root/.ai_env || { echo AI_API_KEY= >> /root/.ai_env; echo AI_BASE_URL=https://api.deepseek.com/v1 >> /root/.ai_env; echo AI_MODEL=deepseek-chat >> /root/.ai_env; }) && docker run -d -p 3000:3000 -p 127.0.0.1:3001:3001 --env-file /root/.ai_env --name help_center help_center"'
needs:
- git_clone_0
- ref: end
name: 结束
task: end
needs:
- ssh_cmd_0

View File

@ -0,0 +1,51 @@
version: 2
name: wyx_helpcenter
description: "代码提交时自动构建并部署到服务器"
trigger:
webhook: gitlink@1.0.0
event:
- ref: push
ruleset-operator: AND
global:
concurrent: 1
workflow:
- ref: start
name: 开始
task: start
- ref: git_clone_0
name: git clone
task: git_clone@1.2.9
input:
remote_url: '"https://gitlink.org.cn/wyxttn/gitlink_help_center.git"'
ref: '"refs/heads/master"'
commit_id: '""'
depth: 1
needs:
- start
- ref: ssh_cmd_0
name: 构建并部署
task: ssh_cmd@1.1.1
input:
ssh_pass: ((wyx_helpcenter.ssh_pass))
ssh_ip: '"121.41.212.97"'
ssh_port: '"22"'
ssh_user: '"root"'
ssh_cmd: '"cd /root && rm -rf gitlink_help_center && git clone --depth=1 https://gitlink.org.cn/wyxttn/gitlink_help_center.git && cd gitlink_help_center && docker build --no-cache -t help_center . && docker stop help_center || true && docker rm help_center || true && (test -f /root/.ai_env || { echo AI_API_KEY= >> /root/.ai_env; echo AI_BASE_URL=https://api.deepseek.com/v1 >> /root/.ai_env; echo AI_MODEL=deepseek-chat >> /root/.ai_env; }) && docker run -d -p 3000:3000 -p 127.0.0.1:3001:3001 --env-file /root/.ai_env --name help_center help_center"'
needs:
- git_clone_0
- ref: ssh_cmd_1
name: 重启nginx
task: ssh_cmd@1.1.1
input:
ssh_pass: ((wyx_helpcenter.ssh_pass))
ssh_ip: '"121.41.212.97"'
ssh_port: '"22"'
ssh_user: '"root"'
ssh_cmd: '"service nginx restart"'
needs:
- ssh_cmd_0
- ref: end
name: 结束
task: end
needs:
- ssh_cmd_0

View File

@ -0,0 +1,53 @@
version: 2
name: helpcenter-docker
description: "代码提交时自动构建Docker镜像并部署到服务器"
global:
concurrent: 1
trigger:
webhook: gitlink@1.0.0
event:
- ref: push
ruleset-operator: AND
workflow:
- ref: start
name: 开始
task: start
- ref: git_clone_0
name: git clone
task: git_clone@1.2.9
input:
remote_url: '"https://gitlink.org.cn/wyxttn/gitlink_help_center.git"'
ref: '"refs/heads/master"'
commit_id: '""'
depth: 1
needs:
- start
- ref: scp_resource_0
name: scp拷贝代码到服务器
task: scp_resource@1.4.3
input:
ssh_pass: ((server_ssh.password))
ssh_ip: '"121.41.212.97"'
ssh_port: '"22"'
ssh_user: '"root"'
remote_file: '"/root/gitlink_help_center"'
local_file: ((git_clone_0.git_path))
file_content: '""'
needs:
- git_clone_0
- ref: ssh_cmd_0
name: Docker构建并运行
task: ssh_cmd@1.1.1
input:
ssh_pass: ((server_ssh.password))
ssh_ip: '"121.41.212.97"'
ssh_port: '"22"'
ssh_user: '"root"'
ssh_cmd: '"cd /root/gitlink_help_center && docker build -t gitlink_help_center . && docker rm -f gitlink_help_center 2>/dev/null; docker run -d --name gitlink_help_center -p 3000:3000 gitlink_help_center"'
needs:
- scp_resource_0
- ref: end
name: 结束
task: end
needs:
- ssh_cmd_0

View File

@ -1,62 +0,0 @@
version: 2
name: 未命名项目
description: ""
global:
concurrent: 1
trigger:
webhook: gitlink@1.0.0
event:
- ref: push
ruleset-operator: AND
workflow:
- ref: start
name: 开始
task: start
- ref: git_clone_0
name: git clone
task: git_clone@1.2.9
input:
username: ((ganshihao21.ganshihao21_user))
password: ((ganshihao21.ganshihao21))
remote_url: '"https://gitlink.org.cn/ganshihao21/reposync.git"'
ref: '"refs/heads/master"'
commit_id: '""'
depth: 1
needs:
- start
- ref: docker_image_build_0
name: docker镜像构建
task: docker_image_build@1.6.0
input:
docker_username: ((nudt_devops.nudt_devops))
docker_password: ((nudt_devops.nudt_devops))
image_name: '"registry.cn-guangzhou.aliyuncs.com/nudt_devops/reposync_group5"'
image_tag: '"latest"'
registry_address: '"registry.cn-guangzhou.aliyuncs.com"'
docker_file: '"Dockerfile"'
docker_build_path: '"."'
workspace: git_clone_0.git_path
image_push: true
build_args: '""'
needs:
- git_clone_0
- ref: ssh_cmd_0
name: ssh执行命令
task: ssh_cmd@1.1.1
input:
ssh_pass: ((ssh_key.ssh_key))
ssh_ip: '"47.96.164.145"'
ssh_port: '"22"'
ssh_user: '"root"'
ssh_cmd: '"docker stop abc_group5 && docker rm abc_group5 && docker pull
registry.cn-guangzhou.aliyuncs.com/nudt_devops/reposync_group5:latest &&
docker run -d -p 3000:3000 --name abc_group5
registry.cn-guangzhou.aliyuncs.com/nudt_devops/reposync_group5:latest"'
needs:
- docker_image_build_0
- ref: end
name: 结束
task: end
needs:
- ssh_cmd_0

3
.gitignore vendored
View File

@ -11,6 +11,9 @@ deploy.sh
.docusaurus
.cache-loader
# Sensitive files
gitee私人令牌.txt
# Misc
.DS_Store
.env.local

View File

@ -1,10 +1,17 @@
FROM node:18-alpine
FROM docker.1ms.run/library/node:20-alpine
LABEL maintainer="yuankaifneg <2894340009@qq.com>"
WORKDIR /gitlink_help_center
COPY ./ /gitlink_help_center/
RUN yarn install
RUN npm install --legacy-peer-deps
RUN node -e "const fs=require('fs');const d='node_modules/@docusaurus/bundler/lib';fs.readdirSync(d).filter(f=>f.endsWith('.js')).forEach(f=>{const p=d+'/'+f;let c=fs.readFileSync(p,'utf8');if(c.includes('ProgressPlugin')){c=c.replace(/(new\s+[\w.]*ProgressPlugin)\(\s*\{[\s\S]*?\}\s*\)/g,'$1()');fs.writeFileSync(p,c);console.log('patched '+f);}});"
ENV CI=true
# 预生成 AI 助手知识库(来自 docs/),确保文档更新后知识库始终最新
RUN node scripts/build-knowledge.js
RUN npm run build -- --locale zh-cn
CMD ["npm", "run", "serve"]
# 同时运行:静态站点(docusaurus serve, 3000) + AI 代理(ai-proxy, 3001)
CMD ["sh", "-c", "node ai-proxy/server.js & npm run serve"]

128
ai-proxy/DEPLOY.md Normal file
View File

@ -0,0 +1,128 @@
# 把 AI 助手部署到线上http://121.41.212.97/
> 模式:**服务器共享 Key**。访客打开网站即可直接提问,无需填任何 Key。
> Key 只存在服务器的 `/root/.ai_env` 文件里,**不进代码、不进 git、不上传远端**。
---
## 架构
```
浏览器(访客) ──http://121.41.212.97/──> nginx(:80)
├── 其它路径 ──> 容器:3000 (docusaurus serve 静态站点)
└── /api/ai ──> 容器:3001 (ai-proxy 后端代理)
│ 读取 /root/.ai_env 里的 Key
大模型(DeepSeek 等)
```
容器内同时跑两个进程:`docusaurus serve`(站点,3000) + `ai-proxy`(代理,3001)。
Key 通过 `docker run --env-file /root/.ai_env` 注入,文件只在服务器上。
---
## 一、服务器一次性配置(做一次,之后不用再动)
SSH 登录服务器后执行:
### 1) 创建 Key 文件(**只在本机,不提交**
```bash
cat > /root/.ai_env << 'EOF'
AI_API_KEY=sk-把这里换成你的真实Key
AI_BASE_URL=https://api.deepseek.com/v1
AI_MODEL=deepseek-chat
EOF
chmod 600 /root/.ai_env
```
> 想换厂商?改 `AI_BASE_URL``AI_MODEL` 即可,例如:
> - 通义千问:`AI_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1``AI_MODEL=qwen-plus`
> - 智谱 GLM`AI_BASE_URL=https://open.bigmodel.cn/api/paas/v4``AI_MODEL=glm-4-flash`
### 2) 给 nginx 加一条 `/api/ai` 反代规则
找到你的站点配置(之前是 `/etc/nginx/conf.d/helpcenter.conf`),在 `location /` **之前**加一个 `location /api/ai`
```bash
cat > /etc/nginx/conf.d/helpcenter.conf << 'EOF'
server {
listen 80;
server_name 121.41.212.97;
# AI 助手后端代理 —— 必须放在 location / 之前
location /api/ai {
proxy_pass http://127.0.0.1:3001;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# 流式响应(SSE)需要:关闭缓冲、延长超时
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 300s;
chunked_transfer_encoding on;
}
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
EOF
nginx -t && service nginx reload
```
> 关键:`proxy_buffering off;` —— 不关掉的话流式回答会被 nginx 缓存到全部生成完才返回,体感像卡住。
---
## 二、发布代码上线(每次更新都这样)
代码改动已就绪Dockerfile 会自动构建知识库 + 同时启动站点和代理;流水线会用 `--env-file /root/.ai_env` 注入 Key
在你本地Windows
```bash
cd gitlink_help_center
git add Dockerfile .devops/wyx_helpcenter.yml package.json \
scripts/ src/components/AIAssistant/ ai-proxy/
git commit -m "feat: 新增可拖拽AI助手(服务器共享Key模式)"
# 当前在 fix/wyx-errors 分支;流水线拉的是 master所以合并到 master 再推
git checkout master
git merge fix/wyx-errors
git push origin master # 推送后流水线自动构建并部署到 121.41.212.97
```
> 推送后到 GitLink 仓库 →「建木」查看流水线执行情况。
> (若你启用的是 `.devops/helpcenter.yml` 而非 `wyx_helpcenter.yml`,把同样的改动同步过去。)
---
## 三、验证
部署完成后,浏览器打开 http://121.41.212.97/
1. 右下角应出现蓝色 **AI** 按钮(可拖拽)。
2. 点开 → 直接提问「如何创建仓库?」→ 应流式返回基于文档的回答,并带 `📎 相关文档` 链接。
3. 问「今天天气怎么样?」→ 应回复「无关」。
4. 若助手回复「服务器尚未配置 AI_API_KEY…」→ 说明 `/root/.ai_env` 还没填真实 Key回服务器改好后重新触发一次流水线`docker restart help_center`)即可。
命令行快速自检(在服务器上):
```bash
docker ps | grep help_center # 容器在跑
docker logs help_center 2>&1 | grep ai-proxy # 看到 [ai-proxy] listening on ...:3001
curl -X POST http://127.0.0.1/api/ai -H "Content-Type: application/json" -d '{"question":"测试","context":[]}'
```
---
## 四、安全说明
- **Key 从不进代码**`/root/.ai_env` 只在服务器磁盘上,`.devops/wyx_helpcenter.yml` 里只写 `--env-file /root/.ai_env`,不含 Key 明文。`git push` 绝不会泄露。
- 代理端口 3001 仅绑定到 `127.0.0.1``-p 127.0.0.1:3001:3001`),公网无法直连,只有本机 nginx 能访问。
- 如担心 Key 被滥用产生费用,可在 DeepSeek 后台设置用量上限。

151
ai-proxy/server.js Normal file
View File

@ -0,0 +1,151 @@
/**
* GitLink 帮助中心 AI 助手 后端代理极简版
*
* 作用把浏览器的提问转发给大模型API Key 只存在服务器环境变量里
* 不出现在代码不进 git不上传远端访客无需填写任何 Key
*
* 运行node ai-proxy/server.js 默认监听 3001
*
* 需要的环境变量通过 docker run --env-file /root/.ai_env 注入
* AI_API_KEY 大模型 API Key必填
* AI_BASE_URL OpenAI 兼容接口地址默认 https://api.deepseek.com/v1
* AI_MODEL 模型名默认 deepseek-chat
* PORT 代理监听端口默认 3001
*
* 仅依赖 Node 内置模块 + 全局 fetchNode 20 自带无需 npm install
*/
const http = require('http');
const PORT = Number(process.env.PORT) || 3001;
const AI_BASE_URL = (process.env.AI_BASE_URL || 'https://api.deepseek.com/v1').replace(/\/+$/, '');
const AI_MODEL = process.env.AI_MODEL || 'deepseek-chat';
const AI_API_KEY = process.env.AI_API_KEY || '';
const SYSTEM_PROMPT = `你是「GitLink 帮助中心」的智能助手,专门帮助用户解答关于 GitLink 平台确实开源CCF 官方开源创新服务平台)使用方法的问题。
必须遵守的规则
1. 你只能依据下面帮助中心参考资料里的内容来回答用户问题禁止使用资料以外的知识禁止编造
2. 在多段资料中只要有一段与问题相关就据其作答简洁分点保留关键步骤入口位置和按钮名称
3. 帮助中心文档以截图为主文字为辅若资料只给出了入口或概述而省略了图文步骤请据此简述操作路径并在末尾引导用户点开文档链接查看完整图文步骤不要因为没看到详细步骤就拒绝回答
4. 回答末尾若资料中包含对应文档路径形如 /代码库管理/仓库创建请用一行给出📎 相关文档<路径>方便用户点击查看
5. 只有当所有资料都与问题毫无关系例如闲聊天气其它产品其它网站或资料完全为空时才只回复两个字无关
帮助中心参考资料
{CONTEXT}
`;
function buildMessages(question, context) {
const ctx = Array.isArray(context) && context.length
? context
.map((c, i) => `【资料${i + 1}】来源:${c.title || ''}${c.section ? ' / ' + c.section : ''}(页面 ${c.path || ''}\n${c.text || ''}`)
.join('\n\n')
: '(本次未检索到任何相关资料)';
return [
{ role: 'system', content: SYSTEM_PROMPT.replace('{CONTEXT}', ctx) },
{ role: 'user', content: question },
];
}
/** 读取请求体JSON */
function readBody(req) {
return new Promise((resolve) => {
let buf = '';
req.on('data', (c) => {
buf += c;
if (buf.length > 1e6) req.destroy(); // 防止超大请求
});
req.on('end', () => {
try { resolve(JSON.parse(buf || '{}')); }
catch (e) { resolve({}); }
});
req.on('error', () => resolve({}));
});
}
function sendJson(res, status, obj) {
const body = JSON.stringify(obj);
res.writeHead(status, {
'Content-Type': 'application/json; charset=utf-8',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
});
res.end(body);
}
const server = http.createServer(async (req, res) => {
// 简单 CORS 预检
if (req.method === 'OPTIONS') {
res.writeHead(204, {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
});
return res.end();
}
if (req.method === 'POST' && req.url.replace(/\?.*$/, '').endsWith('/api/ai')) {
if (!AI_API_KEY) {
return sendJson(res, 500, { error: '服务器尚未配置 AI_API_KEY请联系管理员在服务器上设置 /root/.ai_env 并重启容器。' });
}
const { question, context } = await readBody(req);
if (!question || typeof question !== 'string') {
return sendJson(res, 400, { error: '缺少 question 字段' });
}
const upstreamUrl = AI_BASE_URL.endsWith('/chat/completions')
? AI_BASE_URL
: `${AI_BASE_URL}/chat/completions`;
let upstream;
try {
upstream = await fetch(upstreamUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${AI_API_KEY}`,
},
body: JSON.stringify({
model: AI_MODEL,
messages: buildMessages(question, context),
temperature: 0.2,
stream: true,
}),
});
} catch (e) {
return sendJson(res, 502, { error: `无法连接大模型服务:${e.message}` });
}
if (!upstream.ok || !upstream.body) {
const t = await upstream.text().catch(() => '');
return sendJson(res, 502, { error: `大模型接口返回错误HTTP ${upstream.status})。${t.slice(0, 200)}` });
}
// 透传 SSE 流给浏览器
res.writeHead(200, {
'Content-Type': 'text/event-stream; charset=utf-8',
'Cache-Control': 'no-cache, no-transform',
Connection: 'keep-alive',
'Access-Control-Allow-Origin': '*',
});
try {
const reader = upstream.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { value, done } = await reader.read();
if (done) break;
res.write(decoder.decode(value));
}
} catch (e) {
// 连接中断,安静退出
}
return res.end();
}
// 其它路径一律 404静态站点由 docusaurus serve 在 3000 提供)
return sendJson(res, 404, { error: 'Not Found' });
});
server.listen(PORT, () => {
console.log(`[ai-proxy] listening on http://0.0.0.0:${PORT} (model=${AI_MODEL}, base=${AI_BASE_URL})`);
});

View File

@ -1,13 +1,13 @@
---
sidebar_label: 'bot安装'
sidebar_position: 1
sidebar_position: 1
---
import MyButton from './mybutton';
import MyButton from '../../src/components/mybutton';
<MyButton onClick={() => {
var pElements = document.getElementsByTagName('p');
var hElements = document.getElementsByTagName('h1');
var aElements = document.getElementsByTagName('a');
var pElements = document.getElementsByTagName('p');
var hElements = document.getElementsByTagName('h1');
var aElements = document.getElementsByTagName('a');
var hzh = ['bot安装','bot安装'];
var hen = ['Bot Installation','Bot Installation'];
var azh = ['跳到主要内容','','帮助中心','','','','','','','','','','','','','','','','','','','','','机器人(Bot)','编辑此页','上一页 bot市场','下一页bot 配置','网站首页','关于我们','教学实践','合作伙伴','API文档','Git常用命令','引擎使用手册','服务协议','官网邮箱gitlink@ccf.org.cn','QQ群','公众号'];
@ -15,55 +15,55 @@ var aen = ['Jump to main content','','Help Center','','','','','','','','','',''
var zhtranalate = ['Bot安装是进行bot安装和管理控制的重要模块主要包括bot安装、安装查询、安装管理等功能。','在bot详情页用户点击“安装此Bot”按钮后可以看到该bot工作所需的各项权限信息。若用户同意授予bot所需的相关权限即可进行安装。用户可选择将bot安装到所有仓库用户拥有的所有仓库也可以选择指定的仓库进行安装。','','在个人“设置”或者“仓库设置”中用户可以看到目前已经安装的Bot情况点击“配置”按钮可以对bot安装情况进行配置点击“卸载”按钮可以进行卸载。','','在bot安装配置页中用户可以掌握该bot的安装位置和工作状态。若用户需要更改bot的工作仓库时可以进行更改安装位置。bot的工作状态包括激活和挂起用户可根据需要对bot的状态进行调整将其挂起或者激活会影响到bot对仓库数据的访问权限。','','©Copyright 2024 CCF 开源发展委员会','Powered by Trustie& IntelliDE 京ICP备13000930号'];
var entranslate = ['Bot installation is an important module for bot installation and management control, which mainly includes functions such as bot installation, installation queries, and installation management.','On the bot details page, after clicking the "Install this bot" button, users can see the necessary permission information for the bot to work. If the user agrees to grant the necessary permissions to the bot, installation can proceed. Users can choose to install the bot in all warehouses (all warehouses owned by the user) or select a specified warehouse for installation.','','In personal "settings" or "warehouse settings", users can see the current installation status of bots. Click the "configure" button to configure the bot installation status, and click the "uninstall" button to uninstall.','','In the bot installation configuration page, users can grasp the installation location and working status of the bot. If the user needs to change the workspace of the bot, they can change the installation location. The working status of bots includes activation and suspension. Users can adjust the status of bots as needed. Suspending or activating bots can affect their access to warehouse data.','','© Copyright 2024 CCF Open Source Development Committee','Powered by Trust&IntelliDE Beijing ICP Preparation No. 13000930'];
if (pElements[0].innerText == zhtranalate[0]){
for (var i = 0; i < pElements.length; i++) {
for (var i = 0; i < pElements.length; i++) {
if (pElements[i].innerText != ''){
if (i < pElements.length){
pElements[i].innerText = entranslate[i];
pElements[i].innerText = entranslate[i];
}
}
}
}
else{
for (var i = 0; i < pElements.length; i++) {
for (var i = 0; i < pElements.length; i++) {
if (pElements[i].innerText != ''){
if (i < pElements.length){
pElements[i].innerText = zhtranalate[i];
pElements[i].innerText = zhtranalate[i];
}
}
}
}
if (hElements[0].innerText == hzh[0]){
for (var i = 0; i < hElements.length; i++) {
for (var i = 0; i < hElements.length; i++) {
if (hElements[i].innerText != ''){
if (i < hElements.length){
hElements[i].innerText = hen[i];
hElements[i].innerText = hen[i];
}
}
}
}
else{
for (var i = 0; i < hElements.length; i++) {
for (var i = 0; i < hElements.length; i++) {
if (hElements[i].innerText != ''){
if (i < hElements.length){
hElements[i].innerText = hzh[i];
hElements[i].innerText = hzh[i];
}
}
}
}
if (aElements[0].innerText == azh[0]){
for (var i = 0; i < aElements.length; i++) {
for (var i = 0; i < aElements.length; i++) {
if (aElements[i].innerText != ''){
if (i < aElements.length){
aElements[i].innerText = aen[i];
aElements[i].innerText = aen[i];
}
}
}
}
else{
for (var i = 0; i < aElements.length; i++) {
for (var i = 0; i < aElements.length; i++) {
if (aElements[i].innerText != ''){
if (i < aElements.length){
aElements[i].innerText = azh[i];
aElements[i].innerText = azh[i];
}
}
}
@ -73,7 +73,7 @@ else{
# bot安装
Bot安装是进行bot安装和管理控制的重要模块主要包括bot安装、安装查询、安装管理等功能。
Bot安装是进行bot安装和管理控制的重要模块主要包括bot安装、安装查询、安装管理等功能。
在bot详情页用户点击“安装此Bot”按钮后可以看到该bot工作所需的各项权限信息。若用户同意授予bot所需的相关权限即可进行安装。用户可选择将bot安装到所有仓库用户拥有的所有仓库也可以选择指定的仓库进行安装。

View File

@ -1,48 +1,48 @@
---
sidebar_label: 'bot市场'
sidebar_position: 4
sidebar_label: 'bot市场'
sidebar_position: 4
---
import MyButton from './mybutton';
import MyButton from '../../src/components/mybutton';
<MyButton onClick={() => {
var pElements = document.getElementsByTagName('p');
var hElements = document.getElementsByTagName('h1');
var hElements = document.getElementsByTagName('h1');
var hzh = ['bot市场','bot市场'];
var hen = ['Bot Market','Bot Market'];
var hen = ['Bot Market','Bot Market'];
var zhtranalate = ['Bot市场是进行Bot分享与复用的重要模块主要包括bot的搜索发现详情查看等功能。','Bot市场主页中展示了目前所有已经上架市场的bot简要信息包括bot的头像名称开发者简介和安装次数等信息用户可根据这些基本信息初步判断该bot是否符合自己的项目需求。','在bot市场主页中用户可以选择指定的bot种类筛选出特定分类的bot在这个种类范围内进行搜索与选择。','此外用户通过在搜索栏中输入关键字进行搜索可检索出内容包含指定关键字的相关bot。','用户可结合种类筛选和关键字搜索缩小范围在市场中快速找到符合项目相关需求的bot。','','在bot市场页中用户点击指定的bot卡片即可进入该bot的详情页。Bot的详情页包含bot的头像名称开发者种类和详细介绍等信息用户可在此掌握该bot的各项介绍进一步判断是否将其安装到指定仓库中。','','在bot详情页中若用户认为该bot满足自己的项目需求可点击“安装此Bot”按钮了解该bot的权限信息将其安装到指定的仓库中关于安装的更多介绍可见“Bot安装”部分。','去市场看看吧!','©Copyright 2024 CCF 开源发展委员会','Powered by Trustie& IntelliDE 京ICP备13000930号'];
var entranslate = ['The bot market is an important module for sharing and reusing bots, mainly including functions such as searching, discovering, and viewing details of bots.' , 'The Bot Market homepage displays brief information about all bots that have been put on the market, including their avatars, names, developers, introductions, and installation times. Users can use this basic information to preliminarily determine whether the bot meets their project requirements.' , 'On the bot market homepage, users can select a specific type of bot, filter out specific categories of bots, and search and select within this category range.' , 'In addition, users can search for relevant bots containing specified keywords by entering keywords in the search bar.' , 'Users can combine category filtering and keyword search to narrow down the scope and quickly find bots that meet project related needs in the market.' , '' , 'In the bot market page, users can click on the specified bot card to enter the details page of the bot. The details page of the bot includes information such as the bot avatar, name, developer, type, and detailed introduction. Users can grasp the various introductions of the bot here and further determine whether to install it in the designated warehouse.' , '' , 'In the bot details page, if users believe that the bot meets their project requirements, they can click the "Install this bot" button to learn about the bots permission information and install it in the designated warehouse. For more information on installation, please refer to the "Bot Installation" section' , 'Go take a look at the market!' , '©Copyright 2024 CCF Open Source Development Committee' , 'Powered by Trustie& IntelliDE 京ICP备13000930号'];
if (pElements[0].innerText == zhtranalate[0]){
for (var i = 0; i < pElements.length; i++) {
for (var i = 0; i < pElements.length; i++) {
if (pElements[i].innerText != ''){
if (i < pElements.length){
pElements[i].innerText = entranslate[i];
pElements[i].innerText = entranslate[i];
}
}
}
}
else{
for (var i = 0; i < pElements.length; i++) {
for (var i = 0; i < pElements.length; i++) {
if (pElements[i].innerText != ''){
if (i < pElements.length){
pElements[i].innerText = zhtranalate[i];
pElements[i].innerText = zhtranalate[i];
}
}
}
}
if (hElements[0].innerText == hzh[0]){
for (var i = 0; i < hElements.length; i++) {
for (var i = 0; i < hElements.length; i++) {
if (hElements[i].innerText != ''){
if (i < hElements.length){
hElements[i].innerText = hen[i];
hElements[i].innerText = hen[i];
}
}
}
}
else{
for (var i = 0; i < hElements.length; i++) {
for (var i = 0; i < hElements.length; i++) {
if (hElements[i].innerText != ''){
if (i < hElements.length){
hElements[i].innerText = hzh[i];
hElements[i].innerText = hzh[i];
}
}
}

View File

@ -1,48 +1,48 @@
---
sidebar_label: 'bot开发'
sidebar_position: 3
sidebar_label: 'bot开发'
sidebar_position: 3
---
import MyButton from './mybutton';
import MyButton from '../../src/components/mybutton';
<MyButton onClick={() => {
var pElements = document.getElementsByTagName('p');
var hElements = document.getElementsByTagName('h1');
var pElements = document.getElementsByTagName('p');
var hElements = document.getElementsByTagName('h1');
var hzh = ['bot开发','bot开发'];
var hen = ['Bot Development','Bot Development'];
var zhtranalate = ['Bot开发是开发者进行bot注册的重要模块。' , '在个人“设置”中用户可以看到目前已经注册的bot列表点击对应bot的“编辑”按钮可以对已注册的bot进行配置点击“Bot注册”按钮开始注册新的bot。' , '' , '在注册页中开发者需要填写bot注册的相关信息包括bot的名称、Webhook 地址详细介绍等系统将对开发者输入的信息进行合法性校验确保bot各项信息的完整性和有效性。此外系统将自动生成bot的唯一标识同时调用 GitLink 平台的相关接口生成bot的身份凭证信息包括客户端密钥和私钥等。' , '开发者需通过这些身份信息结合平台接口进行bot身份认证后调用相关接口完成bot的相关功能。' , '平台开发API链接待完善https://www.gitlink.org.cn/docs/api#introduction' , '' , '' , '©Copyright 2024 CCF 开源发展委员会' , 'Powered by Trustie& IntelliDE 京ICP备13000930号'];
var entranslate = ['Bot development is an important module for developers to register their bots.' , 'In personal settings, users can see a list of registered bots and click the "Edit" button on the corresponding bot to configure the registered bots; Click the "Bot Registration" button to start registering a new bot.' , '' , 'In the registration page, developers need to fill in the relevant information for bot registration, including the name of the bot, Webhook address, detailed introduction, etc. The system will verify the legality of the information entered by the developer to ensure the completeness and validity of all bot information. In addition, the system will automatically generate a unique identifier for the bot, and call the relevant interfaces of the GitLink platform to generate the identity credential information of the bot, including the client key and private key.' , 'Developers need to use these identity information in combination with the platform interface for bot identity authentication, and then call the relevant interface to complete the relevant functions of the bot.' , 'Platform development API link (to be improved): https://www.gitlink.org.cn/docs/api#introduction' , '' , '' , '© Copyright 2024 CCF Open Source Development Committee' , 'Powered by Trust&IntelliDE Beijing ICP Preparation No. 13000930'];
if (pElements[0].innerText == zhtranalate[0]){
for (var i = 0; i < pElements.length; i++) {
for (var i = 0; i < pElements.length; i++) {
if (pElements[i].innerText != ''){
if (i < pElements.length){
pElements[i].innerText = entranslate[i];
pElements[i].innerText = entranslate[i];
}
}
}
}
else{
for (var i = 0; i < pElements.length; i++) {
for (var i = 0; i < pElements.length; i++) {
if (pElements[i].innerText != ''){
if (i < pElements.length){
pElements[i].innerText = zhtranalate[i];
pElements[i].innerText = zhtranalate[i];
}
}
}
}
if (hElements[0].innerText == hzh[0]){
for (var i = 0; i < hElements.length; i++) {
for (var i = 0; i < hElements.length; i++) {
if (hElements[i].innerText != ''){
if (i < hElements.length){
hElements[i].innerText = hen[i];
hElements[i].innerText = hen[i];
}
}
}
}
else{
for (var i = 0; i < hElements.length; i++) {
for (var i = 0; i < hElements.length; i++) {
if (hElements[i].innerText != ''){
if (i < hElements.length){
hElements[i].innerText = hzh[i];
hElements[i].innerText = hzh[i];
}
}
}

View File

@ -1,48 +1,48 @@
---
sidebar_label: 'bot配置'
sidebar_position: 2
sidebar_label: 'bot配置'
sidebar_position: 2
---
import MyButton from './mybutton';
import MyButton from '../../src/components/mybutton';
<MyButton onClick={() => {
var pElements = document.getElementsByTagName('p');
var hElements = document.getElementsByTagName('h1');
var pElements = document.getElementsByTagName('p');
var hElements = document.getElementsByTagName('h1');
var hzh = ['bot配置','bot配置'];
var hen = ['Bot Configure','Bot Configure'];
var zhtranalate = ['Bot配置是开发者进行bot维护和配置的重要模块主要包括bot基本信息维护、权限&订阅事件管理、高级选项配置等功能。','Bot的基本信息维护中开发者可以看到bot的各项基本信息并可以根据需要对bot的头像名称Webhook地址等进行修改。','','Bot的权限&订阅事件管理中开发者可根据对仓库资源的访问需要为bot分配不同的权限和等级比如增加代码库权限将拉取请求的写权限变为读权限等。开发者还能更改当前bot订阅的事件列表比如订阅代码库推送取消拉取请求分配订阅等以实现bot功能的更新与升级。','','Bot高级选项配置中开发者可以改变bot的公私有状态从而影响到bot的使用范围。需要注意的是公开状态下的bot在已有其他仓库安装的情况下不能变成私有。开发者可选择将bot上架到市场需要填写上架信息包括市场简介主要功能次要功能等各项信息。','开发者还能进行bot的删除和转让操作发起转让意味着更改bot的所有权需要输入接受者的用户名。在接受者确定接受后即可完成bot的所有权变更拒绝则会取消本次的转让操作。','','©Copyright 2024 CCF 开源发展委员会','Powered by Trustie& IntelliDE 京ICP备13000930号'];
var entranslate = ['Bot configuration is an important module for developers to maintain and configure bots, mainly including basic bot information maintenance, permission&subscription event management, advanced option configuration, and other functions.' , 'In the basic information maintenance of bots, developers can see various basic information of bots and modify their avatars, names, Webhook addresses, etc. as needed.' , '' , 'In the permission and subscription event management of bots, developers can assign different permissions and levels to bots based on their access needs to warehouse resources, such as adding code library permissions, changing write permissions for pull requests to read permissions, etc. Developers can also modify the event list of the current bot subscription, such as subscribing to code library push notifications, canceling pull request allocation subscriptions, etc., to achieve updates and upgrades of bot functionality.' , '' , 'In the advanced options configuration of bots, developers can change the public and private status of bots, thereby affecting their usage scope. It should be noted that bots in public status cannot become private when installed in other warehouses. Developers can choose to put their bots on the market and need to fill in the listing information, including market introduction, main functions, secondary functions, and other related information.' , 'Developers can also perform bot deletion and transfer operations, initiating transfer means changing the ownership of the bot, and the recipients username needs to be entered. After the recipient confirms acceptance, the ownership change of the bot can be completed. Refusal will cancel the transfer operation.' , '' , '© Copyright 2024 CCF Open Source Development Committee' , 'Powered by Trust&IntelliDE Beijing ICP Preparation No. 13000930'];
if (pElements[0].innerText == zhtranalate[0]){
for (var i = 0; i < pElements.length; i++) {
for (var i = 0; i < pElements.length; i++) {
if (pElements[i].innerText != ''){
if (i < pElements.length){
pElements[i].innerText = entranslate[i];
pElements[i].innerText = entranslate[i];
}
}
}
}
else{
for (var i = 0; i < pElements.length; i++) {
for (var i = 0; i < pElements.length; i++) {
if (pElements[i].innerText != ''){
if (i < pElements.length){
pElements[i].innerText = zhtranalate[i];
pElements[i].innerText = zhtranalate[i];
}
}
}
}
if (hElements[0].innerText == hzh[0]){
for (var i = 0; i < hElements.length; i++) {
for (var i = 0; i < hElements.length; i++) {
if (hElements[i].innerText != ''){
if (i < hElements.length){
hElements[i].innerText = hen[i];
hElements[i].innerText = hen[i];
}
}
}
}
else{
for (var i = 0; i < hElements.length; i++) {
for (var i = 0; i < hElements.length; i++) {
if (hElements[i].innerText != ''){
if (i < hElements.length){
hElements[i].innerText = hzh[i];
hElements[i].innerText = hzh[i];
}
}
}

View File

@ -1,13 +0,0 @@
import React from 'react';
import styles from './mybutton.module.css'; // 假设你有一个CSS模块文件来定义样式
const MyButton = ({ onClick, children, className = '' }) => (
<button
onClick={onClick}
className={`${styles.myButton} ${className}`} // 使用CSS模块样式
>
{children}
</button>
);
export default MyButton;

View File

@ -1,12 +1,12 @@
---
sidebar_label: '代码流水线'
sidebar_position: 5
sidebar_label: '代码流水线'
sidebar_position: 5
---
# 代码流水线
可以通过代码的形式(YAML格式)来配置流水线,首先选择代码流水线和对应的分支:
![code_workflow1](../../static/img/engine/code_workflow1.jpg)
编辑流水线代码,其流水线名称描述、触发器、全局参数、执行串行/并发和流水线编排等概念同图形流水线,具体描述如下:
![code_workflow2](../../static/img/engine/code_workflow2.png)

View File

@ -1,12 +1,12 @@
---
sidebar_label: '参数配置'
sidebar_position: 2
sidebar_label: '参数配置'
sidebar_position: 2
---
# 参数配置
可在流水线中使用,实现动态参数配置;有字符串、数字、布尔三种类型。
![parameter1](../../static/img/engine/parameter1.png)

View File

@ -1,8 +1,8 @@
---
sidebar_label: '图形流水线'
sidebar_position: 4
sidebar_label: '图形流水线'
sidebar_position: 4
---
# 图形流水线
## 基本信息
可以编辑流水线的名称、描述

View File

@ -1,10 +1,10 @@
---
sidebar_label: '密钥设置'
sidebar_position: 3
sidebar_label: '密钥设置'
sidebar_position: 3
---
# 密钥设置
将账号密码、密钥、证书等存放到密钥管理里,配置后可直接在流水线中使用,避免因直接填写引起的泄露风险。
![secret1](../../static/img/engine/secret1.png)

View File

@ -1,11 +1,12 @@
---
sidebar_label: '引擎简介'
sidebar_position: 1
sidebar_position: 1
---
# 引擎
# 引擎简介
引擎(Engine)是GitLink提供的一款DevOps工具通过简单的节点编排来构建开发、测试、部署流水线从而创建自动化的软件交付流程。
引擎 (Engine) GitLink 提供的一款 DevOps 工具,通过简单的节点编排来构建开发、测试、部署流水线,从而创建自动化的软件交付流程。
它可以实现持续的代码集成,让开发者能尽早发现质量问题,快速定位修复,提升软件开发效率和质量;自动化的代码扫描、编译打包、单元测试,把研发团队从重复的工作中解放出来,聚焦到更有价值的事情上。
![engine_intro](../../static/img/engine/engine_intro.jpg)

View File

@ -1,10 +1,10 @@
---
sidebar_label: '执行记录查询'
sidebar_position: 6
sidebar_label: '执行记录查询'
sidebar_position: 6
---
# 执行记录查询
用户可以查看流水线的运行状态
![result](../../static/img/engine/result.png)

View File

@ -75,7 +75,7 @@ GitLink确实开源是CCF官方指定的开源创新服务平台旨在
<p>通知简介及设置[2个文档]</p>
</a></article>
<article class="col col--6 margin-bottom--lg">
<a class="card padding--lg cardContainer" href="/个人主页建站/站流程">
<a class="card padding--lg cardContainer" href="/个人主页建站/点创建流程">
<h2 class="text--truncate cardTitle" title="个人主页建站">个人主页建站</h2>
<p>个人主页建站使用及设置[2个文档]</p>
</a></article>

View File

@ -1,6 +1,6 @@
---
sidebar_label: '建站工具'
sidebar_position: 2
sidebar_label: '建站工具'
sidebar_position: 2
---
# 个人建站工具
@ -18,9 +18,9 @@ sidebar_position: 2
修改完毕后点击提交
![img](../../static/img/个人主页建站/建站工具2.png)
提交完成后,开始部署
![img](../../static/img/建站工具3.png)
![img](../../static/img/建站工具/3.png)
静态页面预览
![img](../../static/img/建站工具4.png)
![img](../../static/img/建站工具/4.png)
### jekyll工具
@ -29,17 +29,17 @@ sidebar_position: 2
内容修改两个值。
例如: 现在有一个网站地址为 http://KingChan.gitlink.net
那么结果如下:
baseurl: "/"
baseurl: "/"
url: "http://KingChan.gitlink.net"
![img](../../static/img/建站工具5.png)
![img](../../static/img/建站工具/5.png)
修改完成后点击 提交变更
![img](../../static/img/个人主页建站/建站工具2.png)
提交完成后,前往建站服务开始部署
![img](../../static/img/建站工具7.png)
![img](../../static/img/建站工具/7.png)
![img](../../static/img/建站工具8.png)
![img](../../static/img/建站工具/8.png)
### hexo工具
@ -48,20 +48,14 @@ url: "http://KingChan.gitlink.net"
将值改成 个人建站服务中显示的网站地址,
例如: 现在有一个网站地址为 http://KingChan.gitlink.net
那么 _config.toml 中的 url应该为 http://KingChan.gitlink.net
那么 _config.yml 中的 url 应该为 http://KingChan.gitlink.net
![img](../../static/img/建站工具9.png)
![img](../../static/img/建站工具/9.png)
修改完毕后提交变更
![img](../../static/img/个人主页建站/建站工具2.png)
提交完成后,前往建站服务开始部署
![img](../../static/img/建站工具11.png)
![img](../../static/img/建站工具12.png)
![img](../../static/img/建站工具13.png)
### file 工具
文档格式工具,使用该工具会部署所选中分支下的所有文件。

View File

@ -1,5 +1,5 @@
---
sidebar_label: '建站流程'
sidebar_label: '建站流程'
sidebar_position: 1
---
# 个人建站流程
@ -8,7 +8,7 @@ sidebar_position: 1
![img](../../static/img/建站工具/1.png)
点击左面板中 个人建站 - 我的站点
点击左面板中 个人建站 - 我的站点
![img](../../static/img/建站工具/3.png)
@ -22,7 +22,7 @@ sidebar_position: 1
![img](../../static/img/建站工具/4.png)
填写完毕后,点击页面下方蓝色按钮: 创建站点
填写完毕后,点击页面下方蓝色按钮: 创建站点
![img](../../static/img/建站工具/5.png)

View File

@ -1,7 +1,11 @@
---
sidebar_label: 'WebIDE'
sidebar_position: 9
sidebar_label: 'WebIDE'
sidebar_position: 9
---
> **说明:** 本文档为 WebIDE 的具体使用操作指南。如需查看 WebIDE 的技术背景与功能介绍,请参阅 [第三方服务/WebIDE](../第三方服务/WebIDE.md)。
### **1. 语言服务**
● 支持近 40 种语言的语法高亮
@ -11,7 +15,7 @@ sidebar_position: 9
### **2. WebIDE 入口**
从 Gitlink 仓库首页 点击Web IDE 按键即可进入
![](../../static/img/代码库管理/WebIDE/WebIDE入口.png)<br/>
### **3. 分支**
支持分支切换:点击左下角分支名即可切换分支。
![](../../static/img/代码库管理/WebIDE/WebIDE切换分支.png)<br/>
@ -19,19 +23,19 @@ sidebar_position: 9
### **4. 搜索**
当前支持 Gitlink 仓库内代码搜索(支持单词匹配和文件过滤)和文件搜索。
![](../../static/img/代码库管理/WebIDE/WebIDE搜索.png)<br/>
### **5. 行高亮**
支持单行或多行高亮,点击行号即可高亮行,按住 Shift 可选中连续多行。
![](../../static/img/代码库管理/WebIDE/WebIDE行高亮.png)<br/>
### **6. Blame**
支持查看单行 blame 信息hover 后可展示详细信息。
![](../../static/img/代码库管理/WebIDE/WebIDEblame.png)<br/>
### **7. Graph**
支持 graph 视图,可查看分支 commits 历史以及每个 commit 详细文件变更,并支持文件 diff 查看。底部状态栏左下角点击 Git Graph 或在命令面板输入 View Git Graph即可打开 Git Graph 视图。
![](../../static/img/代码库管理/WebIDE/WebIDEGraph.png)<br/>
### **8. WebSCM**
可以在极速版新建分支,修改代码后在 SCM 面板看到变更文件列表,写完 commit message 后提交到 Gitlink 上。如果想快速修改一些文件可以不用在本地修改,直接通过极速版修改代码一次性提交。

View File

@ -1,29 +1,29 @@
---
sidebar_label: 'Webhook'
sidebar_position: 8
sidebar_position: 8
---
### **1. Webhook模块入口**
在项目主页点击“仓库设置”按键再点击“网络钩子”按键即可进入仓库的Webhook模块。
![](../../static/img/代码库管理/Webhook/Webhook入口.png)<br/>
### **2. 添加Webhook**
单击“添加Webhook”按键可进入配置Webhook页面
![](../../static/img/代码库管理/Webhook/添加Webhook.png)<br/>
![](../../static/img/代码库管理/Webhook/Webhook配置.png)<br/>
### **3. 编辑Webhook**
添加Webhook后如下图所示可以通过点击右侧”编辑“按键来对每个Webhook进行编辑操作。
![](../../static/img/代码库管理/Webhook/编辑Webhook.png)<br/>
### **4. 删除Webhook**
添加Webhook后如下图所示可以通过点击右侧”删除“按键来对每个Webhook进行删除操作。
![](../../static/img/代码库管理/Webhook/删除webhook.png)<br/>
### **1. Webhook模块入口**
在项目主页点击”仓库设置”按键再点击”网络钩子”按键即可进入仓库的Webhook模块。
![](../../static/img/代码库管理/Webhook/Webhook入口.png)<br/>
### **5. Webhook支持的事件类型**
在GitLink中Webhook支持的事件类型如下
### **2. 添加Webhook**
单击”添加Webhook”按键可进入配置Webhook页面
![](../../static/img/代码库管理/Webhook/添加Webhook.png)<br/>
![](../../static/img/代码库管理/Webhook/Webhook配置.png)<br/>
### **3. 编辑Webhook**
添加Webhook后如下图所示可以通过点击右侧”编辑”按键来对每个Webhook进行编辑操作。
![](../../static/img/代码库管理/Webhook/编辑Webhook.png)<br/>
### **4. 删除Webhook**
添加Webhook后如下图所示可以通过点击右侧”删除”按键来对每个Webhook进行删除操作。
![](../../static/img/代码库管理/Webhook/删除webhook.png)<br/>
### **5. Webhook支持的事件类型**
在GitLink中Webhook支持的事件类型如下
- 推送git推送到存储库
- 代码库:创建或删除代码库
- 创建:创建分支或标签
@ -35,4 +35,3 @@ sidebar_position: 8
- 合并请求标签:合并请求的标签被更新或清除
- 合并请求审查:合并请求被批准、拒绝或提出审查意见,审查人员的修改,审查线程已解决或未解决
- 合并请求被同步:合并请求被同步

View File

@ -1,11 +1,11 @@
---
sidebar_label: '仓库创建'
sidebar_position: 1
sidebar_label: '仓库创建'
sidebar_position: 1
---
### **1. 仓库创建入口**
用户可以通过平台主页右上角的按键以及个人主页参与项目模块的新建”按键来创建仓库。
### **1. 仓库创建入口**
用户可以通过平台主页右上角的按键以及个人主页参与项目模块的新建”按键来创建仓库。
![](../../static/img/代码库管理/仓库创建/仓库创建.png)<br/>
### **2. 填写项目基本信息**
进入新建项目页面后,如下图所示,填写拥有者、项目名称、项目标识等信息,单击”创建项目按键即可完成创建。
### **2. 填写项目基本信息**
进入新建项目页面后,如下图所示,填写拥有者、项目名称、项目标识等信息,单击”创建项目按键即可完成创建。
![](../../static/img/代码库管理/仓库创建/新建项目.png)<br/>

View File

@ -1,9 +1,9 @@
---
sidebar_label: '仓库设置'
sidebar_position: 2
sidebar_label: '仓库设置'
sidebar_position: 2
---
### **1. 仓库设置入口**
在仓库主页,点击“仓库设置”按键即可进入仓库设置模块。仓库设置中可以修改项目的基本信息、进行成员管理、配置webhook、设置分支以及安装bot。
在仓库主页,点击“仓库设置”按键即可进入仓库设置模块。仓库设置中可以修改项目的基本信息、进行成员管理、配置 Webhook、设置分支以及安装 Bot。
![](../../static/img/代码库管理/仓库设置/仓库设置.png)<br/>
### **2. 基本设置**

View File

@ -1,11 +1,11 @@
---
sidebar_label: '代码提交'
sidebar_position: 3
sidebar_label: '代码提交'
sidebar_position: 3
---
### **1. 代码提交入口**
用户可以通过项目代码库中间的文件下的上传文件”按键来上传文件。
### **1. 代码提交入口**
用户可以通过项目代码库中间的文件下的上传文件”按键来上传文件。
![](../../static/img/代码库管理/代码提交/上传文件入口.png)<br/>
### **2. 上传文件**
进入上传文件页面后,如下图所示,拖动文件,选择提交变更的分支的单击”创建项目“按键即可完成创建
### **2. 上传文件**
进入上传文件页面后,如下图所示,拖动文件,选择提交变更的分支,单击”提交变更”按键即可完成上传
![](../../static/img/代码库管理/代码提交/上传文件.png)<br/>

View File

@ -1,13 +1,13 @@
---
sidebar_label: '分支管理'
sidebar_position: 5
sidebar_label: '分支管理'
sidebar_position: 5
---
### **1. 查看分支**
### **1. 查看分支**
在代码库栏下,如下图所示,用户可以点击代码库分支从而查看当前项目的所有分支,并且选择对其进行操作。
![](../../static/img/代码库管理/分支管理/分支管理方式1.jpg)<br/>
### **2. 合并分支**
“和并请求PR”下点击“新建合并请求”后,
### **2. 合并分支**
”合并请求PR”下点击”新建合并请求”后,
![](../../static/img/代码库管理/分支管理/分支管理方式3.png)<br/>
如下图所示,用户可以选择源分支与目标分支以及填写分支信息后提交分支合并请求。
![](../../static/img/代码库管理/分支管理/分支管理方式2.png)<br/>

View File

@ -1,23 +1,23 @@
---
sidebar_label: '成员管理'
sidebar_position: 7
sidebar_label: '成员管理'
sidebar_position: 7
---
### **1. 成员管理入口**
在仓库主页,点击“仓库设置”按键,再点击”成员管理“按键,即可进入成员管理模块,如下图所示。
![](../../static/img/代码库管理/成员管理/成员管理入口.png)<br/>
### **2. 筛选、搜索项目成员**
进入成员管理模块后,可以通过”角色筛选“按键来筛选项目成员类型,通过”搜索“按键可以检索具体的项目成员,如下图所示。
![](../../static/img/代码库管理/成员管理/筛选、搜索项目成员.png)<br/>
### **3. 筛选、搜索项目成员**
进入成员管理模块后,可以通过”添加成员按键来添加项目成员,左侧搜索框检索到具体用户并选中后,单击”添加成员“按键即可成功添加项目成员。
### **3. 添加项目成员**
进入成员管理模块后,可以通过”添加成员按键来添加项目成员,左侧搜索框检索到具体用户并选中后,单击”添加成员“按键即可成功添加项目成员。
![](../../static/img/代码库管理/成员管理/添加成员.png)<br/>
### **4. 项目成员权限管理**
进入成员管理模块后,单击项目成员右侧的角色栏,可以选择赋予该名项目成员的权限等级,如下图所示。
![](../../static/img/代码库管理/成员管理/项目成员权限管理.png)<br/>
### **5. 删除项目成员**
进入成员管理模块后,单击项目成员右侧的”删除“按键,可以删除名项目成员,如下图所示。
进入成员管理模块后,单击项目成员右侧的”删除“按键,可以删除名项目成员,如下图所示。
![](../../static/img/代码库管理/成员管理/删除项目成员.png)<br/>

View File

@ -1,19 +1,19 @@
---
sidebar_label: '文件管理'
sidebar_position: 4
sidebar_label: '文件管理'
sidebar_position: 4
---
### **1. 文件管理位置**
在代码库界面,可直接对代码库中该分支下的文件进行操作,如下图所示。
![](../../static/img/代码库管理/文件管理/文件管理入口.png)<br/>
### **2. 进入目录**
点击进入相关目录,找到要编辑的文件,如下图所示。
![](../../static/img/代码库管理/文件管理/进入文件所在目录.png)<br/>
### **3. 进入文件编辑页面**
在线编辑文件,单击文件,即可进入编辑文件页面,如下图所示。
![](../../static/img/代码库管理/文件管理/进入文件编辑界面.png)<br/>
### **4. 编辑文件**
编辑界面右侧有三个按钮,分别是“下载”、“编辑”和“删除”,点击下载即可将文件下载到本地,编辑则可在线编辑文档,删除则将文件从代码库中删除,按钮位置如下图所示。
![](../../static/img/代码库管理/文件管理/文件编辑按钮.png)<br/>

View File

@ -1,29 +1,31 @@
---
sidebar_label: '标签和发行版管理'
sidebar_position: 6
sidebar_label: '标签和发行版管理'
sidebar_position: 6
---
### **1. 标签和发行版管理入口**
在仓库主页,点击“代码库”按键,再点击”标签“按键,即可进入标签和发行版管理模块,如下图所示。
![](../../static/img/代码库管理/标签和发行版管理/标签和发行版管理入口.png)<br/>
### **2. 删除标签**
### **2. 创建标签**
在标签和发行版管理模块中,点击"新建标签"按键,在弹出的页面中填写标签名称(通常使用语义化版本号,如 v1.0.0),选择标签所基于的分支或提交记录,填写标签说明后单击"确认"按键即可创建新标签。
### **3. 删除标签**
进入标签和发行版管理模块后,点击标签右侧“删除”按键,即可实现对标签的删除操作,如下图所示。
![](../../static/img/代码库管理/标签和发行版管理/删除标签.png)<br/>
### **3. 查看发行版**
### **4. 查看发行版**
进入标签和发行版管理模块后,点击”发行版”按键即可查看项目的发行版,如下图所示。
![](../../static/img/代码库管理/标签和发行版管理/查看发行版.png)<br/>
### **4. 创建发行版**
### **5. 创建发行版**
进入标签和发行版管理模块后,点击标签右侧”创建发行版”按键,即可快速创建绑定该标签的发行版,如下图所示。
![](../../static/img/代码库管理/标签和发行版管理/发布新版本.png)<br/>
### **5. 修改发行版**
进入标签和发行版管理模块后,点击发行版右侧的修改按键,即可进入发行版的修改页面,如下图所示。完成发行版的修改后,单击”保存发行版按键即可保存修改内容。
### **6. 修改发行版**
进入标签和发行版管理模块后,点击发行版右侧的修改按键,即可进入发行版的修改页面,如下图所示。完成发行版的修改后,单击”保存发行版按键即可保存修改内容。
![](../../static/img/代码库管理/标签和发行版管理/修改发行版.png)<br/>
![](../../static/img/代码库管理/标签和发行版管理/发行版修改页面.png)<br/>
### **6. 删除发行版**
### **7. 删除发行版**
进入标签和发行版管理模块后,点击发行版右侧的删除按键,即可进入发行版的删除页面,如下图所示。
![](../../static/img/代码库管理/标签和发行版管理/删除发行版.png)<br/>

View File

@ -1,13 +1,22 @@
---
sidebar_label: '代码评审'
sidebar_position: 3
sidebar_label: '代码评审'
sidebar_position: 3
---
# 代码评审
### **1. 进入代码评审**
如下图所示,点击“代码评审”按钮可以进入代码评审
![](../../static/img/PR/进入代码评审.png)<br/>
### **2. 代码评审界面**
如下图所示用户可以在该界面下查看该PR操作要合并的新的代码并对其进行评审。
![](../../static/img/PR/具体代码评审.png)<br/>
### **1. 进入代码评审**
如下图所示,点击”代码评审”按钮可以进入代码评审
![](/img/PR/进入代码评审.png)
### **2. 代码评审界面**
如下图所示,用户可以在该界面下查看该 PR 操作要合并的新的代码,并对其进行评审。
![](/img/PR/具体代码评审.png)
### **3. 添加评审意见**
在代码评审界面中,用户可以点击代码行左侧的”+”按钮,对特定代码行添加评审意见。填写评论内容后单击”提交”按键即可发表评论。
### **4. 评审状态管理**
评审意见可以分为”已解决”和”未解决”两种状态。已解决的评审意见表示该问题已处理,未解决的评审意见表示该问题仍需关注。项目维护者可以根据评审结果决定是否合并该请求。
### **5. 完成评审**
当所有评审意见都已处理完毕后评审人员可以在评审界面中给出最终评审结果批准Approve或请求修改Request Changes

View File

@ -1,6 +1,6 @@
---
sidebar_label: '创建合并请求'
sidebar_position: 2
sidebar_label: '创建合并请求'
sidebar_position: 2
---
# 创建合并请求
@ -13,9 +13,9 @@ sidebar_position: 2
2. 选择需要合并的**源分支**和**目标分支**其中源分支为已完成代码开发、需要合并其代码变更的分支目标分支为要并入代码变更的分支既可以是同一仓库下的其他分支branch也可以是被复刻的源仓库下的分支
3. 选中分支后,填写本次合并请求的标题和描述内容,为审查者提供辅助理解本次合并请求的信息,进而加快合并请求审查过程(见 ***代码评审*** 一节);
3. 选中分支后,填写本次合并请求的标题和描述内容,为审查者提供辅助理解本次合并请求的信息,进而加快合并请求审查过程(见 [代码评审](/合并请求/代码评审) 一节);
4. 此外,用户还可以在右侧边栏中指定审查人员、添加里程碑、标记以及优先级(合并请求本质上是一个疑修,这些操作与疑修模块中的操作含义相同或相近,所以可以参照疑修章节中的介绍辅助理解);
4. 此外,用户还可以在右侧边栏中指定审查人员、添加里程碑、标记以及优先级(合并请求本质上是一个疑修,这些操作与疑修模块中的操作含义相同或相近,所以可以参照 [疑修简介](/疑修/疑修简介) 中的介绍辅助理解);
5. 最后信息填写完毕后,点击底部的“**创建**”按钮即可提交您的第一个合并请求了🎉🎉🎉!

View File

@ -1,6 +1,6 @@
---
sidebar_label: '合并模式简介'
sidebar_position: 4
sidebar_position: 4
---
# 合并模式简介

View File

@ -1,6 +1,19 @@
---
sidebar_label: '合并请求关联疑修'
sidebar_position: 5
sidebar_label: '合并请求关联疑修'
sidebar_position: 5
---
# 合并请求关联疑修
# 合并请求关联疑修
在 GitLink 中合并请求PR可以与疑修Issue进行关联从而让项目成员清晰地了解某个合并请求是为了解决哪些具体问题。
## 关联合并请求与疑修
1. 在创建或编辑合并请求时,可以在右侧边栏的"疑修"区域中搜索并选择需要关联的疑修;
2. 也可以在疑修详情页面中,通过编辑"合并请求"属性来关联已有的合并请求。
## 关联的作用
- 当合并请求被合并后,其所关联的疑修可以自动关闭,减少手动操作;
- 在疑修列表中可以快速跳转到对应的合并请求,反之亦然;
- 方便项目管理者追踪疑修的解决进度和对应的代码变更。

View File

@ -1,18 +1,18 @@
---
sidebar_label: '合并请求简介'
sidebar_position: 1
sidebar_label: '合并请求简介'
sidebar_position: 1
---
# 合并请求简介
**Pull Request**,即**合并请求**,是现代分布式软件开发中两个软件仓库间合并代码变更的一种方式,通常用于 fork 仓库向被 fork 仓库(源仓库)提交代码变更,同时也是为团队项目或开源项目做贡献的一种非常好的团队协作方式。当你拉取并修改了他人的仓库代码后,将你的修改告知给原仓库的管理者,请求其合并你的修改,这个过程就被称为 **合并请求**
GitLink中的 **合并请求(PR)** 模块提供合并请求创建和管理两方面的功能:
GitLink 中的**合并请求 (PR)** 模块提供合并请求创建和管理两方面的功能:
- 一方面支持向源仓库或者同一个仓库其他分支创建(发起)代码合并请求;
- 另一方面也为仓库管理者对他人发送到本仓库的合并请求进行管理、审阅并最终确定是否纳入仓库。
如下图所示为合并请求(PR)管理模块:
如下图所示为合并请求 (PR) 管理模块:
![合并请求管理模块](/img/PR/imagePR1.png)

View File

@ -0,0 +1,68 @@
---
sidebar_label: 常见问题
sidebar_position: 1
---
# 常见问题FAQ
本页汇总了 GitLink 平台使用中最常见的问题及解决方案。
## 注册与登录
### Q1如何注册 GitLink 账号?
访问 [GitLink 官网](https://www.gitlink.org.cn),点击右上角"注册"按钮支持手机号注册和第三方登录Gitee、GitHub
### Q2忘记密码怎么办
在登录页面点击"忘记密码",通过注册手机号接收验证码,验证后即可重置密码。
## 代码库管理
### Q3如何将 GitHub 项目导入 GitLink
进入"项目"模块,点击"新建项目",选择"导入已有项目",输入 GitHub 仓库 URL 和 Access Token 即可导入。详见 [导入 GitHub 等第三方 Git 项目](/快速开始/导入GitHub等第三方Git项目)。
### Q4仓库容量有限制吗
GitLink 单个仓库建议不超过 1GB如有更大需求可联系官方邮箱 gitlink@ccf.org.cn。
### Q5如何配置 SSH 公钥?
在本地生成 SSH 密钥对后,进入 GitLink **设置 → SSH公钥** 页面,将公钥内容粘贴提交即可。命令:
```bash
ssh-keygen -t ed25519 -C "your_email@example.com"
cat ~/.ssh/id_ed25519.pub
```
## 合并请求
### Q6合并请求和疑修有什么区别
**疑修Issue** 用于跟踪 Bug、功能需求和任务**合并请求Merge Request** 用于代码审查和合并。可以将疑修与合并请求关联,实现问题追踪与代码变更的对应。
### Q7如何解决合并冲突
当合并请求出现冲突时,在本地拉取目标分支,合并源分支后手动解决冲突文件,然后提交并推送即可。详见 [合并模式简介](/合并请求/合并模式简介)。
## DevOps 引擎
### Q8DevOps 引擎支持哪些语言?
DevOps 引擎基于建木JianMu构建支持 Shell 脚本、Node.js、Python、Java、Docker 等多种构建环境,可灵活编排流水线节点。
### Q9流水线构建失败怎么排查
进入 DevOps 引擎 → 执行记录查询,找到失败的执行记录,点击查看每个节点的详细日志,根据错误信息定位问题。
## 其他
### Q10如何联系 GitLink 官方?
- 官方邮箱gitlink@ccf.org.cn
- QQ 群:见网站底部二维码
- 公众号:关注"GitLink"微信公众号
---
> 没有找到您的问题?请在 [GitLink 帮助中心项目](https://gitlink.org.cn/Gitlink/gitlink_help_center/issues) 提交疑修反馈。

View File

@ -1,4 +1,14 @@
---
sidebar_label: '平台公告'
sidebar_position: 1
---
sidebar_label: '平台公告'
sidebar_position: 1
---
# 平台公告
## 最新公告
目前暂无最新平台公告。请关注 GitLink 平台通知,获取最新的服务更新、功能上线及维护信息。
## 历史公告
历史公告可在 GitLink 平台首页查看,或通过站内通知系统查询。如有疑问,请联系平台管理员。

View File

@ -0,0 +1,63 @@
---
sidebar_label: Git 命令速查
sidebar_position: 6
---
# Git 命令速查表
本文汇总 Git 日常使用中最常用的命令,帮助您快速上手。
## 初始配置
| 命令 | 说明 |
|------|------|
| `git config --global user.name "姓名"` | 设置全局用户名 |
| `git config --global user.email "邮箱"` | 设置全局邮箱 |
| `git config --list` | 查看当前配置 |
## 仓库操作
| 命令 | 说明 |
|------|------|
| `git init` | 初始化新仓库 |
| `git clone <url>` | 克隆远程仓库 |
| `git status` | 查看工作区状态 |
| `git add <file>` | 添加文件到暂存区 |
| `git add .` | 添加所有修改到暂存区 |
| `git commit -m "消息"` | 提交暂存区修改 |
| `git log` | 查看提交历史 |
| `git log --oneline` | 简洁查看提交历史 |
## 分支操作
| 命令 | 说明 |
|------|------|
| `git branch` | 查看本地分支 |
| `git branch <name>` | 创建新分支 |
| `git checkout <name>` | 切换分支 |
| `git checkout -b <name>` | 创建并切换分支 |
| `git merge <name>` | 合并指定分支到当前分支 |
| `git branch -d <name>` | 删除分支 |
| `git stash` | 暂存当前修改 |
| `git stash pop` | 恢复暂存修改 |
## 远程操作
| 命令 | 说明 |
|------|------|
| `git remote -v` | 查看远程仓库 |
| `git remote add origin <url>` | 添加远程仓库 |
| `git push origin <branch>` | 推送到远程 |
| `git pull origin <branch>` | 拉取远程更新 |
| `git fetch` | 获取远程更新(不合并) |
## 撤销与回退
| 命令 | 说明 |
|------|------|
| `git checkout -- <file>` | 撤销工作区修改 |
| `git reset HEAD <file>` | 取消暂存 |
| `git reset --soft HEAD~1` | 回退提交(保留修改) |
| `git reset --hard HEAD~1` | 回退提交(丢弃修改) |
> **提示**:更多 Git 操作详见 [提交第一行代码](/快速开始/提交第一行代码) 和 [分支管理](/代码库管理/分支管理)。

View File

@ -1,9 +1,9 @@
---
sidebar_label: '创建第一个开源项目'
sidebar_position: 2
sidebar_label: '创建第一个开源项目'
sidebar_position: 2
---
# 创建第一个开源项目
# 创建wyx第一个开源项目
## 1. 新建项目

View File

@ -1,9 +1,9 @@
---
sidebar_label: '导入GitHub等第三方Git项目'
sidebar_position: 5
sidebar_label: '导入GitHub等第三方Git项目'
sidebar_position: 5
---
# 导入GitHub等第三方Git项目
# 导入 GitHub 等第三方 Git 项目
## 1. 导入项目
@ -13,35 +13,35 @@ sidebar_position: 5
## 2. 填写信息
填写需要导入的第三方Git项目地址和项目信息如果导入项目为私有仓库则需输入目标平台用户token进行授权。
填写需要导入的第三方 Git 项目地址和项目信息,如果导入项目为私有仓库,则需输入目标平台用户 token 进行授权。
![](/img/quickstart/import_info.png)
## 3. 授权验证
在使用GitLink平台导入其他平台如GitHub、Gitee的开源项目时如果项目为私有则无法通过正常途径导入需要输入对应平台有权限的token值进行校验。
在使用 GitLink 平台导入其他平台(如 GitHub、Gitee的开源项目时如果项目为私有则无法通过正常途径导入需要输入对应平台有权限的 token 值进行校验。
![](/img/quickstart/import_verify.png)
下面将列举一些典型开源平台的token获取方式。
下面将列举一些典型开源平台的 token 获取方式。
### GitHub token获取方式
### GitHub token 获取方式
1. 登录GitHub账号
1. 登录 GitHub 账号
2. 访问用户头像下的settings菜单
2. 访问用户头像下的 settings 菜单
![img](https://forum.gitlink.org.cn/api/attachments/422328)
3. 访问最下方的Developer settings
3. 访问最下方的 Developer settings
![img](https://forum.gitlink.org.cn/api/attachments/422329)
4. 在跳转页访问Tokenclassic新建一个classic token若已保存token
4. 在跳转页访问 Tokenclassic新建一个 classic token若已保存 token
![img](https://forum.gitlink.org.cn/api/attachments/422362)
5. 在token配置页面输入token用途并确保该token已勾选“repo”选项否则将导入失败
5. 在 token 配置页面,输入 token 用途,并确保该 token 已勾选”repo”选项否则将导入失败
![img](https://forum.gitlink.org.cn/api/attachments/422364)
@ -49,33 +49,33 @@ sidebar_position: 5
![img](https://forum.gitlink.org.cn/api/attachments/422366)
6. 复制该token
6. 复制该 token
![img](https://forum.gitlink.org.cn/api/attachments/422368)
并将该token输入至GitLink导入项目认证填写框
并将该 token 输入至 GitLink 导入项目认证填写框
![img](https://forum.gitlink.org.cn/api/attachments/422376)
### Gitee token获取方式
### Gitee token 获取方式
1. 登录Gitee账号
1. 登录 Gitee 账号
2. 访问用户头像下的设置菜单
![img](https://forum.gitlink.org.cn/api/attachments/422333)
3. 访问“安全设置”栏下方“私人令牌”菜单
3. 访问”安全设置”栏下方”私人令牌”菜单
![img](https://forum.gitlink.org.cn/api/attachments/422334)
4. 点击生成新令牌,并在令牌生成页面配置令牌名称,并确保令牌已勾选project”权限选项并保存该令牌
4. 点击生成新令牌,并在令牌生成页面配置令牌名称,并确保令牌已勾选project”权限选项并保存该令牌
![img](https://forum.gitlink.org.cn/api/attachments/422371)
![img](https://forum.gitlink.org.cn/api/attachments/422370)
5. 在令牌生成成功的弹窗复制该令牌并将该token输入至GitLink导入项目认证填写框
5. 在令牌生成成功的弹窗,复制该令牌,并将该 token 输入至 GitLink 导入项目认证填写框
![img](https://forum.gitlink.org.cn/api/attachments/422374)
@ -83,7 +83,7 @@ sidebar_position: 5
## 4. 导入成功
提示正在从第三方Git项目地址迁移
提示正在从第三方 Git 项目地址迁移
![](/img/quickstart/import_wait.png)

View File

@ -1,17 +1,17 @@
---
sidebar_label: '提交第一行代码'
sidebar_position: 3
sidebar_label: '提交第一行代码'
sidebar_position: 3
---
# 提交第一行代码
# 1. 编辑代码
## 1. 编辑代码
点击编辑按钮,开始编辑代码。
![](/img/quickstart/first_edit.png)
# 2. 提交代码
## 2. 提交代码
在编辑框中编写代码,编写完成后填写变更信息后提交变更。
@ -19,4 +19,4 @@ sidebar_position: 3
## 3. 代码更新成功
提交成功后代码代码更新成功。![](/img/quickstart/first_success.png)
提交成功后代码更新成功。![](/img/quickstart/first_success.png)

View File

@ -1,13 +1,13 @@
---
sidebar_label: '搜索开源项目'
sidebar_position: 4
sidebar_label: '搜索开源项目'
sidebar_position: 4
---
# 搜索开源项目
## 1. 开源项目页面介绍
项目”模块汇聚和管理了所有 GitLink 平台上的托管项目和镜像项目,用户可以输入项目名称关键字进行搜索,也可以根据项目类别对项目进行筛选。
进入”项目”模块汇聚和管理了所有 GitLink 平台上的托管项目和镜像项目,用户可以输入项目名称关键字进行搜索,也可以根据项目类别对项目进行筛选。
进入“项目”模块,左侧列出了项目类型和项目类别。其中,项目类型主要包括开源托管项目和开源镜像项目两类。项目类别主要包括:云计算、大数据、区块链、物联网、机器学习、人工智能、智慧医疗、其他。
@ -21,7 +21,7 @@ sidebar_position: 4
## 2. 搜索开源项目
有两处搜索框可以搜索
有两处搜索框可以搜索
### 开源项目搜索框

View File

@ -1,9 +1,9 @@
---
sidebar_label: '注册GitLink账号'
sidebar_position: 1
sidebar_label: '注册GitLink账号'
sidebar_position: 1
---
# 注册GitLink账号
# 注册jtx的 GitLink 账号
## 1. 点击**立即注册**按钮

View File

@ -0,0 +1,73 @@
---
sidebar_label: 贡献指南
sidebar_position: 7
---
# 如何为帮助中心贡献
GitLink 帮助中心是一个开源文档项目,欢迎所有人参与贡献!
## 贡献流程
### 1. 复刻项目
点击项目右上角的 **"复刻"Fork** 按钮,将项目复制到您的个人账户下。
### 2. 编辑文档
有两种编辑方式:
**方式一:网页端直接编辑**
1. 进入您复刻的仓库
2. 找到需要修改的文件(`docs/` 目录下的 `.md` 文件)
3. 点击文件上方的 **编辑** 按钮(铅笔图标)
4. 修改内容并提交
**方式二:本地编辑**
```bash
git clone https://gitlink.org.cn/您的用户名/gitlink_help_center.git
cd gitlink_help_center
npm install --legacy-peer-deps
npm run dev
# 在浏览器中访问 http://localhost:3000 预览
```
### 3. 文档格式规范
每个 Markdown 文件应包含以下 frontmatter
```yaml
---
sidebar_label: 页面显示标题
sidebar_position: 排序位置
---
```
### 4. 提交合并请求
修改完成后,向原仓库提交合并请求:
1. 在原仓库页面点击 **合并请求 → 新建合并请求**
2. 选择您的修改分支作为源分支
3. 填写修改说明
4. 等待审核
## 贡献类型
| 类型 | 说明 | 示例 |
|------|------|------|
| 错别字修正 | 修正文档中的错别字 | "帐号" → "账号" |
| 内容补充 | 补充缺失或不完整的文档 | 补充操作步骤 |
| 截图更新 | 更新过时的界面截图 | 替换旧版界面图 |
| 新增章节 | 添加全新的文档章节 | FAQ 页面 |
| 功能建议 | 提出网站功能改进建议 | 新增搜索功能 |
## 注意事项
- 提交前请运行 `npm run build -- --locale zh-CN` 确保构建通过
- 提交信息请使用规范格式:`fix:`、`docs:`、`feat:` 等
- 如有疑问可在项目中提交疑修Issue讨论
> 感谢您的贡献!每一份改进都让 GitLink 帮助中心变得更好。

View File

@ -1,8 +1,8 @@
---
sidebar_label: 'GitLink服务协议'
sidebar_label: 'GitLink服务协议'
sidebar_position: 1
---
尊敬的用户,您好!
欢迎使用GitLink平台在您使用GitLink平台前请您认真阅读并遵守《GitLink服务协议》以下简称”本协议”请您务必审慎阅读、充分理解协议的各条款内容。

View File

@ -1,6 +1,6 @@
---
sidebar_label: '标记管理'
sidebar_position: 6
sidebar_label: '标记管理'
sidebar_position: 6
---
# 标记管理

View File

@ -1,13 +1,13 @@
---
sidebar_label: '疑修列表'
sidebar_position: 4
sidebar_label: '疑修列表'
sidebar_position: 4
---
# 疑修列表
对于项目开发过程中创建的所有疑修,可以在**疑修(Issue)** 界面统一查看,如下图所示为[确实开源](https://www.gitlink.org.cn/Gitlink/forgeplus)项目下的疑修列表。
![](/img/Issue/imageIssue5.png)
+ **创建疑修**:在疑修列表界面下,点击“**创建疑修**”按钮,同样可以创建疑修,具体见 ***疑修创建*** 一节;
+ **创建疑修**:在疑修列表界面下,点击”**创建疑修**”按钮,同样可以创建疑修,具体见 [疑修创建](/疑修/疑修创建) 一节;
+ **筛选疑修**:疑修列表支持不同条件筛选,包括发布人、标记(见 ***标记管理*** 一节)、里程碑(见 ***里程碑管理*** 一节),负责人、状态和开始/结束日期等;同时支持关键字搜索,以及按多种排序规则对疑修进行排序。
+ **筛选疑修**:疑修列表支持不同条件筛选,包括发布人、标记(见 [标记管理](/疑修/标记管理) 一节)、里程碑(见 [里程碑管理](/疑修/里程碑管理) 一节),负责人、状态和开始/结束日期等;同时支持关键字搜索,以及按多种排序规则对疑修进行排序。

View File

@ -1,6 +1,6 @@
---
sidebar_label: '疑修创建'
sidebar_position: 2
sidebar_label: '疑修创建'
sidebar_position: 2
---
# 疑修创建

View File

@ -1,10 +1,10 @@
---
sidebar_label: '疑修状态变更'
sidebar_position: 3
sidebar_label: '疑修状态变更'
sidebar_position: 3
---
# 疑修状态变更
**疑修**本质上是开发任务,而开发任务随着开发活动的进行,其状态也会发生改变,而“**状态**”便是用于跟踪记录开发活动的变更。如图所示GitLink中疑修的**状态**包括“新增”、“正在解决”、“已解决”、“关闭”和“拒绝“五类,用于表示开发任务的处理进度。
**疑修**本质上是开发任务,而开发任务随着开发活动的进行,其状态也会发生改变,而“**状态**”便是用于跟踪记录开发活动的变更。如图所示GitLink 中疑修的**状态**包括”新增”、”正在解决”、”已解决”、”关闭”和”拒绝”五类,用于表示开发任务的处理进度。
![](/img/Issue/imageIssue4.png)

View File

@ -1,6 +1,6 @@
---
sidebar_label: '疑修简介'
sidebar_position: 1
sidebar_label: '疑修简介'
sidebar_position: 1
---
# 疑修简介
@ -10,8 +10,8 @@ sidebar_position: 1
**说明事项**
1. **疑修**是一个可以追踪开发任务进度的帖子,因此支持参与者回复与评论等功能,见 ***评论及操作记录*** 一节;
1. **疑修**是一个可以追踪开发任务进度的帖子,因此支持参与者回复与评论等功能,见 [评论及操作记录](/疑修/评论及操作记录) 一节;
2. **疑修**的默认类型(标记)包括缺陷、功能、任务、支持、周报等,具体见 ***标记管理*** 一节;
2. **疑修**的默认类型(标记)包括缺陷、功能、任务、支持等,具体见 [标记管理](/疑修/标记管理) 一节;
3. 可以为**疑修**设置开始和结束时间,并指定负责解决该任务的负责人,在疑修截止日期的前一天,系统将自动为任务的发布者和被指派人发送提醒消息。

View File

@ -1,6 +1,6 @@
---
sidebar_label: '评论及操作记录'
sidebar_position: 5
sidebar_label: '评论及操作记录'
sidebar_position: 5
---
# 评论及操作记录

View File

@ -1,6 +1,6 @@
---
sidebar_label: '里程碑管理'
sidebar_position: 7
sidebar_label: '里程碑管理'
sidebar_position: 7
---
# 里程碑管理
@ -24,10 +24,11 @@ sidebar_position: 7
![](/img/Issue/imageIssue18.png)
### 其他操作
- **开启里程碑**
- **关闭里程碑**
- **编辑里程碑**
- **删除里程碑**
- **开启里程碑**:已关闭的里程碑可以通过点击"开启"按钮重新开启,开启后的里程碑可以继续关联新的疑修。
- **关闭里程碑**:当里程碑所关联的疑修全部完成或不再需要跟踪时,可以关闭该里程碑,表示该阶段的任务已经结束。
- **编辑里程碑**:可以修改里程碑的标题、描述和截止日期等信息,以适应项目开发过程中需求的变化。
- **删除里程碑**:不再需要的里程碑可以被删除,删除前请确认该里程碑下没有未完成的疑修,以免丢失任务跟踪信息。
上述操作均可以在里程碑列表中,对目标里程碑进行处理实现,如下图所示:
![](/img/Issue/imageIssue19.png)

View File

@ -1,19 +1,22 @@
---
sidebar_label: 'WebIDE'
sidebar_position: 9
sidebar_label: 'WebIDE'
sidebar_position: 9
---
> **说明:** 本文档为 WebIDE 的技术背景与功能介绍。如需查看 WebIDE 的具体使用操作指南,请参阅 [代码库管理/WebIDE](../代码库管理/WebIDE.md)。
## WebIDE背景
传统的代码托管平台提供了代码仓库的 Git/SVN 的管理可以在平台上做代码浏览、代码评审、缺陷管理、CI 流水线等和代码相关的活动。其中代码浏览、代码评审所用的编辑器组件一般为 CodeMirror代码高亮使用 highlight.js常常只提供了读代码的场景。蚂蚁研发效能部门云研发团队通过自研的 OpenSumi 框架及基于 OpenSumi 框架做的专门针对于 Web IDE无远程容器场景的极速版 Web IDE 框架与内部代码托管平台碰撞出了创新型的、只依赖浏览器的、IDE 风格的代码阅读、代码编写、代码提交、代码运行、代码评审等场景,极大的提高了用户在代码托管平台阅读代码、代码评审、轻研发等场景的效率。本次蚂蚁研发效能云研发团队和 CCF中国计算机学会GitLink 代码托管平台合作,将内部极速版 Web IDE 应用到了 Gitlink 代码托管平台上,解决了长久以来的用户反馈的很多体验问题。
## Web IDE 核心能力
对标有容器的标准版,极速版 IDE 主要在读、写、运行、提交等方面进行了探索:
**1. 读:**
**1. 读:**
- a. 适配了多种代码托管平台,例如 Gitlink、Github、Gitlab 等代码托管平台,业务可以非常方便的使用代码服务的能力
- b. 内置了包括 Java、TS/JS、C++、Go、Python、Rust 等几十种常见语言的语法高亮支持
- c. 支持了如 Git Blame、GitGraph 等代码阅读辅助插件
**2. 写:**
- a. 支持 HTML/CSS/JS/Markdown 等在线语言服务能力,支持错误诊断能力
- b. 浏览器文件系统
@ -21,11 +24,11 @@ sidebar_position: 9
**3. 运行:**
- a. 支持基于 Skypack 的前端代码运行方案
- b. 支持基于 Pyodide 的 Python 运行
**4. 提交:**
- a. 支持 WebSCM提供分支切换/新增、代码提交等能力
如果说以 CodeMirror、Monaco 为代表的浏览器 IDE 组件为 Web IDE 1.0,那拥有上述能力的极速版 Web IDE 就是 Web IDE 2.0。极速版 Web IDE 方案在蚂蚁内部 2021 年 4 月上线,承接了代码阅读、代码评审、在线笔试、代码检查结果反馈、轻量在线研发等众多场景,同年 8 月 vscode.dev 和 github.dev 上线,与之相比,极速版 Web IDE 利用 OpenSumi 框架的高扩展性,业务可以更加深度的定制模块与插件,让业务有多的想象空间。
如果说以 CodeMirror、Monaco 为代表的浏览器 IDE 组件为 Web IDE 1.0,那拥有上述能力的极速版 Web IDE 就是 Web IDE 2.0。极速版 Web IDE 方案在蚂蚁内部 2021 年 4 月上线,承接了代码阅读、代码评审、在线笔试、代码检查结果反馈、轻量在线研发等众多场景,同年 8 月 vscode.dev 和 github.dev 上线,与之相比,极速版 Web IDE 利用 OpenSumi 框架的高扩展性,业务可以更加深度的定制模块与插件,让业务有多的想象空间。
## 代码阅读
在代码托管平台上进行代码阅读时经常需要查看当前方法在哪里被引用、当前接口在哪里被实现,极速版 Web IDE 提供编辑器 + 插件的能力,解决了上述用户需求:
@ -91,7 +94,7 @@ Gitlink 代码阅读场景虽然接入极速版 IDE 编辑器,但文件树、
<center>文件搜索面板</center>
#### **6. 行高亮**
持单行或多行高亮,点击行号即可高亮行,按住 Shift 可选中连续多行。
持单行或多行高亮,点击行号即可高亮行,按住 Shift 可选中连续多行。
![](/img/di_san_fang/WebIDE/多选行高亮.png)
<center>多选行高亮</center>

View File

@ -1,11 +1,11 @@
---
sidebar_label: '跨平台代码同步服务'
sidebar_position: 2
sidebar_position: 2
---
### 功能简介
跨平台仓库代码同步服务用于不同代码托管平台之间的仓库代码双向同步功能。用户将不同平台的仓库分支进行绑定当任意仓库分支有push事件被webhook监听时代码推送将实时自动同步至另一个仓库以实现不同平台之间分支、提交信息的自动同步代码托管平台仅限于github与gitee
跨平台仓库代码同步服务用于不同代码托管平台之间的仓库代码双向同步功能。用户将不同平台的仓库分支进行绑定当任意仓库分支有push事件被webhook监听时代码推送将实时自动同步至另一个仓库以实现不同平台之间分支、提交信息的自动同步代码托管平台仅限于 GitHub 与 Gitee
![1.同步流程](../../static/img/reposyncer/1.同步流程.png)
@ -57,7 +57,7 @@ Gitee配置方式为个人头像→设置→私人令牌→生成新令牌→
②查询两个分支间最新一次的同步时间及同步状态。若同步失败,可在同步记录中查询日志分析失败原因<br />
③添加同步仓库若已绑定了github的同步仓库想在gitee导入一个仓库进行开发并想完成实时多个仓库的分支同步。<br />
④查看同步配置可用于查询同步仓库的地址GitLink 用于接受第三方webhook请求的地址以及更新token。以防token过期<br />
⑤查询同步记录包括查看历次同步的代码变更方同步时间同步状态及对应commt id查询同步日志。<br />
⑤查询同步记录包括查看历次同步的代码变更方同步时间同步状态及对应commit id查询同步日志。<br />
⑥中止、开启同步,相当于同步开关,可随时启停
![同步分支管理](../../static/img/reposyncer/同步分支管理.png)

View File

@ -5,24 +5,24 @@ sidebar_position: 1
## 代码分析入口
![](../../static/img/di_san_fang/重鸟代码溯源/代码分析入口.png)<br/>
![](../../static/img/di_san_fang/重鸟代码溯源/代码分析入口.png)<br/>
<center>代码分析入口</center><br/>
页面说明:<br/>
1、用户可点击"服务"菜单 tab 进入服务页面。<br/>
2、仓库管理员可在"仓库设置" tab 中项目导航处开启/关闭代码分析菜单可见性,如下图:<br/>
![](../../static/img/di_san_fang/重鸟代码溯源/项目导航.png)<br/>
![](../../static/img/di_san_fang/重鸟代码溯源/项目导航.png)<br/>
<center>项目导航</center><br/>
## 代码分析检测
![](../../static/img/di_san_fang/重鸟代码溯源/代码分析检测.png)<br/>
![](../../static/img/di_san_fang/重鸟代码溯源/代码分析检测.png)<br/>
<center>代码分析检测</center><br/>
![](../../static/img/di_san_fang/重鸟代码溯源/新建分析.png)<br/>
![](../../static/img/di_san_fang/重鸟代码溯源/新建分析.png)<br/>
<center>新建分析</center><br/>
页面说明:<br/>
@ -32,7 +32,7 @@ sidebar_position: 1
## 分支选择
![](../../static/img/di_san_fang/重鸟代码溯源/分支选择.png)<br/>
![](../../static/img/di_san_fang/重鸟代码溯源/分支选择.png)<br/>
<center>分支选择</center><br/>
页面说明:<br/>
@ -42,7 +42,7 @@ sidebar_position: 1
## 列表展示
![](../../static/img/di_san_fang/重鸟代码溯源/列表展示.png)<br/>
![](../../static/img/di_san_fang/重鸟代码溯源/列表展示.png)<br/>
<center>列表展示</center><br/>
页面说明:<br/>
@ -52,7 +52,7 @@ sidebar_position: 1
## 结果展示
![](../../static/img/di_san_fang/重鸟代码溯源/结果展示.png)<br/>
![](../../static/img/di_san_fang/重鸟代码溯源/结果展示.png)<br/>
<center>结果展示</center><br/>
页面说明:<br/>
@ -62,6 +62,6 @@ sidebar_position: 1
## 用户操作流程
![](../../static/img/di_san_fang/重鸟代码溯源/用户操作流程(新).png)<br/>
![](../../static/img/di_san_fang/重鸟代码溯源/用户操作流程(新).png)<br/>
<center>用户操作流程</center><br/>

View File

@ -1,31 +1,31 @@
---
sidebar_label: '组织创建及设置'
sidebar_position: 2
sidebar_label: '组织创建及设置'
sidebar_position: 2
---
# 组织创建Organization Creation
*https://www.gitlink.org.cn* 页面点击顶部导航栏的“+”符号可以进行组织新建操作。
![](/img/Org/imageOrg1.png)
![](/img/Org/imageOrg1.png)
在新建页面中输入**组织账号**、**组织名称**、**组织描述**、**所在地区**、**可见性**以及**组织头像**等信息后,点击“**创建组织**”按钮完成组织的创建。
![](/img/Org/imageOrg2.png)
![](/img/Org/imageOrg2.png)
## 组织账号
![](/img/Org/imageOrg4.png)
![](/img/Org/imageOrg4.png)
**注**只能使用以字母、数字开头包含字母、数字、下划线、横杠等长度4到20个字符
> **注**只能使用以字母、数字开头包含字母、数字、下划线、横杠等长度4到20个字符
## 组织名称与组织描述
![](/img/Org/imageOrg5.png)
![](/img/Org/imageOrg5.png)
**注**:此处为必填项,不得为空
> **注**:此处为必填项,不得为空
## 可见性
![](/img/Org/imageOrg3.png)
![](/img/Org/imageOrg3.png)
**注**:可见性预设三类组织:公开、受限(仅对登录用户可见)、私有(仅对组织成员可见)。
> **注**:可见性预设三类组织:公开、受限(仅对登录用户可见)、私有(仅对组织成员可见)。

View File

@ -1,6 +1,6 @@
---
sidebar_label: '组织团队管理'
sidebar_position: 3
sidebar_label: '组织团队管理'
sidebar_position: 3
---
# 组织团队管理
@ -9,26 +9,29 @@ sidebar_position: 3
在团队新建页面,输入团队标识、团队名称、团队描述、项目权限以及版本库权限等信息后,点击“新建团队”完成团队的创建。
![](/img/Org/imageOrg9.png)
![](/img/Org/imageOrg9.png)
## 查看组织团队
点击组织信息页面中的某个团队名称可以查看该团队的详细信息,该页面包括团队的名称、描述等信息,此外还会列出该团队关联的成员以及项目。
![](/img/Org/imageOrg8.png)
![](/img/Org/imageOrg8.png)
## 管理组织团队
点击团队信息页面中的团队设置”按钮可以对团队进行管理
点击团队信息页面中的团队设置”按钮可以对团队进行管理
- 基本设置:修改项目的基本信息,如名称和描述等。
![](/img/Org/imageOrg10.png)
- 基本设置:修改团队的基本信息,如名称和描述等。
- 团队成员管理:为该团队添加新成员或者移除已有成员。
![](/img/Org/imageOrg11.png)
![](/img/Org/imageOrg10.png)
- 团队项目管理:为该团队关联新项目(该组织已经创建的项目)或者移除已关联项目。
![](/img/Org/imageOrg12.png)
- 团队成员管理:为该团队添加新成员或者移除已有成员。
![](/img/Org/imageOrg11.png)
- 团队项目管理:为该团队关联新项目(该组织已经创建的项目)或者移除已关联项目。
![](/img/Org/imageOrg12.png)

View File

@ -1,21 +1,24 @@
---
sidebar_label: '组织成员管理'
sidebar_position: 4
sidebar_label: '组织成员管理'
sidebar_position: 4
---
# 成员管理Members Management
个人所管理的项目当中的**仓库设置**当中的**成员管理**可以进入成员管理界面
![](/img/Org/imageOrg14.png)
组织信息页面中的"成员管理"可以进入成员管理界面
![](/img/Org/imageOrg14.png)
## 成员查询和添加
在成员管理界面当中,可以进行组织成员的查询和添加
![](/img/Org/imageOrg14.png)
![](/img/Org/imageOrg14.png)
## 成员角色管理
管理员可以对组织成员的角色进行管理,包括设置成员为管理员或普通成员
## 成员邀请权限设置
管理员可以对邀请成员的权限进行设置
![](/img/Org/imageOrg15.png)
![](/img/Org/imageOrg15.png)
此外,也可以复制邀请链接的方式来更方便地邀请成员加入到项目当中来
**注**:此项功能仅对管理员可见
> **注**:此项功能仅对管理员可见

View File

@ -9,7 +9,7 @@ sidebar_position: 1
您的团队可以通过使用组织帐户在 GitLink 上进行协作,组织帐户充当共享工作的容器,并为工作赋予独特的名称和品牌。同时,平台支持组织在“组织详情”页面发布新闻动态,显示项目概览和仓库详情等内容
![](/img/Org/imageOrg6.png)
![](/img/Org/imageOrg6.png)
## 作为组织拥有者
@ -19,10 +19,10 @@ sidebar_position: 1
为了简化访问管理并增强协作,您可以创建能体现组结构的嵌套团队。您可以根据他们的角色或项目将人员分组,并分配任务。
![](/img/Org/imageOrg7.png)
![](/img/Org/imageOrg7.png)
平台同时支持组织拥有者管理对数据访问的自定义设置。
## 作为组织成员
您可以通过组织与无限数量的人在多个项目中协作,与志同道合的人一起,通过分工和作,参与开发流程,发布或处理问题。
您可以通过组织与无限数量的人在多个项目中协作,与志同道合的人一起,通过分工和作,参与开发流程,发布或处理问题。

View File

@ -1,12 +1,25 @@
---
sidebar_label: '组织项目管理'
sidebar_position: 5
sidebar_label: '组织项目管理'
sidebar_position: 5
---
## 创建组织项目
点击组织信息页面中的“新建项目”按钮可以创建属于该组织的托管项目或者镜像项目。
# 组织项目管理
![](/img/Org/imageOrg13.png)
## 创建组织项目
**注**:在“拥有者”一栏的下拉选项中,可以选择:个人、组织、团队
点击组织信息页面中的"新建项目"按钮可以创建属于该组织的托管项目或者镜像项目。
![](/img/Org/imageOrg13.png)
> **注**:在"拥有者"一栏的下拉选项中,可以选择:个人、组织、团队
## 管理组织项目
组织创建者和管理员可以对组织下的项目进行统一管理,主要包括以下操作:
- **查看项目列表**:在组织信息页面中可以查看该组织下的所有项目,包括项目名称、描述、可见性等信息。
- **项目权限设置**:可以为不同的团队分配不同的项目访问权限,包括读取、写入和管理员权限。
- **项目转移**:可以将个人项目转移到组织下,也可以将组织下的项目转移给其他组织或个人。
## 移除组织项目
如果需要将项目从组织中移除,可以在项目设置中进行操作。移除后项目将不再显示在组织的项目列表中,但项目本身不会被删除。

View File

@ -1,14 +1,14 @@
---
sidebar_label: '模板导入及导出'
sidebar_position: 2
sidebar_label: '模板导入及导出'
sidebar_position: 2
---
# 确实开源帮助中心
# 模板导入及导出
## 模板的导入及导出管理
#### 1.模板导入
通过点击wiki界面的**导入模板**按钮即可导入本地的txtmarkdown等格式文件进行Wiki文档的创建当前平台此项功能尚在开发测试环节
[](/img/wiki/img4.png)
![img](/img/wiki/img4.png)
#### 2.模板导出
当前平台此项功能尚在开发测试环节
<br/>

View File

@ -1,15 +1,15 @@
---
sidebar_label: '维基页面管理'
sidebar_position: 1
sidebar_label: '维基页面管理'
sidebar_position: 1
---
# 确实开源帮助中心
# 维基页面管理
## 维基界面管理
#### 1.维基界面
GitLink项目安排了专门的**维基(Wiki)界面**来展示项目的一些情况
[](/img/wiki/img1.png)
![img](/img/wiki/img1.png)
#### 2.创建维基内容
在初始化的维基界面当中点击**创建Wiki文档**或者**导入模板**即可进行Wiki文档的编辑
@ -17,7 +17,7 @@ GitLink项目安排了专门的**维基(Wiki)界面**来展示项目的一些情
#### 3.进行Wiki文档的编辑
点击**创建Wiki文档**后即跳转到编辑界面,初始内容为空白的文档
[](/img/wiki/img3.png)
![img](/img/wiki/img3.png)
也可以通过勾选*添加模板*来进行模板的导入,平台提供的模板包括*周报*和*月报*的格式
[](/img/wiki/img2.png)
完成编辑后点击左下角的保存即可
![img](/img/wiki/img2.png)
完成编辑后点击左下角的保存即可

View File

@ -1,9 +1,9 @@
---
sidebar_label: '通知简介'
sidebar_label: '通知简介'
sidebar_position: 1
---
# 确实开源帮助中心
# 通知简介
## 通知简介
#### 1.通知类型
@ -25,5 +25,5 @@ GitLink将通知分为“系统通知”和“@我”的两种类型:
<br/>
点击通知按钮可以进入消息通知界面,“我的通知”界面可以进行“查看未读消息”和“所有消息一键已读”的选择。
点击通知按钮可以进入消息通知界面,“我的通知”界面可以进行“查看未读消息”和“所有消息一键已读”的选择。
![](/img/notice/my_notice2.png)

View File

@ -1,10 +1,11 @@
---
sidebar_label: '通知设置'
sidebar_position: 2
sidebar_position: 2
---
# 确实开源帮助中心
# 通知设置
## 通知设置
#### 1.进入通知设置界面
@ -16,6 +17,6 @@ sidebar_position: 2
![](/img/notice/into_notice.png)
#### 2.进行通知设置
通过“通知管理”可以对接受通知的方式进行设置,默认所有通知都是通过站内信的方式接受,可以通过勾选为重要的通知类型增加邮件接受方式。
通过“通知管理”可以对接收通知的方式进行设置,默认所有通知都是通过站内信的方式接收,可以通过勾选为重要的通知类型增加邮件接收方式。
![](/img/notice/notice_set.png)

View File

@ -3,13 +3,12 @@ const darkCodeTheme = require('prism-react-renderer/themes/dracula');
//Docusaurus v3写法
// const { themes } = require('prism-react-renderer');
// import { themes } from 'prism-react-renderer';
import("@easyops-cn/docusaurus-search-local").PluginOptions;
/** @type {import('@docusaurus/types').DocusaurusConfig} */
module.exports = {
title: 'gitlink',
tagline:'gitlink帮助中心',
url: 'https://help.gitlink.org.cn/',
url: 'https://help.gitlink.org.cn',
baseUrl: '/',
onBrokenLinks: 'warn',
onBrokenMarkdownLinks: 'warn',
@ -17,6 +16,20 @@ module.exports = {
organizationName: 'luffyZh', // Usually your GitHub org/user name.
projectName: 'docusaurus-luffyzh-website', // Usually your repo name.
scripts: [],
plugins: [
[
'@docusaurus/plugin-pwa',
{
debug: false,
offlineModeActivationStrategies: ['appInstalled', 'standalone', 'queryString'],
pwaHead: [
{ tagName: 'link', rel: 'icon', href: '/img/icon.ico' },
{ tagName: 'link', rel: 'manifest', href: '/manifest.json' },
{ tagName: 'meta', name: 'theme-color', content: '#466aff' },
],
},
],
],
// stylesheets: ['styles/dark-mode.css'],
themeConfig: {
docs:{
@ -24,6 +37,10 @@ module.exports = {
hideable:true,
},
},
tableOfContents: {
minHeadingLevel: 2,
maxHeadingLevel: 4,
},
metadata:[
{name:"Keywords",content:"Gitlink,forgeplus,GitLink,gitLink,GitLink,gitlink,帮助中心"},
{name:"hostname",content:"gitlink.org.cn"},
@ -72,10 +89,10 @@ module.exports = {
position: 'left',
dropdownActiveClassDisabled: true
},
// {
// type: 'localeDropdown',
// position: 'right',
// },
{
type: 'localeDropdown',
position: 'right',
},
// {
// position: 'right',
// component:'./src/components/LanguageSwitcher',
@ -110,19 +127,19 @@ module.exports = {
items: [
{
label: '网站首页',
to: 'https://www.gitlink.org.cn',
href: 'https://www.gitlink.org.cn',
},
{
label: '关于我们',
to: 'https://www.gitlink.org.cn/aboutus',
href: 'https://www.gitlink.org.cn/aboutus',
},
{
label: '教学实践',
to: 'https://www.gitlink.org.cn/educoder',
href: 'https://www.gitlink.org.cn/educoder',
},
{
label: '合作伙伴',
to: 'https://forum.trustie.net/forums/5030/detail',
href: 'https://forum.trustie.net/forums/5030/detail',
},
],
},
@ -131,19 +148,19 @@ module.exports = {
items: [
{
label: 'API文档',
to: 'https://forgeplus.trustie.net/docs/api',
href: 'https://forgeplus.trustie.net/docs/api',
},
{
label: 'Git常用命令',
to: 'https://git-scm.com',
href: 'https://git-scm.com',
},
{
label: '引擎使用手册',
to: 'https://forum.gitlink.org.cn/forums/7487/detail',
href: 'https://forum.gitlink.org.cn/forums/7487/detail',
},
{
label: '服务协议',
to: 'https://forum.trustie.net/forums/5029/detail',
href: 'https://forum.trustie.net/forums/5029/detail',
},
],
},
@ -152,15 +169,15 @@ module.exports = {
items: [
{
label: '官网邮箱gitlink@ccf.org.cn',
to:"https://www.gitlink.org.cn"
href:"https://www.gitlink.org.cn"
},
{
label:'QQ群',
to:'https://www.gitlink.org.cn'
href:'https://www.gitlink.org.cn'
},
{
label:'公众号',
to:'https://www.gitlink.org.cn'
href:'https://www.gitlink.org.cn'
}
],
},
@ -193,7 +210,7 @@ module.exports = {
hashed: true,
language: ["en", "zh"],
highlightSearchTermsOnTargetPage: true,
blogRouteBasePath: "/",
blogRouteBasePath: "/",
explicitSearchResultPath: true,
// For Docs using Chinese, The `language` is recommended to set to:
// ```

View File

@ -1 +0,0 @@
23e041136cc43bcfa244f806799f5640

14
i18n/en/code.json Normal file
View File

@ -0,0 +1,14 @@
{
"theme.ErrorPageContent.title": {
"message": "Page not found",
"description": "The title of the 404 page"
},
"theme.NotFound.title": {
"message": "Page Not Found",
"description": "Title of the 404 page"
},
"theme.AnnouncementBar.closeButtonAriaLabel": {
"message": "Close",
"description": "The ARIA label for close button of announcement bar"
}
}

View File

@ -0,0 +1,25 @@
---
sidebar_label: "Introduction"
sidebar_position: 1
slug: /
---
# About GitLink
GitLink is the official open-source innovation service platform designated by CCF (China Computer Federation).
![GitLink](/img/ccf_gitlink.png)
# Platform Features
- **Distributed Collaborative Development**: Online file editing, branch management, contribution statistics, repository forking, and merge requests
- **One-stop Process Management**: Issues, milestones, notifications, labels, Wiki, and organization management
- **Efficient CI/CD Pipeline**: Lightweight workflow engine with custom configurations, static analysis, and artifact building
- **Multi-level Code Analysis**: Code traceability, license risk analysis, vulnerability detection, and hardening recommendations
- **Multi-dimensional User Profiling**: Development activity statistics, contribution calendar, capability modeling, and role positioning
# Help Documentation
The help documentation will help you fully understand the GitLink platform. Let's contribute to open-source innovation together!
> **Note**: English documentation is currently under development. For complete documentation, please switch to the Chinese version using the language selector in the top-right corner.

View File

@ -0,0 +1,10 @@
{
"title": {
"message": "GitLink Help Center",
"description": "The title in the navbar"
},
"item.label.帮助中心": {
"message": "Help Center",
"description": "Navbar item with label 帮助中心"
}
}

6649
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -6,6 +6,7 @@
"docusaurus": "docusaurus",
"dev": "docusaurus start --port 3000",
"build": "docusaurus build",
"build:kb": "node scripts/build-knowledge.js",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy",
"clear": "docusaurus clear",
@ -14,11 +15,11 @@
"write-heading-ids": "docusaurus write-heading-ids"
},
"dependencies": {
"@cmfcmf/docusaurus-search-local": "1.1.0",
"@docusaurus/core": "3.9.2",
"@docusaurus/plugin-client-redirects": "3.9.2",
"@docusaurus/plugin-pwa": "^3.9.2",
"@docusaurus/preset-classic": "3.9.2",
"@easyops-cn/docusaurus-search-local": "^0.35.0",
"@easyops-cn/docusaurus-search-local": "^0.55.1",
"@mdx-js/react": "^3.1.1",
"@node-rs/jieba": "^1.7.0",
"clsx": "1.2.1",

156
scripts/build-knowledge.js Normal file
View File

@ -0,0 +1,156 @@
/**
* 帮助中心 AI 助手 知识库构建脚本
*
* 作用扫描 docs/ 下所有 Markdown 文档去掉 frontmatter 与图片/代码噪声
* 按标题# / ## / ###切分成可检索的段落chunk输出为 knowledge.json
* 供前端 AI 助手做客户端检索RAG使用
*
* 用法node scripts/build-knowledge.js
* 文档有更新后重新运行一次即可
*/
const fs = require('fs');
const path = require('path');
const DOCS_DIR = path.resolve(__dirname, '..', 'docs');
const OUT_FILE = path.resolve(__dirname, '..', 'src', 'components', 'AIAssistant', 'knowledge.json');
/** 收集所有 .md 文件(相对路径) */
function walkMd(dir, base = '') {
const out = [];
for (const name of fs.readdirSync(dir)) {
const full = path.join(dir, name);
const rel = base ? `${base}/${name}` : name;
const stat = fs.statSync(full);
if (stat.isDirectory()) out.push(...walkMd(full, rel));
else if (name.endsWith('.md')) out.push({ abs: full, rel });
}
return out;
}
/** 去掉 frontmatter--- ... --- */
function stripFrontmatter(text) {
if (text.startsWith('---')) {
const end = text.indexOf('\n---', 3);
if (end !== -1) return text.slice(end + 4).replace(/^\s*\n/, '');
}
return text;
}
/** 取 frontmatter 里的 sidebar_label / label作为页面友好标题 */
function frontTitle(text, fileRel) {
const m = text.match(/^---([\s\S]*?)---/);
let label = '';
if (m) {
const lm = m[1].match(/sidebar_label:\s*['"]?([^'"\n]+)['"]?/);
const km = m[1].match(/label:\s*['"]?([^'"\n]+)['"]?/);
label = (lm && lm[1].trim()) || (km && km[1].trim()) || '';
}
if (!label) {
label = path.basename(fileRel, '.md');
}
return label.trim();
}
/** 计算页面在站点中的访问路径,与 Docusaurus routeBasePath:'/' 对齐 */
function pageRoute(fileRel, text) {
let slug = '';
const m = text.match(/^---([\s\S]*?)---/);
if (m) {
const sm = m[1].match(/slug:\s*['"]?([^'"\n]+)['"]?/);
if (sm) slug = sm[1].trim();
}
if (slug && slug !== '/') {
// 以 slug 里的内容作为路径(去掉前导斜杠)
return slug.replace(/^\/+/, '');
}
// 默认:目录/文件名(去掉 .md。intro.md 的 slug 是 / 单独处理。
const noExt = fileRel.replace(/\.md$/, '');
if (noExt === 'intro') return '';
return noExt;
}
/** 清洗一段文本去掉图片、HTML、链接保留文字、多余空行 */
function cleanText(text) {
return text
.replace(/!\[[^\]]*\]\([^)]*\)/g, '') // 图片
.replace(/<[^>]+>/g, ' ') // HTML 标签
.replace(/\[([^\]]+)\]\([^)]*\)/g, '$1') // 链接保留文字
.replace(/`{1,3}/g, '') // 代码反引号
.replace(/^\s{0,3}>\s?/gm, '') // 引用符
.replace(/\n{3,}/g, '\n\n')
.trim();
}
/** 按标题把一页切成多个 chunk */
function chunkPage(text, pageLabel, route, fileName) {
const lines = text.split('\n');
const chunks = [];
let curSection = pageLabel; // 当前小节标题
let buf = [];
const flush = () => {
const body = cleanText(buf.join('\n'));
if (body && body.length > 8) {
chunks.push({
title: pageLabel,
section: curSection,
path: '/' + route,
source: fileName,
text: body,
});
}
buf = [];
};
for (const raw of lines) {
const hm = raw.match(/^(#{1,4})\s+(.*)/);
if (hm) {
flush();
const level = hm[1].length;
const headText = cleanText(hm[2]);
// 用一级标题作为页面标题候选,二三级作为小节
if (level === 1) {
if (headText) { /* 一级标题作为页面正文一部分,不单独覆盖 pageLabel */ }
}
curSection = headText || pageLabel;
buf.push(raw); // 保留标题文字进正文
} else {
buf.push(raw);
}
}
flush();
return chunks;
}
function main() {
if (!fs.existsSync(DOCS_DIR)) {
console.error('[build-knowledge] docs 目录不存在:', DOCS_DIR);
process.exit(1);
}
const files = walkMd(DOCS_DIR);
const all = [];
let pageCount = 0;
for (const f of files) {
let raw;
try {
raw = fs.readFileSync(f.abs, 'utf8');
} catch (e) {
console.warn('[build-knowledge] 读取失败:', f.rel, e.message);
continue;
}
const label = frontTitle(raw, f.rel);
const route = pageRoute(f.rel, raw);
const body = stripFrontmatter(raw);
const chunks = chunkPage(body, label, route, f.rel);
if (chunks.length) {
all.push(...chunks);
pageCount += 1;
}
}
fs.mkdirSync(path.dirname(OUT_FILE), { recursive: true });
fs.writeFileSync(OUT_FILE, JSON.stringify(all, null, 0), 'utf8');
console.log(`[build-knowledge] 完成:${pageCount} 个文档,${all.length} 个段落 -> ${path.relative(process.cwd(), OUT_FILE)}`);
}
main();

View File

@ -2,7 +2,7 @@ module.exports = {
DocsSidebar: [
{
type: 'doc',
id: 'index',
id: 'intro',
label: 'Docs',
}
],

View File

@ -0,0 +1,91 @@
# GitLink 帮助中心 AI 智能助手
> 对应课程「任务五:新功能特性」的实现 —— 一个可在页面任意拖动、与用户交互、
> 回答内容**只来自帮助中心文档**的 AI 助手;与帮助中心无关的问题会回复「无关」。
---
## 一、这个 AI 工具需要 API 吗?
**需要。** 要让助手真正「听懂」自然语言、判断问题是否与帮助中心相关、并基于文档
生成自然流畅的回答必须接入一个大语言模型LLM
- 纯关键词匹配无法可靠地判断「无关」,也无法生成通顺的回答,所以本项目接入了 LLM。
- 本助手兼容任何 **OpenAI `/v1/chat/completions` 协议**的服务,例如:
- DeepSeek默认推荐国内可直连、便宜`https://api.deepseek.com/v1`,模型 `deepseek-chat`
- 通义千问:`https://dashscope.aliyuncs.com/compatible-mode/v1`,模型 `qwen-plus`
- 智谱 GLM`https://open.bigmodel.cn/api/paas/v4`,模型 `glm-4-flash`
- Moonshot / OpenAI 等
> **API Key 安全说明(重要)**:密钥**不会写入任何代码文件、不会提交到代码仓库、不会上传到远端**。
> 你只需在助手界面的「⚙ 设置」里粘贴一次,密钥只保存在**你当前浏览器的 localStorage** 中。
> 因此 `git push` 时绝不会泄露密钥。
---
## 二、如何使用(填入你的 API Key
1. 启动项目:
```bash
npm install --legacy-peer-deps # 安装依赖(已安装可跳过)
npm run build:kb # 生成知识库 knowledge.json文档更新后重跑
npm run dev # 开发预览http://localhost:3000
# 或生产构建npm run build -- --locale zh-cn && npm run serve
```
2. 打开页面后,右下角会出现一个蓝色 **AI** 圆形按钮。
3. **拖拽**:按住按钮可拖到屏幕任意位置(位置会被记住)。
4. **点击**按钮打开聊天面板 → 点右上角 **⚙ 设置**。
5. 在设置里:
- 点一个「服务厂商」快捷按钮(或手动填 Base URL / 模型名);
- 在 **API Key** 框粘贴你的密钥(如 DeepSeek 的 `sk-...`
- 点「保存」。
6. 回到聊天框提问即可。例如:
- 「如何创建代码仓库?」→ 基于文档回答并附 `📎 相关文档` 链接
- 「什么是疑修?」→ 基于文档回答
- 「今天天气怎么样?」→ 回复「无关」(与帮助中心无关)
---
## 三、工作原理(轻量 RAG
```
用户提问
① 客户端检索 (retrieval.js, BM25) ── 扫描 knowledge.json (来自 docs/)
│ └─ 完全检索不到任何相关内容 → 直接回复「无关」(不调用模型,省 token)
② 取最相关的 4 段文档作为「上下文」
③ 调用大模型 (llm.js) + 强约束系统提示:只能依据上下文回答,无关则回「无关」
④ 流式输出回答;若上下文含文档路径,附「📎 相关文档」可点击跳转
```
- **回答来源严格限定**:系统提示词强制模型「只能用提供的帮助中心资料回答」,
资料外的问题一律回「无关」,从而满足「回答都来自帮助中心文档」的要求。
- **知识库**`scripts/build-knowledge.js` 扫描 `docs/` 下全部 Markdown按标题切分、
去噪,生成 `knowledge.json`。文档有更新时重新执行 `npm run build:kb` 即可。
---
## 四、文件清单
| 文件 | 作用 |
|------|------|
| `src/components/AIAssistant/index.js` | 主组件:可拖拽浮动按钮 + 聊天面板 + 设置面板 |
| `src/components/AIAssistant/retrieval.js` | 客户端 BM25 检索(中文/英文分词) |
| `src/components/AIAssistant/llm.js` | OpenAI 兼容协议调用 + 强约束系统提示(流式) |
| `src/components/AIAssistant/styles.module.css` | 样式(支持亮/暗模式) |
| `src/components/AIAssistant/knowledge.json` | 知识库(由脚本生成,已纳入仓库,便于构建) |
| `scripts/build-knowledge.js` | 知识库构建脚本(`npm run build:kb` |
| `src/theme/Layout/index.js` | 已注入 `<AIAssistant />`,全站显示 |
---
## 五、生产部署的安全建议(可选)
当前为「静态站点 + 前端直连模型」方案,密钥存在用户浏览器,适合演示/个人使用。
若要正式上线面向公众,建议改为 **后端代理**:把密钥放在服务器环境变量里,
前端只请求你自己的后端接口,由后端转发给 LLM从而彻底隐藏密钥。
(如需此方案,可新建一个简单的 Node/Express 转发服务,本项目结构无需大改。)

View File

@ -0,0 +1,478 @@
/**
* GitLink 帮助中心 AI 智能助手可拖拽浮动组件
*
* 功能
* 1. 页面右下角浮动按钮支持鼠标/触摸拖拽到任意位置位置记忆到 localStorage
* 2. 点击打开聊天面板与用户交互回答内容全部来自帮助中心文档RAG
* 3. 与帮助中心无关的问题直接回复无关
* 4. 自带设置面板填入 API 地址 / Key / 模型即可使用密钥只存在本机浏览器
*
* 安全说明API Key 仅保存在当前浏览器的 localStorage不会写入代码不会上传到远端
* 如需生产环境部署建议改为后端代理转发见项目 README AI 助手一节
*/
import React, { useEffect, useRef, useState, useCallback } from 'react';
import styles from './styles.module.css';
import { retrieve } from './retrieval';
import { askAssistant } from './llm';
const LS = {
pos: 'ai_assistant_pos',
cfg: 'ai_assistant_config',
chat: 'ai_assistant_chat',
};
const DEFAULT_CFG = {
mode: 'server', // 'server' = 走后端共享Key(推荐) | 'local' = 访客自带Key(兜底)
backendUrl: '/api/ai',
baseURL: 'https://api.deepseek.com/v1',
apiKey: '',
model: 'deepseek-chat',
};
const QUICK_QUESTIONS = [
'如何创建一个代码仓库?',
'什么是疑修Issue',
'怎样配置 DevOps 流水线?',
'如何把 GitHub 项目导入 GitLink',
];
/* ----------------------------- 小工具 ----------------------------- */
function loadPos() {
try {
const p = JSON.parse(localStorage.getItem(LS.pos));
if (p && typeof p.x === 'number' && typeof p.y === 'number') return p;
} catch (e) { /* ignore */ }
return null;
}
function loadCfg() {
try {
const c = JSON.parse(localStorage.getItem(LS.cfg));
return { ...DEFAULT_CFG, ...(c || {}) };
} catch (e) { /* ignore */ }
return { ...DEFAULT_CFG };
}
/** 把助手回复里的“📎 相关文档:/xxx”渲染成可点击链接并保留换行 */
function renderAnswer(text) {
const lines = text.split('\n');
return lines.map((line, i) => {
const m = line.match(/^(.*?)相关文档[:]\s*(\/[^\s】】)]+)/);
if (m) {
return (
<span key={i}>
{m[1]}
<a className={styles.docLink} href={m[2]} target="_blank" rel="noreferrer">
{m[2]}
</a>
</span>
);
}
return (
<span key={i}>
{line}
{i < lines.length - 1 && <br />}
</span>
);
});
}
/* ----------------------------- 主组件 ----------------------------- */
export default function AIAssistant() {
const [mounted, setMounted] = useState(false);
const [open, setOpen] = useState(false);
const [showSettings, setShowSettings] = useState(false);
// 浮动按钮位置(左上角坐标)
const [pos, setPos] = useState({ x: -1, y: -1 }); // -1 表示使用默认(右下角)
const [vw, setVw] = useState(typeof window !== 'undefined' ? window.innerWidth : 1280);
// 聊天状态
const [messages, setMessages] = useState([]); // {role:'user'|'assistant', content, pending?}
const [input, setInput] = useState('');
const [loading, setLoading] = useState(false);
const [cfg, setCfg] = useState(DEFAULT_CFG);
const abortRef = useRef(null);
const scrollRef = useRef(null);
const dragState = useRef({ dragging: false, moved: false, startX: 0, startY: 0, origX: 0, origY: 0 });
/* ---- 初始化(仅在浏览器端) ---- */
useEffect(() => {
setMounted(true);
setCfg(loadCfg());
const p = loadPos();
if (p) setPos(p);
try {
const saved = JSON.parse(localStorage.getItem(LS.chat) || '[]');
if (Array.isArray(saved) && saved.length) setMessages(saved);
} catch (e) { /* ignore */ }
const onResize = () => setVw(window.innerWidth);
window.addEventListener('resize', onResize);
return () => window.removeEventListener('resize', onResize);
}, []);
/* ---- 消息变化时持久化 + 滚到底部 ---- */
useEffect(() => {
if (!mounted) return;
try { localStorage.setItem(LS.chat, JSON.stringify(messages.slice(-30))); } catch (e) { /* ignore */ }
}, [messages, mounted]);
useEffect(() => {
if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
}, [messages, open]);
/* ---- 拖拽逻辑pointer events ---- */
const onPointerDown = (e) => {
// 打开状态下不拖拽(避免误触)
if (open) return;
dragState.current = {
dragging: true,
moved: false,
startX: e.clientX,
startY: e.clientY,
origX: pos.x < 0 ? window.innerWidth - 72 : pos.x,
origY: pos.y < 0 ? window.innerHeight - 72 : pos.y,
};
e.currentTarget.setPointerCapture(e.pointerId);
};
const onPointerMove = (e) => {
const ds = dragState.current;
if (!ds.dragging) return;
const dx = e.clientX - ds.startX;
const dy = e.clientY - ds.startY;
if (Math.abs(dx) > 4 || Math.abs(dy) > 4) ds.moved = true;
const nx = Math.max(8, Math.min(window.innerWidth - 64, ds.origX + dx));
const ny = Math.max(8, Math.min(window.innerHeight - 64, ds.origY + dy));
setPos({ x: nx, y: ny });
};
const onPointerUp = (e) => {
const ds = dragState.current;
ds.dragging = false;
try { e.currentTarget.releasePointerCapture(e.pointerId); } catch (err) { /* ignore */ }
// 只有“没怎么动”才算点击 → 打开面板
if (!ds.moved) {
setOpen(true);
} else {
try { localStorage.setItem(LS.pos, JSON.stringify(pos)); } catch (err) { /* ignore */ }
}
};
/* ---- 发送消息 ---- */
const send = useCallback(async (text) => {
const question = (text ?? input).trim();
if (!question || loading) return;
// 仅 local 模式才需要访客自己填 Keyserver 模式由后端持有 Key
if (cfg.mode === 'local' && !cfg.apiKey) {
setOpen(true);
setShowSettings(true);
return;
}
setInput('');
const userMsg = { role: 'user', content: question };
setMessages((m) => [...m, userMsg, { role: 'assistant', content: '', pending: true }]);
setLoading(true);
// 1) 客户端检索帮助中心资料
const { chunks, hasHit } = retrieve(question, 4);
// 2) 完全检索不到任何相关内容 → 直接“无关”,不调用模型(省 token
if (!hasHit) {
setMessages((m) => {
const arr = m.slice();
arr[arr.length - 1] = { role: 'assistant', content: '无关' };
return arr;
});
setLoading(false);
return;
}
// 3) 调用大模型,流式回填
const controller = new AbortController();
abortRef.current = controller;
try {
let acc = '';
await askAssistant(cfg, question, chunks, {
signal: controller.signal,
onDelta: (d) => {
acc += d;
setMessages((m) => {
const arr = m.slice();
arr[arr.length - 1] = { role: 'assistant', content: acc, pending: false };
return arr;
});
},
});
// 兜底:流式没回填成功时直接设置最终文本
setMessages((m) => {
const arr = m.slice();
const last = arr[arr.length - 1];
if (last && last.role === 'assistant') arr[arr.length - 1] = { ...last, content: acc || last.content, pending: false };
return arr;
});
} catch (err) {
setMessages((m) => {
const arr = m.slice();
arr[arr.length - 1] = {
role: 'assistant',
content: `⚠️ 出错了:${err.message || err}`,
pending: false,
error: true,
};
return arr;
});
} finally {
setLoading(false);
abortRef.current = null;
}
}, [input, loading, cfg]);
const stop = () => {
if (abortRef.current) abortRef.current.abort();
setLoading(false);
setMessages((m) => {
const arr = m.slice();
const last = arr[arr.length - 1];
if (last && last.pending) arr[arr.length - 1] = { ...last, pending: false };
return arr;
});
};
const clearChat = () => {
setMessages([]);
try { localStorage.removeItem(LS.chat); } catch (e) { /* ignore */ }
};
/* ---- 设置保存 ---- */
const saveCfg = (next) => {
setCfg(next);
try { localStorage.setItem(LS.cfg, JSON.stringify(next)); } catch (e) { /* ignore */ }
setShowSettings(false);
};
/* ----------------------------- 渲染 ----------------------------- */
if (!mounted) return null;
const isMobile = vw < 600;
const fabSize = 56;
// 计算浮动按钮的 fixed 位置
const fabStyle = pos.x < 0
? { right: 24, bottom: 24 }
: { left: pos.x, top: pos.y };
// 计算面板位置(桌面端,跟随按钮,并夹在视口内)
let panelStyle = {};
if (open && !isMobile) {
const panelW = 384;
const panelH = 560;
const fabX = pos.x < 0 ? window.innerWidth - 24 - fabSize : pos.x;
const fabY = pos.y < 0 ? window.innerHeight - 24 - fabSize : pos.y;
let left = fabX + fabSize - panelW; // 面板右对齐到按钮
let top = fabY - panelH - 12; // 面板在按钮上方
if (top < 12) top = fabY + fabSize + 12; // 上方放不下就放下方
if (left < 12) left = 12;
if (left + panelW > vw - 12) left = vw - panelW - 12;
panelStyle = { left, top, width: panelW, height: panelH };
}
return (
<>
{/* 浮动按钮 */}
<button
type="button"
className={`${styles.fab} ${open ? styles.fabHidden : ''}`}
style={fabStyle}
onPointerDown={onPointerDown}
onPointerMove={onPointerMove}
onPointerUp={onPointerUp}
aria-label="GitLink AI 助手"
title="GitLink AI 助手(可拖拽)"
>
<span className={styles.fabIcon}>AI</span>
</button>
{/* 聊天面板 */}
{open && (
<div className={`${styles.panel} ${isMobile ? styles.panelMobile : ''}`} style={panelStyle}>
{/* 标题栏 */}
<div className={styles.header}>
<div className={styles.headerTitle}>
<span className={styles.headerBadge}>AI</span>
GitLink 帮助助手
</div>
<div className={styles.headerBtns}>
<button className={styles.iconBtn} onClick={() => setShowSettings(true)} title="设置"></button>
<button className={styles.iconBtn} onClick={clearChat} title="清空对话"></button>
<button className={styles.iconBtn} onClick={() => setOpen(false)} title="关闭"></button>
</div>
</div>
{/* 消息区 */}
<div className={styles.body} ref={scrollRef}>
{messages.length === 0 && (
<div className={styles.welcome}>
<div className={styles.welcomeTitle}>👋 你好我是 GitLink 帮助中心 AI 助手</div>
<div className={styles.welcomeDesc}>
我可以帮你解答 GitLink 平台的使用问题回答内容均来自帮助中心文档
与帮助中心无关的问题我会回复无关
</div>
<div className={styles.quickList}>
{QUICK_QUESTIONS.map((q) => (
<button key={q} className={styles.quickBtn} onClick={() => send(q)}>{q}</button>
))}
</div>
</div>
)}
{messages.map((m, i) => (
<div key={i} className={`${styles.msg} ${m.role === 'user' ? styles.msgUser : styles.msgBot}`}>
<div className={`${styles.bubble} ${m.role === 'user' ? styles.bubbleUser : styles.bubbleBot} ${m.error ? styles.bubbleErr : ''}`}>
{m.pending && !m.content ? (
<span className={styles.typing}>思考中<span className={styles.dot}>·</span><span className={styles.dot}>·</span><span className={styles.dot}>·</span></span>
) : (
renderAnswer(m.content)
)}
{m.pending && m.content ? <span className={styles.cursor}></span> : null}
</div>
</div>
))}
</div>
{/* 输入区 */}
<div className={styles.footer}>
<textarea
className={styles.textarea}
placeholder="输入你的问题…Enter 发送Shift+Enter 换行)"
value={input}
rows={1}
onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
send();
}
}}
/>
{loading ? (
<button className={styles.sendBtn} onClick={stop} title="停止"></button>
) : (
<button className={styles.sendBtn} onClick={() => send()} title="发送"></button>
)}
</div>
</div>
)}
{/* 设置弹层 */}
{showSettings && (
<SettingsModal cfg={cfg} onSave={saveCfg} onClose={() => setShowSettings(false)} />
)}
</>
);
}
/* ----------------------------- 设置弹层 ----------------------------- */
function SettingsModal({ cfg, onSave, onClose }) {
const [mode, setMode] = useState(cfg.mode || 'server');
const [backendUrl, setBackendUrl] = useState(cfg.backendUrl || '/api/ai');
const [baseURL, setBaseURL] = useState(cfg.baseURL);
const [apiKey, setApiKey] = useState(cfg.apiKey);
const [model, setModel] = useState(cfg.model);
const presets = [
{ name: 'DeepSeek', baseURL: 'https://api.deepseek.com/v1', model: 'deepseek-chat' },
{ name: '通义千问', baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1', model: 'qwen-plus' },
{ name: '智谱 GLM', baseURL: 'https://open.bigmodel.cn/api/paas/v4', model: 'glm-4-flash' },
{ name: 'Moonshot', baseURL: 'https://api.moonshot.cn/v1', model: 'moonshot-v1-8k' },
{ name: 'OpenAI', baseURL: 'https://api.openai.com/v1', model: 'gpt-4o-mini' },
];
return (
<div className={styles.modalMask} onClick={onClose}>
<div className={styles.modal} onClick={(e) => e.stopPropagation()}>
<div className={styles.modalHeader}>
<span>AI 助手设置</span>
<button className={styles.iconBtn} onClick={onClose}></button>
</div>
<div className={styles.modalBody}>
{/* 模式切换 */}
<div className={styles.field}>
<label>调用方式</label>
<div className={styles.presetRow}>
<button
className={`${styles.presetBtn} ${mode === 'server' ? styles.presetActive : ''}`}
onClick={() => setMode('server')}
>
服务器共享Key推荐
</button>
<button
className={`${styles.presetBtn} ${mode === 'local' ? styles.presetActive : ''}`}
onClick={() => setMode('local')}
>
使用我自己的Key
</button>
</div>
</div>
{mode === 'server' ? (
<>
<div className={styles.field}>
<label>后端代理地址</label>
<input className={styles.input} value={backendUrl} onChange={(e) => setBackendUrl(e.target.value)} placeholder="/api/ai" />
</div>
<div className={styles.tip}>
此模式下所有访客打开即可直接提问无需填写任何 Key
Key 安全地保存在服务器环境变量/root/.ai_env不进代码不上传 git
</div>
</>
) : (
<>
<div className={styles.field}>
<label>服务厂商快捷选择</label>
<div className={styles.presetRow}>
{presets.map((p) => (
<button
key={p.name}
className={`${styles.presetBtn} ${baseURL === p.baseURL ? styles.presetActive : ''}`}
onClick={() => { setBaseURL(p.baseURL); setModel(p.model); }}
>
{p.name}
</button>
))}
</div>
</div>
<div className={styles.field}>
<label>API 地址Base URL</label>
<input className={styles.input} value={baseURL} onChange={(e) => setBaseURL(e.target.value)} placeholder="https://api.deepseek.com/v1" />
</div>
<div className={styles.field}>
<label>模型名称</label>
<input className={styles.input} value={model} onChange={(e) => setModel(e.target.value)} placeholder="deepseek-chat" />
</div>
<div className={styles.field}>
<label>API Key <span className={styles.hint}>(只保存在本机浏览器不会上传)</span></label>
<input className={styles.input} type="password" value={apiKey} onChange={(e) => setApiKey(e.target.value)} placeholder="sk-..." />
</div>
<div className={styles.tip}>
说明密钥仅写入当前浏览器的 localStorage不会出现在项目代码里也不会提交到代码仓库
</div>
</>
)}
</div>
<div className={styles.modalFooter}>
<button className={styles.cancelBtn} onClick={onClose}>取消</button>
<button
className={styles.saveBtn}
onClick={() => onSave({ mode, backendUrl, baseURL, apiKey, model })}
>
保存
</button>
</div>
</div>
</div>
);
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,129 @@
/**
* LLM 调用模块
*
* 支持两种模式 cfg.mode 决定
* - 'server'默认/推荐调用同源后端代理 /api/ai由服务器持有 Key 转发给大模型
* 访客无需填写任何 KeyKey 只存在服务器的环境变量里不进代码不上传
* - 'local'兜底浏览器直连大模型OpenAI 兼容协议Key 由用户在设置里填写
* 仅存于其本机 localStorage适合本地开发或后端不可用时
*
* 两种模式都强制只依据帮助中心文档回答无关则回无关
*/
/* ---------------- 系统 promptlocal 兜底模式用server 模式由后端持有) ---------------- */
const SYSTEM_PROMPT = `你是「GitLink 帮助中心」的智能助手,专门帮助用户解答关于 GitLink 平台确实开源CCF 官方开源创新服务平台)使用方法的问题。
必须遵守的规则
1. 你只能依据下面帮助中心参考资料里的内容来回答用户问题禁止使用资料以外的知识禁止编造
2. 在多段资料中只要有一段与问题相关就据其作答简洁分点保留关键步骤入口位置和按钮名称
3. 帮助中心文档以截图为主文字为辅若资料只给出了入口或概述而省略了图文步骤请据此简述操作路径并在末尾引导用户点开文档链接查看完整图文步骤不要因为没看到详细步骤就拒绝回答
4. 回答末尾若资料中包含对应文档路径形如 /代码库管理/仓库创建请用一行给出📎 相关文档<路径>方便用户点击查看
5. 只有当所有资料都与问题毫无关系例如闲聊天气其它产品其它网站或资料完全为空时才只回复两个字无关
帮助中心参考资料
{CONTEXT}
`;
function buildMessages(question, contextChunks) {
const context = contextChunks && contextChunks.length
? contextChunks
.map((c, i) => `【资料${i + 1}】来源:${c.title}${c.section ? ' / ' + c.section : ''}(页面 ${c.path}\n${c.text}`)
.join('\n\n')
: '(本次未检索到任何相关资料)';
return [
{ role: 'system', content: SYSTEM_PROMPT.replace('{CONTEXT}', context) },
{ role: 'user', content: question },
];
}
/** 解析 SSE 流,逐 token 回调 */
async function consumeSseStream(body, onDelta) {
const reader = body.getReader();
const decoder = new TextDecoder('utf-8');
let buf = '';
let full = '';
while (true) {
const { value, done } = await reader.read();
if (done) break;
buf += decoder.decode(value, { stream: true });
const lines = buf.split('\n');
buf = lines.pop() || '';
for (const line of lines) {
const s = line.trim();
if (!s || !s.startsWith('data:')) continue;
const data = s.slice(5).trim();
if (data === '[DONE]') continue;
try {
const json = JSON.parse(data);
const delta = json.choices?.[0]?.delta?.content || '';
if (delta) {
full += delta;
onDelta && onDelta(delta);
}
} catch (e) { /* 忽略非 JSON 心跳行 */ }
}
}
return full;
}
/* ---------------- server 模式:调用后端代理 ---------------- */
async function askViaBackend(backendUrl, question, contextChunks, opts = {}) {
const resp = await fetch(backendUrl || '/api/ai', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ question, context: contextChunks }),
signal: opts.signal,
});
if (!resp.ok) {
let msg = `接口请求失败HTTP ${resp.status}`;
try {
const j = await resp.json();
if (j && j.error) msg = j.error;
} catch (e) { /* ignore */ }
throw new Error(msg);
}
return consumeSseStream(resp.body, opts.onDelta);
}
/* ---------------- local 模式:浏览器直连大模型 ---------------- */
async function askDirect(cfg, question, contextChunks, opts = {}) {
if (!cfg.apiKey) throw new Error('尚未配置 API Key请在“设置”里切换到“使用我自己的Key”并填写。');
const base = (cfg.baseURL || 'https://api.deepseek.com/v1').replace(/\/+$/, '');
const url = base.endsWith('/chat/completions') ? base : `${base}/chat/completions`;
const messages = buildMessages(question, contextChunks);
const resp = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${cfg.apiKey}`,
},
body: JSON.stringify({
model: cfg.model || 'deepseek-chat',
messages,
temperature: 0.2,
stream: true,
}),
signal: opts.signal,
});
if (!resp.ok) {
let detail = '';
try { detail = await resp.text(); } catch (e) { /* ignore */ }
throw new Error(`接口请求失败HTTP ${resp.status})。${detail.slice(0, 200)}`);
}
return consumeSseStream(resp.body, opts.onDelta);
}
/**
* 统一入口
* @param {Object} cfg { mode, backendUrl, baseURL, apiKey, model }
* @param {string} question
* @param {Array} contextChunks
* @param {Object} opts { onDelta, signal }
*/
export async function askAssistant(cfg, question, contextChunks, opts = {}) {
if (cfg && cfg.mode === 'local') return askDirect(cfg, question, contextChunks, opts);
return askViaBackend(cfg && cfg.backendUrl, question, contextChunks, opts);
}

View File

@ -0,0 +1,104 @@
/**
* 客户端检索轻量 RAG Retrieve 部分
*
* 不依赖任何外部服务纯前端在浏览器里跑
* 1) 分词英文/数字按词中文用滑动二元组(bigram) + 单字组合"创建/仓库/疑修"
* 这类两字词能被当成一个词来匹配显著好于纯单字分词
* 2) 打分 BM25 公式并对出现在标题/小节里的查询词额外大幅加分
* 因为标题是人工命名的强信号用户问"仓库"标题就叫"仓库创建"的文档应优先
* 3) 返回 top-k 段落作为送给大模型的上下文
*
* 目的只把相关文档片段喂给大模型既省钱又让回答严格基于帮助中心内容
* 完全检索不到任何相关内容时直接判定无关无需调用模型
*/
import knowledge from './knowledge.json';
const STOP = new Set([
'的', '了', '是', '在', '和', '与', '或', '也', '都', '就', '你', '我', '他',
'她', '它', '这', '那', '有', '为', '及', '以', '上', '下', '不', '个', '中',
'一', '个', '怎么', '如何', '什么', '可以', '能够', 'the', 'to', 'of', 'and',
'a', 'in', 'is', 'it', 'for', 'on', 'how', 'what',
]);
/** 分词:英文词(>=2) + 中文 bigram(滑动2字) + 中文单字兜底 */
export function tokenize(text) {
if (!text) return [];
const s = String(text).toLowerCase();
const out = [];
let m;
const wordRe = /[a-z0-9_]+/g;
while ((m = wordRe.exec(s)) !== null) {
if (m[0].length >= 2) out.push(m[0]);
}
const cjkRuns = s.match(/[一-龥]+/g) || [];
for (const run of cjkRuns) {
if (run.length === 1) {
out.push(run);
} else {
for (let i = 0; i < run.length - 1; i++) out.push(run.slice(i, i + 2));
for (const ch of run) out.push(ch);
}
}
return out.filter((t) => !STOP.has(t));
}
let INDEX = null;
function buildIndex() {
const docs = knowledge.map((chunk, i) => {
const bodyToks = tokenize(chunk.text);
// 标题/小节作为强信号:单独建集合,并在 hay 里重复 3 次
const titleToks = tokenize(`${chunk.title} ${chunk.section}`);
const hay = [...titleToks, ...titleToks, ...titleToks, ...bodyToks];
const tf = new Map();
for (const t of hay) tf.set(t, (tf.get(t) || 0) + 1);
return { id: i, len: hay.length || 1, tf, titleSet: new Set(titleToks) };
});
const N = docs.length;
const avg = docs.reduce((s, d) => s + d.len, 0) / (N || 1);
const df = new Map();
for (const d of docs) {
for (const t of d.tf.keys()) df.set(t, (df.get(t) || 0) + 1);
}
INDEX = { docs, N, avg, df };
}
/**
* BM25 打分检索
* @param {string} query 用户问题
* @param {number} k 返回条数默认 6
* @returns {{chunks: any[], maxScore: number, hasHit: boolean}}
*/
export function retrieve(query, k = 6) {
if (!INDEX) buildIndex();
const qTerms = Array.from(new Set(tokenize(query)));
if (qTerms.length === 0) return { chunks: [], maxScore: 0, hasHit: false };
const { docs, N, avg, df } = INDEX;
const k1 = 1.5;
const b = 0.75;
const TITLE_HIT_BONUS = 6; // 查询词命中标题/小节的额外加分
const scored = [];
for (const d of docs) {
let score = 0;
let titleHits = 0;
for (const t of qTerms) {
const f = d.tf.get(t);
if (f) {
const n = df.get(t) || 1;
const idf = Math.log(1 + (N - n + 0.5) / (n + 0.5));
const denom = f + k1 * (1 - b + (b * d.len) / avg);
score += idf * ((f * (k1 + 1)) / denom);
}
if (d.titleSet.has(t)) titleHits += 1;
}
score += titleHits * TITLE_HIT_BONUS;
if (score > 0) scored.push({ id: d.id, score });
}
if (scored.length === 0) return { chunks: [], maxScore: 0, hasHit: false };
scored.sort((a, c) => c.score - a.score);
const top = scored.slice(0, k);
const maxScore = top[0].score;
const chunks = top.map((s) => knowledge[s.id]);
return { chunks, maxScore, hasHit: true };
}

View File

@ -0,0 +1,401 @@
/* ===================== 浮动按钮 ===================== */
.fab {
position: fixed;
width: 56px;
height: 56px;
border-radius: 50%;
border: none;
cursor: grab;
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #466aff 0%, #00c6ff 100%);
box-shadow: 0 6px 20px rgba(70, 106, 255, 0.45);
transition: transform 0.18s ease, box-shadow 0.18s ease, opacity 0.2s ease;
touch-action: none;
user-select: none;
}
.fab:hover {
transform: scale(1.06);
box-shadow: 0 8px 26px rgba(70, 106, 255, 0.6);
}
.fab:active {
cursor: grabbing;
transform: scale(0.97);
}
.fabIcon {
color: #fff;
font-weight: 800;
font-size: 18px;
letter-spacing: 1px;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}
.fabHidden {
opacity: 0;
pointer-events: none;
transform: scale(0.6);
}
/* ===================== 聊天面板 ===================== */
.panel {
position: fixed;
z-index: 9999;
display: flex;
flex-direction: column;
background: var(--ifm-background-surface-color, #ffffff);
border: 1px solid var(--ifm-color-emphasis-300, #e3e6ec);
border-radius: 16px;
box-shadow: 0 16px 50px rgba(0, 0, 0, 0.22);
overflow: hidden;
animation: pop 0.18s ease;
}
@keyframes pop {
from { opacity: 0; transform: translateY(8px) scale(0.98); }
to { opacity: 1; transform: translateY(0) scale(1); }
}
.panelMobile {
left: 0 !important;
right: 0 !important;
top: auto !important;
bottom: 0 !important;
width: 100% !important;
height: 78vh !important;
border-radius: 16px 16px 0 0;
}
/* 标题栏 */
.header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 14px;
background: linear-gradient(135deg, #466aff 0%, #5b7bff 100%);
color: #fff;
}
.headerTitle {
display: flex;
align-items: center;
gap: 8px;
font-weight: 700;
font-size: 15px;
}
.headerBadge {
background: rgba(255, 255, 255, 0.25);
border-radius: 6px;
padding: 1px 7px;
font-size: 12px;
font-weight: 800;
}
.headerBtns {
display: flex;
gap: 2px;
}
.iconBtn {
background: transparent;
border: none;
color: #fff;
width: 30px;
height: 30px;
border-radius: 8px;
cursor: pointer;
font-size: 15px;
transition: background 0.15s ease;
}
.iconBtn:hover {
background: rgba(255, 255, 255, 0.2);
}
/* 消息区 */
.body {
flex: 1;
overflow-y: auto;
padding: 16px 14px;
background: var(--ifm-background-color, #f7f8fa);
scroll-behavior: smooth;
}
.welcome {
text-align: center;
padding: 18px 6px;
}
.welcomeTitle {
font-size: 15px;
font-weight: 700;
color: var(--ifm-color-emphasis-900, #1f2329);
margin-bottom: 8px;
}
.welcomeDesc {
font-size: 13px;
line-height: 1.7;
color: var(--ifm-color-emphasis-700, #4e5969);
margin-bottom: 16px;
}
.quickList {
display: flex;
flex-direction: column;
gap: 8px;
align-items: stretch;
}
.quickBtn {
background: var(--ifm-background-surface-color, #fff);
border: 1px solid var(--ifm-color-emphasis-300, #e3e6ec);
border-radius: 10px;
padding: 9px 12px;
font-size: 13px;
color: var(--ifm-color-emphasis-800, #1f2329);
cursor: pointer;
text-align: left;
transition: all 0.15s ease;
}
.quickBtn:hover {
border-color: #466aff;
color: #466aff;
background: rgba(70, 106, 255, 0.06);
}
/* 单条消息 */
.msg {
display: flex;
margin-bottom: 12px;
}
.msgUser {
justify-content: flex-end;
}
.msgBot {
justify-content: flex-start;
}
.bubble {
max-width: 82%;
padding: 9px 13px;
border-radius: 14px;
font-size: 13.5px;
line-height: 1.7;
word-break: break-word;
white-space: pre-wrap;
}
.bubbleUser {
background: #466aff;
color: #fff;
border-bottom-right-radius: 4px;
}
.bubbleBot {
background: var(--ifm-background-surface-color, #fff);
color: var(--ifm-color-emphasis-900, #1f2329);
border: 1px solid var(--ifm-color-emphasis-300, #e3e6ec);
border-bottom-left-radius: 4px;
}
.bubbleErr {
border-color: #ff6b6b;
color: #d33;
background: rgba(255, 107, 107, 0.08);
}
.docLink {
color: #466aff;
text-decoration: underline;
word-break: break-all;
}
.cursor {
display: inline-block;
animation: blink 1s steps(2) infinite;
color: #466aff;
}
@keyframes blink {
50% { opacity: 0; }
}
.typing {
color: var(--ifm-color-emphasis-600, #8a909c);
}
.dot {
animation: blink 1.2s infinite;
}
.dot:nth-child(2) { animation-delay: 0.2s; }
.dot:nth-child(3) { animation-delay: 0.4s; }
/* 输入区 */
.footer {
display: flex;
align-items: flex-end;
gap: 8px;
padding: 10px 12px;
border-top: 1px solid var(--ifm-color-emphasis-300, #e3e6ec);
background: var(--ifm-background-surface-color, #fff);
}
.textarea {
flex: 1;
resize: none;
max-height: 110px;
border: 1px solid var(--ifm-color-emphasis-300, #e3e6ec);
border-radius: 10px;
padding: 9px 11px;
font-size: 13.5px;
line-height: 1.5;
background: var(--ifm-background-color, #fff);
color: var(--ifm-color-emphasis-900, #1f2329);
outline: none;
font-family: inherit;
}
.textarea:focus {
border-color: #466aff;
box-shadow: 0 0 0 2px rgba(70, 106, 255, 0.15);
}
.sendBtn {
width: 38px;
height: 38px;
border-radius: 10px;
border: none;
background: #466aff;
color: #fff;
font-size: 15px;
cursor: pointer;
flex-shrink: 0;
transition: background 0.15s ease;
}
.sendBtn:hover {
background: #3655e8;
}
/* ===================== 设置弹层 ===================== */
.modalMask {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.4);
z-index: 10000;
display: flex;
align-items: center;
justify-content: center;
padding: 16px;
animation: pop 0.16s ease;
}
.modal {
width: 100%;
max-width: 420px;
background: var(--ifm-background-surface-color, #fff);
border-radius: 14px;
box-shadow: 0 16px 50px rgba(0, 0, 0, 0.3);
overflow: hidden;
}
.modalHeader {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 16px;
font-weight: 700;
font-size: 15px;
color: var(--ifm-color-emphasis-900, #1f2329);
border-bottom: 1px solid var(--ifm-color-emphasis-300, #e3e6ec);
}
.modalHeader .iconBtn {
color: var(--ifm-color-emphasis-600, #8a909c);
}
.modalHeader .iconBtn:hover {
background: var(--ifm-color-emphasis-200, #eef0f3);
}
.modalBody {
padding: 16px;
max-height: 60vh;
overflow-y: auto;
}
.field {
margin-bottom: 14px;
}
.field label {
display: block;
font-size: 13px;
font-weight: 600;
color: var(--ifm-color-emphasis-800, #1f2329);
margin-bottom: 6px;
}
.hint {
font-weight: 400;
color: var(--ifm-color-emphasis-600, #8a909c);
font-size: 12px;
}
.input {
width: 100%;
border: 1px solid var(--ifm-color-emphasis-300, #e3e6ec);
border-radius: 8px;
padding: 9px 11px;
font-size: 13px;
background: var(--ifm-background-color, #fff);
color: var(--ifm-color-emphasis-900, #1f2329);
outline: none;
box-sizing: border-box;
font-family: inherit;
}
.input:focus {
border-color: #466aff;
box-shadow: 0 0 0 2px rgba(70, 106, 255, 0.15);
}
.presetRow {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.presetBtn {
border: 1px solid var(--ifm-color-emphasis-300, #e3e6ec);
background: var(--ifm-background-color, #fff);
color: var(--ifm-color-emphasis-800, #1f2329);
border-radius: 7px;
padding: 5px 11px;
font-size: 12px;
cursor: pointer;
transition: all 0.15s ease;
}
.presetBtn:hover {
border-color: #466aff;
}
.presetActive {
border-color: #466aff;
background: rgba(70, 106, 255, 0.1);
color: #466aff;
}
.tip {
margin-top: 4px;
padding: 9px 11px;
background: rgba(70, 106, 255, 0.06);
border-radius: 8px;
font-size: 12px;
line-height: 1.6;
color: var(--ifm-color-emphasis-700, #4e5969);
}
.modalFooter {
display: flex;
justify-content: flex-end;
gap: 8px;
padding: 12px 16px;
border-top: 1px solid var(--ifm-color-emphasis-300, #e3e6ec);
}
.cancelBtn {
border: 1px solid var(--ifm-color-emphasis-300, #e3e6ec);
background: var(--ifm-background-color, #fff);
color: var(--ifm-color-emphasis-800, #1f2329);
border-radius: 8px;
padding: 8px 16px;
font-size: 13px;
cursor: pointer;
}
.cancelBtn:hover {
background: var(--ifm-color-emphasis-200, #eef0f3);
}
.saveBtn {
border: none;
background: #466aff;
color: #fff;
border-radius: 8px;
padding: 8px 18px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
}
.saveBtn:hover {
background: #3655e8;
}
/* ===================== 暗色模式微调 ===================== */
:global(html[data-theme='dark']) .bubbleBot {
background: #2a2b3d;
border-color: #3a3b5c;
}
:global(html[data-theme='dark']) .body {
background: #1a1b2e;
}

View File

@ -0,0 +1,48 @@
import React, { useState, useEffect } from 'react';
export default function BackToTop() {
const [visible, setVisible] = useState(false);
useEffect(() => {
const toggleVisible = () => {
setVisible(window.scrollY > 300);
};
window.addEventListener('scroll', toggleVisible);
return () => window.removeEventListener('scroll', toggleVisible);
}, []);
const scrollToTop = () => {
window.scrollTo({ top: 0, behavior: 'smooth' });
};
return (
<button
onClick={scrollToTop}
aria-label="回到顶部"
style={{
position: 'fixed',
bottom: '2rem',
right: '2rem',
width: '44px',
height: '44px',
borderRadius: '50%',
border: 'none',
background: 'var(--ifm-color-primary)',
color: '#fff',
fontSize: '1.3rem',
cursor: 'pointer',
opacity: visible ? 1 : 0,
transform: visible ? 'translateY(0)' : 'translateY(20px)',
transition: 'all 0.3s ease',
zIndex: 1000,
boxShadow: '0 2px 10px rgba(0,0,0,0.2)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
pointerEvents: visible ? 'auto' : 'none',
}}
>
</button>
);
}

View File

@ -0,0 +1,105 @@
import React, { useState, useEffect } from 'react';
import { useLocation } from '@docusaurus/router';
export default function DocFeedback() {
const location = useLocation();
const [feedback, setFeedback] = useState(null);
useEffect(() => {
setFeedback(null);
}, [location.pathname]);
const handleFeedback = (type) => {
const feedbacks = JSON.parse(localStorage.getItem('doc-feedbacks') || '{}');
feedbacks[location.pathname] = {
type,
timestamp: new Date().toISOString(),
};
localStorage.setItem('doc-feedbacks', JSON.stringify(feedbacks));
setFeedback(type);
};
return (
<div
style={{
marginTop: '3rem',
padding: '1.5rem',
borderTop: '1px solid var(--ifm-color-emphasis-300)',
borderRadius: '8px',
}}
>
{feedback ? (
<div
style={{
textAlign: 'center',
color: 'var(--ifm-color-success)',
fontSize: '1rem',
}}
>
感谢您的反馈我们会持续改进文档质量
</div>
) : (
<>
<p
style={{
fontSize: '1rem',
fontWeight: 600,
marginBottom: '1rem',
color: 'var(--ifm-color-emphasis-800)',
}}
>
这篇文档对您有帮助吗
</p>
<div style={{ display: 'flex', gap: '1rem' }}>
<button
onClick={() => handleFeedback('positive')}
style={{
padding: '8px 24px',
border: '1px solid var(--ifm-color-success)',
borderRadius: '6px',
background: 'transparent',
color: 'var(--ifm-color-success)',
cursor: 'pointer',
fontSize: '0.9rem',
transition: 'all 0.2s',
}}
onMouseOver={(e) => {
e.target.style.background = 'var(--ifm-color-success)';
e.target.style.color = '#fff';
}}
onMouseOut={(e) => {
e.target.style.background = 'transparent';
e.target.style.color = 'var(--ifm-color-success)';
}}
>
有帮助
</button>
<button
onClick={() => handleFeedback('negative')}
style={{
padding: '8px 24px',
border: '1px solid var(--ifm-color-danger)',
borderRadius: '6px',
background: 'transparent',
color: 'var(--ifm-color-danger)',
cursor: 'pointer',
fontSize: '0.9rem',
transition: 'all 0.2s',
}}
onMouseOver={(e) => {
e.target.style.background = 'var(--ifm-color-danger)';
e.target.style.color = '#fff';
}}
onMouseOut={(e) => {
e.target.style.background = 'transparent';
e.target.style.color = 'var(--ifm-color-danger)';
}}
>
没帮助
</button>
</div>
</>
)}
</div>
);
}

View File

@ -7,7 +7,7 @@ export default function LanguageSwitcher() {
const locales = [
{ code: 'en', label: 'English' },
{ code: 'zh-Hans', label: '简体中文' }
{ code: 'zh-Hans', label: '简体中文' },
];
return (
@ -35,4 +35,4 @@ export default function LanguageSwitcher() {
</select>
</div>
);
}
}

View File

@ -0,0 +1,45 @@
import React, { useState, useEffect } from 'react';
import { useLocation } from '@docusaurus/router';
export default function ReadingProgress() {
const [progress, setProgress] = useState(0);
const location = useLocation();
useEffect(() => {
const isDocPage = location.pathname !== '/';
if (!isDocPage) {
setProgress(0);
return;
}
const updateProgress = () => {
const scrollTop = window.scrollY;
const docHeight = document.documentElement.scrollHeight - window.innerHeight;
if (docHeight > 0) {
setProgress(Math.min((scrollTop / docHeight) * 100, 100));
}
};
window.addEventListener('scroll', updateProgress);
updateProgress();
return () => window.removeEventListener('scroll', updateProgress);
}, [location.pathname]);
if (progress === 0 && location.pathname === '/') return null;
return (
<div
style={{
position: 'fixed',
top: 0,
left: 0,
height: '3px',
width: `${progress}%`,
background: 'linear-gradient(90deg, #466aff, #00d4ff)',
zIndex: 9999,
transition: 'width 0.1s ease-out',
boxShadow: '0 0 8px rgba(70, 106, 255, 0.5)',
}}
/>
);
}

View File

@ -0,0 +1,13 @@
import React from 'react';
import styles from './mybutton.module.css'; // 假设你有一个CSS模块文件来定义样式
const MyButton = ({ onClick, children, className = '' }) => (
<button
onClick={onClick}
className={`${styles.myButton} ${className}`} // 使用CSS模块样式
>
{children}
</button>
);
export default MyButton;

View File

@ -26,9 +26,6 @@
.navbar__link{
color: #fff;
}
.table-of-contents__link--active{
color: #466aff;
}
html[data-theme=light] .menu{
background-image:linear-gradient(180deg,#f3f5f8 0%,#ffffff 100%);
box-shadow:8px 6px 18px rgba(171, 202, 255, 0.24) inset;
@ -41,9 +38,6 @@ html[data-theme=light] .menu{
padding: 0 var(--ifm-pre-padding);
}
html[data-theme='dark'] .docusaurus-highlight-code-line {
background-color: rgba(0, 0, 0, 0.3);
}
.navbar__search span[role='listbox']{
background-color: #33416b;
}
@ -148,4 +142,92 @@ html[data-theme='dark'] .docusaurus-highlight-code-line {
.widget{
display: none!important;
opacity: 0;
}
/* === 暗色模式全局优化 === */
html[data-theme='dark'] {
--ifm-background-color: #1a1b2e;
--ifm-background-surface-color: #232440;
--ifm-color-emphasis-300: #3a3b5c;
}
html[data-theme='dark'] .menu {
background-image: linear-gradient(180deg, #232440 0%, #1a1b2e 100%);
box-shadow: 8px 6px 18px rgba(0, 0, 0, 0.3) inset;
}
html[data-theme='dark'] :root {
--search-local-modal-background: #232440;
}
html[data-theme='dark'] .navbar__search span[role='listbox'] {
background-color: #2a2b4a;
}
html[data-theme='dark'] .navbar {
background-color: #12132a;
}
html[data-theme='dark'] .footer {
background: #12132a;
}
html[data-theme='dark'] .container .footer__copyright {
background-color: #0d0e20;
}
html[data-theme='dark'] .cardContainer {
background: var(--ifm-background-surface-color);
border-color: var(--ifm-color-emphasis-300);
}
html[data-theme='dark'] .docusaurus-highlight-code-line {
background-color: rgba(255, 255, 255, 0.1);
}
html[data-theme='dark'] table {
--ifm-table-background: var(--ifm-background-surface-color);
--ifm-table-border-color: var(--ifm-color-emphasis-300);
}
html[data-theme='dark'] .back-to-top-btn {
background: #466aff;
box-shadow: 0 2px 10px rgba(70, 106, 255, 0.4);
}
html[data-theme='dark'] .markdown img {
opacity: 0.9;
}
/* === 首页卡片动效优化 === */
.cardContainer {
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
border: 1px solid var(--ifm-color-emphasis-300);
border-radius: 8px;
text-decoration: none !important;
}
.cardContainer:hover {
transform: translateY(-4px);
box-shadow: 0 8px 25px rgba(70, 106, 255, 0.15);
border-color: var(--ifm-color-primary);
}
html[data-theme='dark'] .cardContainer:hover {
box-shadow: 0 8px 25px rgba(70, 106, 255, 0.25);
border-color: var(--ifm-color-primary);
}
.cardTitle {
color: var(--ifm-color-emphasis-900);
font-weight: 600;
}
.cardContainer p {
color: var(--ifm-color-emphasis-600);
font-size: 0.9rem;
}
/* === 目录导航优化 === */
.table-of-contents {
border-left: 2px solid var(--ifm-color-emphasis-300);
padding-left: 1rem;
}
.table-of-contents__link {
font-size: 0.85rem;
line-height: 1.8;
}
.table-of-contents__link:hover {
color: var(--ifm-color-primary);
}
.table-of-contents__link--active {
font-weight: 600;
border-left: 2px solid var(--ifm-color-primary);
margin-left: -1rem;
padding-left: calc(1rem - 2px);
}

View File

@ -0,0 +1,12 @@
import React from 'react';
import Layout from '@theme-original/DocItem/Layout';
import DocFeedback from '@site/src/components/DocFeedback';
export default function LayoutWrapper(props) {
return (
<>
<Layout {...props} />
<DocFeedback />
</>
);
}

16
src/theme/Layout/index.js Normal file
View File

@ -0,0 +1,16 @@
import React from 'react';
import Layout from '@theme-original/Layout';
import ReadingProgress from '@site/src/components/ReadingProgress';
import BackToTop from '@site/src/components/BackToTop';
import AIAssistant from '@site/src/components/AIAssistant';
export default function LayoutWrapper(props) {
return (
<>
<ReadingProgress />
<Layout {...props} />
<BackToTop />
<AIAssistant />
</>
);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.5 KiB

View File

@ -1,195 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="420px" height="842px" viewBox="0 0 420 842" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="background: #FFFFFF;">
<!-- Generator: Sketch 40.3 (33839) - http://www.bohemiancoding.com/sketch -->
<title>Artboard 4</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Artboard-4">
<g id="HtmlToSvg" transform="translate(-5.000000, -5.000000)">
<g id="Created-by-HiQPdf">
<g id="Group">
<g id="second-one" transform="translate(24.000000, 363.000000)">
<g id="Group-3">
<path d="M381,0 C382.657,0 384,1.343 384,3 L384,212 C384,213.657 382.657,215 381,215 L3,215 C1.343,215 0,213.657 0,212 L0,3 C0,1.343 1.343,0 3,0 L381,0 Z" id="Shape" fill="#F5F5F5"></path>
<g id="Group" transform="translate(12.000000, 121.000000)" fill="#FFFFFF">
<path d="M3,0 L357,0 C358.657,0 360,1.343 360,3 L360,34 C360,35.657 358.657,37 357,37 L3,37 C1.343,37 0,35.657 0,34 L0,3 C0,1.343 1.343,0 3,0" id="Shape"></path>
</g>
<g id="Group" transform="translate(20.000000, 47.000000)" fill="#395185">
<path d="M19.8409684,21.0000692 C20.4809758,21.0000692 20.9999872,20.4811399 20.9999872,19.8410504 L20.9999872,1.15901882 C20.9999872,0.51884734 20.4809758,0 19.8409684,0 L1.15901882,0 C0.518765309,0 0,0.51884734 0,1.15901882 L0,19.8410504 C0,20.4811399 0.518765309,21.0000692 1.15901882,21.0000692 L19.8409684,21.0000692" id="Shape"></path>
</g>
<g id="Group" transform="translate(28.449214, 50.117186)" fill="#FFFFFF">
<path d="M6.04036741,17.8828836 L6.04036741,9.75055656 L8.77011965,9.75055656 L9.17879909,6.58128114 L6.04036741,6.58128114 L6.04036741,4.5577355 C6.04036741,3.6401345 6.29523835,3.01481066 7.61110083,3.01481066 L9.28937715,3.01407238 L9.28937715,0.179484266 C8.9989867,0.14084757 8.00279981,0.054550748 6.84386302,0.054550748 C4.42410668,0.054550748 2.76756863,1.5315225 2.76756863,4.24396616 L2.76756863,6.58128114 L0.0308437312,6.58128114 L0.0308437312,9.75055656 L2.76756863,9.75055656 L2.76756863,17.8828836 L6.04036741,17.8828836" id="Shape"></path>
</g>
<g id="Group" transform="translate(20.000000, 88.000000)" fill="#55ACEE">
<path d="M1.0664056,0 L19.9335816,0 C20.5225656,0 20.9999872,0.477446193 20.9999872,1.0664056 L20.9999872,19.9335816 C20.9999872,20.5225656 20.5225656,20.9999872 19.9335816,20.9999872 L1.0664056,20.9999872 C0.477446193,20.9999872 0,20.5225656 0,19.9335816 L0,1.0664056 C0,0.477446193 0.477446193,0 1.0664056,0" id="Shape"></path>
</g>
<g id="Group" transform="translate(22.953123, 92.347654)" fill="#FFFFFF">
<path d="M15.1674868,1.45563544 C14.6094286,1.70252474 14.0096985,1.86939261 13.3801911,1.94444296 C14.0226594,1.56023342 14.5160771,0.951881842 14.7483894,0.227029549 C14.1471828,0.582725035 13.4812535,0.8410823 12.7724219,0.980330262 C12.204766,0.377007192 11.3960204,0 10.50106,0 C8.78250637,0 7.3891244,1.38987923 7.3891244,3.10420006 C7.3891244,3.3474964 7.41676892,3.58442711 7.46976107,3.81162072 C4.88355523,3.68216729 2.59066014,2.44630984 1.055881,0.568230122 C0.78802452,1.0267107 0.634568754,1.55995452 0.634568754,2.12884909 C0.634568754,3.20581211 1.18398092,4.15594669 2.01894369,4.71261041 C1.50881627,4.69653229 1.02902398,4.55691519 0.609442597,4.32435674 C0.609221113,4.33731767 0.609221113,4.35036063 0.609221113,4.36340359 C0.609221113,5.86744564 1.68185288,7.12211285 3.10538951,7.4072533 C2.84425139,7.47821029 2.56935664,7.5161087 2.28552048,7.5161087 C2.08501982,7.5161087 1.89006447,7.49674934 1.70010482,7.46049155 C2.09606122,8.69374861 3.24525271,9.59125197 4.60693782,9.61635351 C3.54195956,10.4488882 2.20019163,10.9451769 0.742308532,10.9451769 C0.491153607,10.9451769 0.243443992,10.9304933 0,10.9017824 C1.37710697,11.7825514 3.0128009,12.2964769 4.77009787,12.2964769 C10.4937592,12.2964769 13.6236597,7.56631179 13.6236597,3.46416117 C13.6236597,3.32956438 13.6207065,3.19568126 13.6147183,3.06257743 C14.2226515,2.62490817 14.7502762,2.07815381 15.1674868,1.45563544" id="Shape"></path>
</g>
<g id="Group" transform="translate(20.000000, 129.000000)" fill="#D53F71">
<path d="M20.1250023,0 C20.6082716,0 21.0000024,0.391751006 21.0000024,0.8750001 L21.0000024,20.1250023 C21.0000024,20.6082716 20.6082716,21.0000024 20.1250023,21.0000024 L0.8750001,21.0000024 C0.391751006,21.0000024 0,20.6082716 0,20.1250023 L0,0.8750001 C0,0.391751006 0.391751006,0 0.8750001,0 L20.1250023,0 Z" id="Shape"></path>
</g>
<g id="Group" transform="translate(22.692000, 131.692000)" fill="#FFFFFF">
<path d="M0,4.51006686 C0,2.01922427 2.01943965,0 4.51006686,0 L11.1052993,0 C13.5961554,0 15.6153864,2.01943965 15.6153864,4.51006686 L15.6153864,11.1052993 C15.6153864,13.5961554 13.5959535,15.6153864 11.1052993,15.6153864 L4.51006686,15.6153864 C2.01922427,15.6153864 0,13.5959535 0,11.1052993 L0,4.51006686 M1.4134617,4.64466207 C1.4134617,2.86011956 2.86024744,1.4134617 4.64466207,1.4134617 L10.9707513,1.4134617 C12.7552803,1.4134617 14.2019247,2.86024744 14.2019247,4.64466207 L14.2019247,10.9707513 C14.2019247,12.7552803 12.7551457,14.2019247 10.9707513,14.2019247 L4.64466207,14.2019247 C2.86011956,14.2019247 1.4134617,12.7551457 1.4134617,10.9707513 L1.4134617,4.64466207" id="Shape"></path>
</g>
<g id="Group" transform="translate(26.528539, 135.528539)" fill="#FFFFFF">
<path d="M3.9711543,7.9423086 C6.1643757,7.9423086 7.9423086,6.1643757 7.9423086,3.9711543 C7.9423086,1.77794636 6.1643757,0 3.9711543,0 C1.77794636,0 0,1.77794636 0,3.9711543 C0,6.1643757 1.77794636,7.9423086 3.9711543,7.9423086 M3.9711543,6.5288469 C5.383741,6.5288469 6.5288469,5.383741 6.5288469,3.9711543 C6.5288469,2.55858106 5.383741,1.4134617 3.9711543,1.4134617 C2.55858106,1.4134617 1.4134617,2.55858106 1.4134617,3.9711543 C1.4134617,5.383741 2.55858106,6.5288469 3.9711543,6.5288469" id="Shape"></path>
</g>
<g id="Group" transform="translate(33.663155, 134.384308)" fill="#FFFFFF">
<path d="M1.8846156,0.9423078 C1.8846156,1.46273094 1.46273094,1.8846156 0.9423078,1.8846156 C0.421884664,1.8846156 0,1.46273094 0,0.9423078 C0,0.421884664 0.421884664,0 0.9423078,0 C1.46273094,0 1.8846156,0.421884664 1.8846156,0.9423078" id="Shape"></path>
</g>
<g id="Group" transform="translate(20.000000, 170.000000)" fill="#231F20">
<path d="M1.0664056,0 L19.9335816,0 C20.5225656,0 20.9999872,0.477446193 20.9999872,1.0664056 L20.9999872,19.9335816 C20.9999872,20.5225656 20.5225656,20.9999872 19.9335816,20.9999872 L1.0664056,20.9999872 C0.477446193,20.9999872 0,20.5225656 0,19.9335816 L0,1.0664056 C0,0.477446193 0.477446193,0 1.0664056,0" id="Shape"></path>
</g>
<g id="Group" transform="translate(32.714401, 174.758000)" fill="#7F7F7F">
<path d="M0.445921603,0.0612928103 C0.240105322,-0.041772748 0.0716952688,0.062456669 0.0716952688,0.292898962 L0.0716952688,9.46008408 L3.82823204,11.3409775 C4.24166929,11.5478601 4.57676674,11.402993 4.57676674,11.0174464 L4.57676674,2.18971624 C4.57676674,2.15286783 4.55584879,2.11898074 4.52295427,2.10242684 L0.445921603,0.0612928103" id="Shape"></path>
</g>
<g id="Group" transform="translate(28.202685, 174.758000)" fill="#ABACAD">
<path d="M0.0784382334,7.20449678 L4.36414187,0.230697984 C4.48472774,0.0343948618 4.75182132,-0.041772748 4.9576376,0.0612928103 L9.03926402,2.10475653 C9.0657601,2.1180784 9.07519369,2.15118619 9.05968979,2.17640258 L4.58341127,9.46008408 L0.0784382334,7.20449678" id="Shape"></path>
</g>
<g id="Group" transform="translate(24.019094, 174.840031)" fill="#D8D8D8">
<path d="M0.380692034,0.104182085 C0.0375637271,-0.0676804158 -0.0786629989,0.0594381669 0.122409418,0.386609764 L4.26202943,7.12246558 L8.76700247,9.37805288 L4.27171732,2.06319952 C4.26538451,2.05285539 4.25621342,2.04457844 4.2453689,2.03914798 L0.380692034,0.104182085" id="Shape"></path>
</g>
<g id="Group" transform="translate(23.773000, 174.758000)" fill="#FFFFFF">
<path d="M4.50812303,11.0174464 C4.50812303,11.4015165 4.22737125,11.5751765 3.88436599,11.4034032 L0.377285278,9.64744334 C0.171433724,9.54433012 0.00303408799,9.27149435 0.00303408799,9.04106871 L0.00303408799,0.432562002 C0.00303408799,0.125304299 0.22761033,-0.0137110229 0.502035046,0.123753089 L4.4838418,2.1172991 C4.49869765,2.12480495 4.50812303,2.13993151 4.50812303,2.1564854 L4.50812303,11.0174464" id="Shape"></path>
</g>
<g id="social-networks" transform="translate(18.000000, 18.000000)">
<rect id="Rectangle" fill-opacity="0.699999988" fill="#B3B3B3" x="62" y="36" width="76" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill-opacity="0.699999988" fill="#B3B3B3" x="62" y="77" width="76" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill-opacity="0.699999988" fill="#B3B3B3" x="62" y="118" width="76" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill-opacity="0.699999988" fill="#B3B3B3" x="62" y="159" width="76" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill-opacity="0.699999988" fill="#B3B3B3" x="305" y="77" width="41" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill-opacity="0.699999988" fill="#B3B3B3" x="305" y="36" width="41" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill="#52CA6F" x="305" y="118" width="41" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill-opacity="0.699999988" fill="#B3B3B3" x="305" y="159" width="41" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill-opacity="0.799999952" fill="#C9C9C9" x="33" y="77" width="20.0703125" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill-opacity="0.799999952" fill="#C9C9C9" x="33" y="36" width="20.0703125" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill-opacity="0.799999952" fill="#C9C9C9" x="33" y="118" width="20.0703125" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill-opacity="0.799999952" fill="#C9C9C9" x="33" y="159" width="20.0703125" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill="#B3B3B3" x="0" y="0" width="138" height="9" rx="4.5"></rect>
</g>
</g>
</g>
<g id="Group-2">
<g id="Group" stroke="#F5F5F5" fill="#FFFFFF">
<polygon id="Shape" points="435 0 435 73 0 73 0 0"></polygon>
</g>
<g id="Group" transform="translate(29.000000, 32.000000)" fill="#CCCCCC">
<path d="M12.5,11 L11.71,11 L11.43,10.73 C12.41,9.59 13,8.11 13,6.5 C13,2.91 10.09,0 6.5,0 C2.91,0 0,2.91 0,6.5 C0,10.09 2.91,13 6.5,13 C8.11,13 9.59,12.41 10.73,11.43 L11,11.71 L11,12.5 L16,17.49 L17.49,16 L12.5,11 M6.5,11 C4.01,11 2,8.99 2,6.5 C2,4.01 4.01,2 6.5,2 C8.99,2 11,4.01 11,6.5 C11,8.99 8.99,11 6.5,11" id="Shape"></path>
</g>
</g>
<g id="topone" transform="translate(24.000000, 96.000000)">
<g id="top-one" fill="#F5F5F5">
<path d="M381,0 C382.657,0 384,1.343 384,3 L384,253 C384,254.657 382.657,256 381,256 L3,256 C1.343,256 0,254.657 0,253 L0,3 C0,1.343 1.343,0 3,0 L381,0 Z" id="Shape"></path>
</g>
<g id="Group" transform="translate(19.000000, 88.000000)" fill-opacity="0.0392157" fill="#000000">
<polyline id="Shape" points="0 0 346 0 346 23 0 23 0 0"></polyline>
</g>
<g id="Group" transform="translate(12.000000, 80.000000)" fill="#FFFFFF">
<path d="M3,0 L357,0 C358.657,0 360,1.343 360,3 L360,34 C360,35.657 358.657,37 357,37 L3,37 C1.343,37 0,35.657 0,34 L0,3 C0,1.343 1.343,0 3,0" id="Shape"></path>
</g>
<g id="Group" transform="translate(19.000000, 129.000000)" fill-opacity="0.0392157" fill="#000000">
<polyline id="Shape" points="0 0 346 0 346 23 0 23 0 0"></polyline>
</g>
<g id="Group" transform="translate(12.000000, 121.000000)" fill="#FFFFFF">
<path d="M3,0 L357,0 C358.657,0 360,1.343 360,3 L360,34 C360,35.657 358.657,37 357,37 L3,37 C1.343,37 0,35.657 0,34 L0,3 C0,1.343 1.343,0 3,0" id="Shape"></path>
</g>
<g id="Group" transform="translate(19.000000, 170.000000)" fill-opacity="0.0392157" fill="#000000">
<polyline id="Shape" points="0 0 346 0 346 23 0 23 0 0"></polyline>
</g>
<g id="Group" transform="translate(12.000000, 162.000000)" fill="#FFFFFF">
<path d="M3,0 L357,0 C358.657,0 360,1.343 360,3 L360,34 C360,35.657 358.657,37 357,37 L3,37 C1.343,37 0,35.657 0,34 L0,3 C0,1.343 1.343,0 3,0" id="Shape"></path>
</g>
<g id="Group" transform="translate(19.000000, 211.000000)" fill-opacity="0.0392157" fill="#000000">
<polyline id="Shape" points="0 0 346 0 346 23 0 23 0 0"></polyline>
</g>
<g id="Group" transform="translate(12.000000, 203.000000)" fill="#FFFFFF">
<path d="M3,0 L357,0 C358.657,0 360,1.343 360,3 L360,34 C360,35.657 358.657,37 357,37 L3,37 C1.343,37 0,35.657 0,34 L0,3 C0,1.343 1.343,0 3,0" id="Shape"></path>
</g>
<g id="Group" transform="translate(136.000000, 88.000000)"></g>
<rect id="Rectangle" fill-opacity="0.900000036" fill="#3FC55F" x="323" y="95" width="41" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill-opacity="0.900000036" fill="#3FC55F" x="323" y="218" width="41" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill="#C6C6C6" x="323" y="54" width="41" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill-opacity="0.900000036" fill="#3FC55F" x="323" y="136" width="41" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill-opacity="0.900000036" fill="#3FC55F" x="323" y="177" width="41" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill-opacity="0.5" fill="#B3B3B3" x="53" y="56" width="76" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill-opacity="0.5" fill="#B3B3B3" x="53" y="97" width="76" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill-opacity="0.5" fill="#B3B3B3" x="53" y="138" width="76" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill-opacity="0.5" fill="#B3B3B3" x="53" y="179" width="76" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill-opacity="0.5" fill="#B3B3B3" x="53" y="219" width="76" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill-opacity="0.300000012" fill="#B3B3B3" x="138" y="97" width="41" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill-opacity="0.300000012" fill="#B3B3B3" x="138" y="56" width="41" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill-opacity="0.300000012" fill="#B3B3B3" x="138" y="138" width="41" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill-opacity="0.300000012" fill="#B3B3B3" x="138" y="179" width="41" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill-opacity="0.300000012" fill="#B3B3B3" x="138" y="219" width="41" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill="#C9C9C9" x="25" y="97" width="20.0703125" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill="#C9C9C9" x="25" y="56" width="20.0703125" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill="#C9C9C9" x="25" y="138" width="20.0703125" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill="#C9C9C9" x="25" y="179" width="20.0703125" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill="#C9C9C9" x="25" y="219" width="20.0703125" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill="#B3B3B3" x="18" y="15" width="110" height="9" rx="4.5"></rect>
</g>
<g id="bottom-one" transform="translate(24.000000, 594.000000)">
<g id="top-one" fill="#F5F5F5">
<path d="M381,0 C382.657,0 384,1.343 384,3 L384,253 C384,254.657 382.657,256 381,256 L3,256 C1.343,256 0,254.657 0,253 L0,3 C0,1.343 1.343,0 3,0 L381,0 Z" id="Shape"></path>
</g>
<g id="Group" transform="translate(19.000000, 88.000000)" fill-opacity="0.0392157" fill="#000000">
<polyline id="Shape" points="0 0 346 0 346 23 0 23 0 0"></polyline>
</g>
<g id="Group" transform="translate(12.000000, 80.000000)" fill="#FFFFFF">
<path d="M3,0 L357,0 C358.657,0 360,1.343 360,3 L360,34 C360,35.657 358.657,37 357,37 L3,37 C1.343,37 0,35.657 0,34 L0,3 C0,1.343 1.343,0 3,0" id="Shape"></path>
</g>
<g id="Group" transform="translate(12.000000, 40.000000)" fill="#FFFFFF">
<path d="M3,0 L357,0 C358.657,0 360,1.343 360,3 L360,34 C360,35.657 358.657,37 357,37 L3,37 C1.343,37 0,35.657 0,34 L0,3 C0,1.343 1.343,0 3,0" id="Shape"></path>
</g>
<g id="Group" transform="translate(19.000000, 129.000000)" fill-opacity="0.0392157" fill="#000000">
<polyline id="Shape" points="0 0 346 0 346 23 0 23 0 0"></polyline>
</g>
<g id="Group" transform="translate(12.000000, 121.000000)" fill="#FFFFFF">
<path d="M3,0 L357,0 C358.657,0 360,1.343 360,3 L360,34 C360,35.657 358.657,37 357,37 L3,37 C1.343,37 0,35.657 0,34 L0,3 C0,1.343 1.343,0 3,0" id="Shape"></path>
</g>
<g id="Group" transform="translate(19.000000, 170.000000)" fill-opacity="0.0392157" fill="#000000">
<polyline id="Shape" points="0 0 346 0 346 23 0 23 0 0"></polyline>
</g>
<g id="Group" transform="translate(12.000000, 162.000000)" fill="#FFFFFF">
<path d="M3,0 L357,0 C358.657,0 360,1.343 360,3 L360,34 C360,35.657 358.657,37 357,37 L3,37 C1.343,37 0,35.657 0,34 L0,3 C0,1.343 1.343,0 3,0" id="Shape"></path>
</g>
<g id="Group" transform="translate(19.000000, 211.000000)" fill-opacity="0.0392157" fill="#000000">
<polyline id="Shape" points="0 0 346 0 346 23 0 23 0 0"></polyline>
</g>
<g id="Group" transform="translate(12.000000, 203.000000)" fill="#FFFFFF">
<path d="M3,0 L357,0 C358.657,0 360,1.343 360,3 L360,34 C360,35.657 358.657,37 357,37 L3,37 C1.343,37 0,35.657 0,34 L0,3 C0,1.343 1.343,0 3,0" id="Shape"></path>
</g>
<g id="Group" transform="translate(136.000000, 88.000000)"></g>
<rect id="Rectangle" fill-opacity="0.900000036" fill="#3FC55F" x="323" y="95" width="41" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill-opacity="0.900000036" fill="#3FC55F" x="323" y="218" width="41" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill="#52CA6F" x="323" y="54" width="41" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill-opacity="0.900000036" fill="#3FC55F" x="323" y="136" width="41" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill-opacity="0.900000036" fill="#3FC55F" x="323" y="177" width="41" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill-opacity="0.5" fill="#B3B3B3" x="53" y="56" width="76" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill-opacity="0.5" fill="#B3B3B3" x="53" y="97" width="76" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill-opacity="0.5" fill="#B3B3B3" x="53" y="138" width="76" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill-opacity="0.5" fill="#B3B3B3" x="53" y="179" width="76" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill-opacity="0.5" fill="#B3B3B3" x="53" y="219" width="76" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill-opacity="0.300000012" fill="#B3B3B3" x="138" y="97" width="41" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill-opacity="0.300000012" fill="#B3B3B3" x="138" y="56" width="41" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill-opacity="0.300000012" fill="#B3B3B3" x="138" y="138" width="41" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill-opacity="0.300000012" fill="#B3B3B3" x="138" y="179" width="41" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill-opacity="0.300000012" fill="#B3B3B3" x="138" y="219" width="41" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill="#C9C9C9" x="25" y="97" width="20.0703125" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill="#C9C9C9" x="25" y="56" width="20.0703125" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill="#C9C9C9" x="25" y="138" width="20.0703125" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill="#C9C9C9" x="25" y="179" width="20.0703125" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill="#C9C9C9" x="25" y="219" width="20.0703125" height="9" rx="4.5"></rect>
<rect id="Rectangle" fill="#B3B3B3" x="18" y="15" width="110" height="9" rx="4.5"></rect>
</g>
</g>
</g>
</g>
<g id="default-tlds" transform="translate(59.000000, 29.000000)" fill="#B3B3B3" fill-opacity="0.699999988">
<rect id="Rectangle" x="0" y="0" width="123" height="13" rx="6.5"></rect>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 24 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 268 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 306 KiB

Some files were not shown because too many files have changed in this diff Show More