Compare commits
65 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
7f7001e10d | |
|
|
c7173aa2f7 | |
|
|
6a1f97c8a4 | |
|
|
a087222a45 | |
|
|
f33df5e56b | |
|
|
d4ee113de4 | |
|
|
8a6b8d13ea | |
|
|
6aa2466fb1 | |
|
|
2b1fa0a531 | |
|
|
42015bf56d | |
|
|
7988de5fe0 | |
|
|
f9436dcda3 | |
|
|
5474210fe4 | |
|
|
2a1dd93127 | |
|
|
78b4908075 | |
|
|
f446e83fd3 | |
|
|
5d98b2ef7a | |
|
|
e415b20bf3 | |
|
|
8af4f9604a | |
|
|
e4531b35d7 | |
|
|
8d29e574e6 | |
|
|
ad49dc1818 | |
|
|
68b20ab6be | |
|
|
8adcfce2c8 | |
|
|
a31672a812 | |
|
|
ecafcd557d | |
|
|
1899276587 | |
|
|
898206287a | |
|
|
c040eecb75 | |
|
|
d74dc50617 | |
|
|
6a66a84c40 | |
|
|
4800fdd2f0 | |
|
|
e505f51d01 | |
|
|
8bc8dc639f | |
|
|
57cb1b8f39 | |
|
|
9a938c7d03 | |
|
|
5fbd50a4c5 | |
|
|
cfc94177f8 | |
|
|
6654e48286 | |
|
|
34dc1b8015 | |
|
|
f53cdf47e8 | |
|
|
c742cf592d | |
|
|
e5b018a6d5 | |
|
|
c0e2f21eb7 | |
|
|
0d5853a79a | |
|
|
9ddf5dd6e2 | |
|
|
79feddd606 | |
|
|
ded69412eb | |
|
|
f4f714b927 | |
|
|
e85a95cc01 | |
|
|
19be2a3d5b | |
|
|
2405b3c497 | |
|
|
792740c3af | |
|
|
1b013c546f | |
|
|
1ea850b884 | |
|
|
602bf0ddb7 | |
|
|
211f20eef4 | |
|
|
a074a9c25c | |
|
|
a094f464ec | |
|
|
3422b32669 | |
|
|
b74b92f66e | |
|
|
96231da6fe | |
|
|
8b39d43460 | |
|
|
75ce405ca7 | |
|
|
4f085a23e2 |
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -11,6 +11,9 @@ deploy.sh
|
|||
.docusaurus
|
||||
.cache-loader
|
||||
|
||||
# Sensitive files
|
||||
gitee私人令牌.txt
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
|
|
|
|||
13
Dockerfile
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -84,7 +84,8 @@
|
|||

|
||||
|
||||
```
|
||||
|
||||
初始化项目
|
||||
node v20.x npm install --force
|
||||
## 前端build成中文 (i18n中可编辑对应中文内容)
|
||||
```
|
||||
npm run build -- --locale zh-CN
|
||||
|
|
|
|||
|
|
@ -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 后台设置用量上限。
|
||||
|
|
@ -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 内置模块 + 全局 fetch(Node 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})`);
|
||||
});
|
||||
|
|
@ -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安装到所有仓库(用户拥有的所有仓库)中,也可以选择指定的仓库进行安装。
|
||||
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
---
|
||||
sidebar_label: '代码流水线'
|
||||
sidebar_position: 5
|
||||
sidebar_label: '代码流水线'
|
||||
sidebar_position: 5
|
||||
---
|
||||
# 代码流水线
|
||||
可以通过代码的形式(YAML格式)来配置流水线,首先选择代码流水线和对应的分支:
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
编辑流水线代码,其流水线名称描述、触发器、全局参数、执行串行/并发和流水线编排等概念同图形流水线,具体描述如下:
|
||||
|
||||

|
||||
|
|
@ -1,12 +1,12 @@
|
|||
---
|
||||
sidebar_label: '参数配置'
|
||||
sidebar_position: 2
|
||||
sidebar_label: '参数配置'
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
|
||||
# 参数配置
|
||||
|
||||
|
||||
可在流水线中使用,实现动态参数配置;有字符串、数字、布尔三种类型。
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
sidebar_label: '图形流水线'
|
||||
sidebar_position: 4
|
||||
sidebar_label: '图形流水线'
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
|
||||
# 图形流水线
|
||||
## 基本信息
|
||||
可以编辑流水线的名称、描述
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
---
|
||||
sidebar_label: '密钥设置'
|
||||
sidebar_position: 3
|
||||
sidebar_label: '密钥设置'
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
|
||||
# 密钥设置
|
||||
|
||||
|
||||
将账号密码、密钥、证书等存放到密钥管理里,配置后可直接在流水线中使用,避免因直接填写引起的泄露风险。
|
||||
|
||||

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

|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
---
|
||||
sidebar_label: '执行记录查询'
|
||||
sidebar_position: 6
|
||||
sidebar_label: '执行记录查询'
|
||||
sidebar_position: 6
|
||||
---
|
||||
|
||||
|
||||
# 执行记录查询
|
||||
|
||||
|
||||
用户可以查看流水线的运行状态
|
||||
|
||||
|
||||

|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
sidebar_label: '建站工具'
|
||||
sidebar_position: 2
|
||||
sidebar_label: '建站工具'
|
||||
sidebar_position: 2
|
||||
---
|
||||
# 个人建站工具
|
||||
|
||||
|
|
@ -18,9 +18,9 @@ sidebar_position: 2
|
|||
修改完毕后点击提交
|
||||

|
||||
提交完成后,开始部署
|
||||

|
||||

|
||||
静态页面预览
|
||||

|
||||

|
||||
|
||||
### jekyll工具
|
||||
|
||||
|
|
@ -29,17 +29,17 @@ sidebar_position: 2
|
|||
内容修改两个值。
|
||||
例如: 现在有一个网站地址为 http://KingChan.gitlink.net
|
||||
那么结果如下:
|
||||
baseurl: "/"
|
||||
baseurl: "/"
|
||||
url: "http://KingChan.gitlink.net"
|
||||
|
||||

|
||||

|
||||
|
||||
修改完成后点击 提交变更
|
||||

|
||||
提交完成后,前往建站服务开始部署
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
### 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
|
||||
|
||||

|
||||

|
||||
|
||||
修改完毕后提交变更
|
||||

|
||||
提交完成后,前往建站服务开始部署
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
### file 工具
|
||||
文档格式工具,使用该工具会部署所选中分支下的所有文件。
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
sidebar_label: '建站流程'
|
||||
sidebar_label: '建站流程'
|
||||
sidebar_position: 1
|
||||
---
|
||||
# 个人建站流程
|
||||
|
|
@ -8,7 +8,7 @@ sidebar_position: 1
|
|||
|
||||

|
||||
|
||||
点击左测面板中 个人建站 - 我的站点
|
||||
点击左侧面板中 个人建站 - 我的站点
|
||||
|
||||

|
||||
|
||||
|
|
@ -22,7 +22,7 @@ sidebar_position: 1
|
|||
|
||||

|
||||
|
||||
填写完毕后,点击页面下方蓝色按钮: 创建站点
|
||||
填写完毕后,点击页面下方蓝色按钮: 创建站点
|
||||
|
||||

|
||||
|
||||
|
|
|
|||
|
|
@ -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 按键即可进入
|
||||
<br/>
|
||||
|
||||
|
||||
### **3. 分支**
|
||||
支持分支切换:点击左下角分支名即可切换分支。
|
||||
<br/>
|
||||
|
|
@ -19,19 +23,19 @@ sidebar_position: 9
|
|||
### **4. 搜索**
|
||||
当前支持 Gitlink 仓库内代码搜索(支持单词匹配和文件过滤)和文件搜索。
|
||||
<br/>
|
||||
|
||||
|
||||
### **5. 行高亮**
|
||||
支持单行或多行高亮,点击行号即可高亮行,按住 Shift 可选中连续多行。
|
||||
<br/>
|
||||
|
||||
|
||||
### **6. Blame**
|
||||
支持查看单行 blame 信息,hover 后可展示详细信息。
|
||||
<br/>
|
||||
|
||||
|
||||
### **7. Graph**
|
||||
支持 graph 视图,可查看分支 commits 历史以及每个 commit 详细文件变更,并支持文件 diff 查看。底部状态栏左下角点击 Git Graph 或在命令面板输入 View Git Graph,即可打开 Git Graph 视图。
|
||||
<br/>
|
||||
|
||||
|
||||
### **8. WebSCM**
|
||||
可以在极速版新建分支,修改代码后在 SCM 面板看到变更文件列表,写完 commit message 后提交到 Gitlink 上。如果想快速修改一些文件可以不用在本地修改,直接通过极速版修改代码一次性提交。
|
||||
|
||||
|
|
|
|||
|
|
@ -1,29 +1,29 @@
|
|||
---
|
||||
sidebar_label: 'Webhook'
|
||||
sidebar_position: 8
|
||||
sidebar_position: 8
|
||||
---
|
||||
### **1. Webhook模块入口**
|
||||
在项目主页,点击“仓库设置”按键,再点击“网络钩子”按键,即可进入仓库的Webhook模块。
|
||||
<br/>
|
||||
|
||||
### **2. 添加Webhook**
|
||||
单击“添加Webhook”按键,可进入配置Webhook页面:
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
### **3. 编辑Webhook**
|
||||
添加Webhook后,如下图所示,可以通过点击右侧”编辑“按键来对每个Webhook进行编辑操作。
|
||||
|
||||
<br/>
|
||||
|
||||
### **4. 删除Webhook**
|
||||
添加Webhook后,如下图所示,可以通过点击右侧”删除“按键来对每个Webhook进行删除操作。
|
||||
|
||||
<br/>
|
||||
### **1. Webhook模块入口**
|
||||
在项目主页,点击”仓库设置”按键,再点击”网络钩子”按键,即可进入仓库的Webhook模块。
|
||||
<br/>
|
||||
|
||||
### **5. Webhook支持的事件类型**
|
||||
在GitLink中,Webhook支持的事件类型如下:
|
||||
### **2. 添加Webhook**
|
||||
单击”添加Webhook”按键,可进入配置Webhook页面:
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
### **3. 编辑Webhook**
|
||||
添加Webhook后,如下图所示,可以通过点击右侧”编辑”按键来对每个Webhook进行编辑操作。
|
||||
|
||||
<br/>
|
||||
|
||||
### **4. 删除Webhook**
|
||||
添加Webhook后,如下图所示,可以通过点击右侧”删除”按键来对每个Webhook进行删除操作。
|
||||
|
||||
<br/>
|
||||
|
||||
### **5. Webhook支持的事件类型**
|
||||
在GitLink中,Webhook支持的事件类型如下:
|
||||
- 推送:git推送到存储库
|
||||
- 代码库:创建或删除代码库
|
||||
- 创建:创建分支或标签
|
||||
|
|
@ -35,4 +35,3 @@ sidebar_position: 8
|
|||
- 合并请求标签:合并请求的标签被更新或清除
|
||||
- 合并请求审查:合并请求被批准、拒绝或提出审查意见,审查人员的修改,审查线程已解决或未解决
|
||||
- 合并请求被同步:合并请求被同步
|
||||
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
---
|
||||
sidebar_label: '仓库创建'
|
||||
sidebar_position: 1
|
||||
sidebar_label: '仓库创建'
|
||||
sidebar_position: 1
|
||||
---
|
||||
### **1. 仓库创建入口**
|
||||
用户可以通过平台主页右上角的按键以及个人主页参与项目模块的“新建”按键来创建仓库。
|
||||
### **1. 仓库创建入口**
|
||||
用户可以通过平台主页右上角的按键以及个人主页参与项目模块的”新建”按键来创建仓库。
|
||||
<br/>
|
||||
|
||||
### **2. 填写项目基本信息**
|
||||
进入新建项目页面后,如下图所示,填写拥有者、项目名称、项目标识等信息,单击”创建项目“按键即可完成创建。
|
||||
|
||||
### **2. 填写项目基本信息**
|
||||
进入新建项目页面后,如下图所示,填写拥有者、项目名称、项目标识等信息,单击”创建项目”按键即可完成创建。
|
||||
<br/>
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
---
|
||||
sidebar_label: '仓库设置'
|
||||
sidebar_position: 2
|
||||
sidebar_label: '仓库设置'
|
||||
sidebar_position: 2
|
||||
---
|
||||
### **1. 仓库设置入口**
|
||||
在仓库主页,点击“仓库设置”按键即可进入仓库设置模块。仓库设置中可以修改项目的基本信息、进行成员管理、配置webhook、设置分支以及安装bot。
|
||||
在仓库主页,点击“仓库设置”按键即可进入仓库设置模块。仓库设置中可以修改项目的基本信息、进行成员管理、配置 Webhook、设置分支以及安装 Bot。
|
||||
<br/>
|
||||
|
||||
### **2. 基本设置**
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
---
|
||||
sidebar_label: '代码提交'
|
||||
sidebar_position: 3
|
||||
sidebar_label: '代码提交'
|
||||
sidebar_position: 3
|
||||
---
|
||||
### **1. 代码提交入口**
|
||||
用户可以通过项目代码库中间的文件下的“上传文件”按键来上传文件。
|
||||
### **1. 代码提交入口**
|
||||
用户可以通过项目代码库中间的文件下的”上传文件”按键来上传文件。
|
||||
<br/>
|
||||
|
||||
### **2. 上传文件**
|
||||
进入上传文件页面后,如下图所示,拖动文件,选择提交变更的分支的单击”创建项目“按键即可完成创建。
|
||||
|
||||
### **2. 上传文件**
|
||||
进入上传文件页面后,如下图所示,拖动文件,选择提交变更的分支,单击”提交变更”按键即可完成上传。
|
||||
<br/>
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
---
|
||||
sidebar_label: '分支管理'
|
||||
sidebar_position: 5
|
||||
sidebar_label: '分支管理'
|
||||
sidebar_position: 5
|
||||
---
|
||||
### **1. 查看分支**
|
||||
### **1. 查看分支**
|
||||
在代码库栏下,如下图所示,用户可以点击代码库分支从而查看当前项目的所有分支,并且选择对其进行操作。
|
||||
<br/>
|
||||
|
||||
### **2. 合并分支**
|
||||
在“和并请求(PR)”下点击“新建合并请求”后,
|
||||
|
||||
### **2. 合并分支**
|
||||
在”合并请求(PR)”下点击”新建合并请求”后,
|
||||
<br/>
|
||||
如下图所示,用户可以选择源分支与目标分支以及填写分支信息后提交分支合并请求。
|
||||
<br/>
|
||||
|
|
@ -1,23 +1,23 @@
|
|||
---
|
||||
sidebar_label: '成员管理'
|
||||
sidebar_position: 7
|
||||
sidebar_label: '成员管理'
|
||||
sidebar_position: 7
|
||||
---
|
||||
### **1. 成员管理入口**
|
||||
在仓库主页,点击“仓库设置”按键,再点击”成员管理“按键,即可进入成员管理模块,如下图所示。
|
||||
<br/>
|
||||
|
||||
|
||||
### **2. 筛选、搜索项目成员**
|
||||
进入成员管理模块后,可以通过”角色筛选“按键来筛选项目成员类型,通过”搜索“按键可以检索具体的项目成员,如下图所示。
|
||||
<br/>
|
||||
|
||||
### **3. 筛选、搜索项目成员**
|
||||
进入成员管理模块后,可以通过”添加成员“按键来添加项目成员,左侧搜索框检索到具体用户并选中后,单击”添加成员“按键即可成功添加项目成员。
|
||||
|
||||
### **3. 添加项目成员**
|
||||
进入成员管理模块后,可以通过”添加成员”按键来添加项目成员,左侧搜索框检索到具体用户并选中后,单击”添加成员“按键即可成功添加项目成员。
|
||||
<br/>
|
||||
|
||||
|
||||
### **4. 项目成员权限管理**
|
||||
进入成员管理模块后,单击项目成员右侧的角色栏,可以选择赋予该名项目成员的权限等级,如下图所示。
|
||||
<br/>
|
||||
|
||||
|
||||
### **5. 删除项目成员**
|
||||
进入成员管理模块后,单击项目成员右侧的”删除“按键,可以删除改名项目成员,如下图所示。
|
||||
进入成员管理模块后,单击项目成员右侧的”删除“按键,可以删除该名项目成员,如下图所示。
|
||||
<br/>
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
---
|
||||
sidebar_label: '文件管理'
|
||||
sidebar_position: 4
|
||||
sidebar_label: '文件管理'
|
||||
sidebar_position: 4
|
||||
---
|
||||
### **1. 文件管理位置**
|
||||
在代码库界面,可直接对代码库中该分支下的文件进行操作,如下图所示。
|
||||
<br/>
|
||||
|
||||
|
||||
### **2. 进入目录**
|
||||
点击进入相关目录,找到要编辑的文件,如下图所示。
|
||||
<br/>
|
||||
|
||||
|
||||
### **3. 进入文件编辑页面**
|
||||
在线编辑文件,单击文件,即可进入编辑文件页面,如下图所示。
|
||||
<br/>
|
||||
|
||||
|
||||
### **4. 编辑文件**
|
||||
编辑界面右侧有三个按钮,分别是“下载”、“编辑”和“删除”,点击下载即可将文件下载到本地,编辑则可在线编辑文档,删除则将文件从代码库中删除,按钮位置如下图所示。
|
||||
<br/>
|
||||
|
|
@ -1,29 +1,31 @@
|
|||
---
|
||||
sidebar_label: '标签和发行版管理'
|
||||
sidebar_position: 6
|
||||
sidebar_label: '标签和发行版管理'
|
||||
sidebar_position: 6
|
||||
---
|
||||
### **1. 标签和发行版管理入口**
|
||||
在仓库主页,点击“代码库”按键,再点击”标签“按键,即可进入标签和发行版管理模块,如下图所示。
|
||||
<br/>
|
||||
|
||||
### **2. 删除标签**
|
||||
|
||||
### **2. 创建标签**
|
||||
在标签和发行版管理模块中,点击"新建标签"按键,在弹出的页面中填写标签名称(通常使用语义化版本号,如 v1.0.0),选择标签所基于的分支或提交记录,填写标签说明后单击"确认"按键即可创建新标签。
|
||||
|
||||
### **3. 删除标签**
|
||||
进入标签和发行版管理模块后,点击标签右侧“删除”按键,即可实现对标签的删除操作,如下图所示。
|
||||
<br/>
|
||||
|
||||
### **3. 查看发行版**
|
||||
### **4. 查看发行版**
|
||||
进入标签和发行版管理模块后,点击”发行版”按键即可查看项目的发行版,如下图所示。
|
||||
<br/>
|
||||
|
||||
### **4. 创建发行版**
|
||||
|
||||
### **5. 创建发行版**
|
||||
进入标签和发行版管理模块后,点击标签右侧”创建发行版”按键,即可快速创建绑定该标签的发行版,如下图所示。
|
||||
<br/>
|
||||
|
||||
### **5. 修改发行版**
|
||||
进入标签和发行版管理模块后,点击发行版右侧的修改按键,即可进入发行版的修改页面,如下图所示。完成发行版的修改后,单击”保存发行版“按键即可保存修改内容。
|
||||
|
||||
### **6. 修改发行版**
|
||||
进入标签和发行版管理模块后,点击发行版右侧的修改按键,即可进入发行版的修改页面,如下图所示。完成发行版的修改后,单击”保存发行版”按键即可保存修改内容。
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
### **6. 删除发行版**
|
||||
|
||||
### **7. 删除发行版**
|
||||
进入标签和发行版管理模块后,点击发行版右侧的删除按键,即可进入发行版的删除页面,如下图所示。
|
||||
<br/>
|
||||
|
||||
|
|
@ -1,13 +1,22 @@
|
|||
---
|
||||
sidebar_label: '代码评审'
|
||||
sidebar_position: 3
|
||||
sidebar_label: '代码评审'
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# 代码评审
|
||||
### **1. 进入代码评审**
|
||||
如下图所示,点击“代码评审”按钮可以进入代码评审
|
||||
<br/>
|
||||
|
||||
### **2. 代码评审界面**
|
||||
如下图所示,用户可以在该界面下查看该PR操作要合并的新的代码,并对其进行评审。
|
||||
<br/>
|
||||
### **1. 进入代码评审**
|
||||
如下图所示,点击”代码评审”按钮可以进入代码评审
|
||||

|
||||
|
||||
### **2. 代码评审界面**
|
||||
如下图所示,用户可以在该界面下查看该 PR 操作要合并的新的代码,并对其进行评审。
|
||||

|
||||
|
||||
### **3. 添加评审意见**
|
||||
在代码评审界面中,用户可以点击代码行左侧的”+”按钮,对特定代码行添加评审意见。填写评论内容后单击”提交”按键即可发表评论。
|
||||
|
||||
### **4. 评审状态管理**
|
||||
评审意见可以分为”已解决”和”未解决”两种状态。已解决的评审意见表示该问题已处理,未解决的评审意见表示该问题仍需关注。项目维护者可以根据评审结果决定是否合并该请求。
|
||||
|
||||
### **5. 完成评审**
|
||||
当所有评审意见都已处理完毕后,评审人员可以在评审界面中给出最终评审结果:批准(Approve)或请求修改(Request Changes)。
|
||||
|
|
|
|||
|
|
@ -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. 最后信息填写完毕后,点击底部的“**创建**”按钮即可提交您的第一个合并请求了🎉🎉🎉!
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
sidebar_label: '合并模式简介'
|
||||
sidebar_position: 4
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
# 合并模式简介
|
||||
|
|
|
|||
|
|
@ -1,6 +1,19 @@
|
|||
---
|
||||
sidebar_label: '合并请求关联疑修'
|
||||
sidebar_position: 5
|
||||
sidebar_label: '合并请求关联疑修'
|
||||
sidebar_position: 5
|
||||
---
|
||||
|
||||
# 合并请求关联疑修
|
||||
# 合并请求关联疑修
|
||||
|
||||
在 GitLink 中,合并请求(PR)可以与疑修(Issue)进行关联,从而让项目成员清晰地了解某个合并请求是为了解决哪些具体问题。
|
||||
|
||||
## 关联合并请求与疑修
|
||||
|
||||
1. 在创建或编辑合并请求时,可以在右侧边栏的"疑修"区域中搜索并选择需要关联的疑修;
|
||||
2. 也可以在疑修详情页面中,通过编辑"合并请求"属性来关联已有的合并请求。
|
||||
|
||||
## 关联的作用
|
||||
|
||||
- 当合并请求被合并后,其所关联的疑修可以自动关闭,减少手动操作;
|
||||
- 在疑修列表中可以快速跳转到对应的合并请求,反之亦然;
|
||||
- 方便项目管理者追踪疑修的解决进度和对应的代码变更。
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
---
|
||||
sidebar_label: '合并请求简介'
|
||||
sidebar_position: 1
|
||||
sidebar_label: '合并请求简介'
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# 合并请求简介
|
||||
|
||||
**Pull Request**,即**合并请求**,是现代分布式软件开发中两个软件仓库间合并代码变更的一种方式,通常用于 fork 仓库向被 fork 仓库(源仓库)提交代码变更,同时也是为团队项目或开源项目做贡献的一种非常好的团队协作方式。当你拉取并修改了他人的仓库代码后,将你的修改告知给原仓库的管理者,请求其合并你的修改,这个过程就被称为 **合并请求**。
|
||||
|
||||
GitLink中的 **合并请求(PR)** 模块提供合并请求创建和管理两方面的功能:
|
||||
GitLink 中的**合并请求 (PR)** 模块提供合并请求创建和管理两方面的功能:
|
||||
|
||||
- 一方面支持向源仓库或者同一个仓库其他分支创建(发起)代码合并请求;
|
||||
|
||||
- 另一方面也为仓库管理者对他人发送到本仓库的合并请求进行管理、审阅并最终确定是否纳入仓库。
|
||||
|
||||
如下图所示为合并请求(PR)管理模块:
|
||||
如下图所示为合并请求 (PR) 管理模块:
|
||||
|
||||

|
||||
|
|
@ -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 引擎
|
||||
|
||||
### Q8:DevOps 引擎支持哪些语言?
|
||||
|
||||
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) 提交疑修反馈。
|
||||
|
|
@ -1,4 +1,14 @@
|
|||
---
|
||||
sidebar_label: '平台公告'
|
||||
sidebar_position: 1
|
||||
---
|
||||
sidebar_label: '平台公告'
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# 平台公告
|
||||
|
||||
## 最新公告
|
||||
|
||||
目前暂无最新平台公告。请关注 GitLink 平台通知,获取最新的服务更新、功能上线及维护信息。
|
||||
|
||||
## 历史公告
|
||||
|
||||
历史公告可在 GitLink 平台首页查看,或通过站内通知系统查询。如有疑问,请联系平台管理员。
|
||||
|
|
|
|||
|
|
@ -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 操作详见 [提交第一行代码](/快速开始/提交第一行代码) 和 [分支管理](/代码库管理/分支管理)。
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
---
|
||||
sidebar_label: '创建第一个开源项目'
|
||||
sidebar_position: 2
|
||||
sidebar_label: '创建第一个开源项目'
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# 创建第一个开源项目
|
||||
# 创建wyx第一个开源项目
|
||||
|
||||
## 1. 新建项目
|
||||
|
||||
|
|
|
|||
|
|
@ -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 进行授权。
|
||||
|
||||

|
||||
|
||||
## 3. 授权验证
|
||||
|
||||
在使用GitLink平台导入其他平台(如GitHub、Gitee)的开源项目时,如果项目为私有,则无法通过正常途径导入,需要输入对应平台有权限的token值进行校验。
|
||||
在使用 GitLink 平台导入其他平台(如 GitHub、Gitee)的开源项目时,如果项目为私有,则无法通过正常途径导入,需要输入对应平台有权限的 token 值进行校验。
|
||||
|
||||

|
||||
|
||||
下面将列举一些典型开源平台的token获取方式。
|
||||
下面将列举一些典型开源平台的 token 获取方式。
|
||||
|
||||
### GitHub token获取方式
|
||||
### GitHub token 获取方式
|
||||
|
||||
1. 登录GitHub账号
|
||||
1. 登录 GitHub 账号
|
||||
|
||||
2. 访问用户头像下的settings菜单
|
||||
2. 访问用户头像下的 settings 菜单
|
||||
|
||||

|
||||
|
||||
3. 访问最下方的Developer settings
|
||||
3. 访问最下方的 Developer settings
|
||||
|
||||

|
||||
|
||||
4. 在跳转页访问Token(classic),新建一个classic token(若已保存token)
|
||||
4. 在跳转页访问 Token(classic),新建一个 classic token(若已保存 token)
|
||||
|
||||

|
||||
|
||||
5. 在token配置页面,输入token用途,并确保该token已勾选“repo”选项,否则将导入失败
|
||||
5. 在 token 配置页面,输入 token 用途,并确保该 token 已勾选”repo”选项,否则将导入失败
|
||||
|
||||

|
||||
|
||||
|
|
@ -49,33 +49,33 @@ sidebar_position: 5
|
|||
|
||||

|
||||
|
||||
6. 复制该token
|
||||
6. 复制该 token
|
||||
|
||||

|
||||
|
||||
并将该token输入至GitLink导入项目认证填写框
|
||||
并将该 token 输入至 GitLink 导入项目认证填写框
|
||||
|
||||

|
||||
|
||||
### Gitee token获取方式
|
||||
### Gitee token 获取方式
|
||||
|
||||
1. 登录Gitee账号
|
||||
1. 登录 Gitee 账号
|
||||
|
||||
2. 访问用户头像下的设置菜单
|
||||
|
||||

|
||||
|
||||
3. 访问“安全设置”栏下方“私人令牌”菜单
|
||||
3. 访问”安全设置”栏下方”私人令牌”菜单
|
||||
|
||||

|
||||
|
||||
4. 点击生成新令牌,并在令牌生成页面配置令牌名称,并确保令牌已勾选“project”权限选项,并保存该令牌
|
||||
4. 点击生成新令牌,并在令牌生成页面配置令牌名称,并确保令牌已勾选”project”权限选项,并保存该令牌
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
5. 在令牌生成成功的弹窗,复制该令牌,并将该token输入至GitLink导入项目认证填写框
|
||||
5. 在令牌生成成功的弹窗,复制该令牌,并将该 token 输入至 GitLink 导入项目认证填写框
|
||||
|
||||

|
||||
|
||||
|
|
@ -83,7 +83,7 @@ sidebar_position: 5
|
|||
|
||||
## 4. 导入成功
|
||||
|
||||
提示正在从第三方Git项目地址迁移
|
||||
提示正在从第三方 Git 项目地址迁移
|
||||
|
||||

|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
---
|
||||
sidebar_label: '提交第一行代码'
|
||||
sidebar_position: 3
|
||||
sidebar_label: '提交第一行代码'
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# 提交第一行代码
|
||||
|
||||
# 1. 编辑代码
|
||||
## 1. 编辑代码
|
||||
|
||||
点击编辑按钮,开始编辑代码。
|
||||
|
||||

|
||||
|
||||
# 2. 提交代码
|
||||
## 2. 提交代码
|
||||
|
||||
在编辑框中编写代码,编写完成后填写变更信息后提交变更。
|
||||
|
||||
|
|
@ -19,4 +19,4 @@ sidebar_position: 3
|
|||
|
||||
## 3. 代码更新成功
|
||||
|
||||
提交成功后代码代码更新成功。
|
||||
提交成功后代码更新成功。
|
||||
|
|
@ -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. 搜索开源项目
|
||||
|
||||
有两处搜索框可以搜索
|
||||
有两处搜索框可以搜索。
|
||||
|
||||
### 开源项目搜索框
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
---
|
||||
sidebar_label: '注册GitLink账号'
|
||||
sidebar_position: 1
|
||||
sidebar_label: '注册GitLink账号'
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# 注册GitLink账号
|
||||
# 注册jtx的 GitLink 账号
|
||||
|
||||
## 1. 点击**立即注册**按钮
|
||||
|
||||
|
|
|
|||
|
|
@ -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 帮助中心变得更好。
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
sidebar_label: 'GitLink服务协议'
|
||||
sidebar_label: 'GitLink服务协议'
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
|
||||
尊敬的用户,您好!
|
||||
|
||||
欢迎使用GitLink平台,在您使用GitLink平台前,请您认真阅读并遵守《GitLink服务协议》(以下简称”本协议”),请您务必审慎阅读、充分理解协议的各条款内容。
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
sidebar_label: '标记管理'
|
||||
sidebar_position: 6
|
||||
sidebar_label: '标记管理'
|
||||
sidebar_position: 6
|
||||
---
|
||||
|
||||
# 标记管理
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
---
|
||||
sidebar_label: '疑修列表'
|
||||
sidebar_position: 4
|
||||
sidebar_label: '疑修列表'
|
||||
sidebar_position: 4
|
||||
---
|
||||
# 疑修列表
|
||||
|
||||
对于项目开发过程中创建的所有疑修,可以在**疑修(Issue)** 界面统一查看,如下图所示为[确实开源](https://www.gitlink.org.cn/Gitlink/forgeplus)项目下的疑修列表。
|
||||
|
||||
|
||||

|
||||
|
||||
+ **创建疑修**:在疑修列表界面下,点击“**创建疑修**”按钮,同样可以创建疑修,具体见 ***疑修创建*** 一节;
|
||||
+ **创建疑修**:在疑修列表界面下,点击”**创建疑修**”按钮,同样可以创建疑修,具体见 [疑修创建](/疑修/疑修创建) 一节;
|
||||
|
||||
+ **筛选疑修**:疑修列表支持不同条件筛选,包括发布人、标记(见 ***标记管理*** 一节)、里程碑(见 ***里程碑管理*** 一节),负责人、状态和开始/结束日期等;同时支持关键字搜索,以及按多种排序规则对疑修进行排序。
|
||||
+ **筛选疑修**:疑修列表支持不同条件筛选,包括发布人、标记(见 [标记管理](/疑修/标记管理) 一节)、里程碑(见 [里程碑管理](/疑修/里程碑管理) 一节),负责人、状态和开始/结束日期等;同时支持关键字搜索,以及按多种排序规则对疑修进行排序。
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
sidebar_label: '疑修创建'
|
||||
sidebar_position: 2
|
||||
sidebar_label: '疑修创建'
|
||||
sidebar_position: 2
|
||||
---
|
||||
# 疑修创建
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
---
|
||||
sidebar_label: '疑修状态变更'
|
||||
sidebar_position: 3
|
||||
sidebar_label: '疑修状态变更'
|
||||
sidebar_position: 3
|
||||
---
|
||||
# 疑修状态变更
|
||||
|
||||
**疑修**本质上是开发任务,而开发任务随着开发活动的进行,其状态也会发生改变,而“**状态**”便是用于跟踪记录开发活动的变更。如图所示,GitLink中疑修的**状态**包括“新增”、“正在解决”、“已解决”、“关闭”和“拒绝“五类,用于表示开发任务的处理进度。
|
||||
**疑修**本质上是开发任务,而开发任务随着开发活动的进行,其状态也会发生改变,而“**状态**”便是用于跟踪记录开发活动的变更。如图所示,GitLink 中疑修的**状态**包括”新增”、”正在解决”、”已解决”、”关闭”和”拒绝”五类,用于表示开发任务的处理进度。
|
||||
|
||||

|
||||
|
||||
|
|
|
|||
|
|
@ -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. 可以为**疑修**设置开始和结束时间,并指定负责解决该任务的负责人,在疑修截止日期的前一天,系统将自动为任务的发布者和被指派人发送提醒消息。
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
sidebar_label: '评论及操作记录'
|
||||
sidebar_position: 5
|
||||
sidebar_label: '评论及操作记录'
|
||||
sidebar_position: 5
|
||||
---
|
||||
|
||||
# 评论及操作记录
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
sidebar_label: '里程碑管理'
|
||||
sidebar_position: 7
|
||||
sidebar_label: '里程碑管理'
|
||||
sidebar_position: 7
|
||||
---
|
||||
|
||||
# 里程碑管理
|
||||
|
|
@ -24,10 +24,11 @@ sidebar_position: 7
|
|||

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

|
||||
|
|
|
|||
|
|
@ -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 可选中连续多行。
|
||||

|
||||
<center>多选行高亮</center>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
---
|
||||
sidebar_label: '跨平台代码同步服务'
|
||||
sidebar_position: 2
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
### 功能简介
|
||||
|
||||
跨平台仓库代码同步服务用于不同代码托管平台之间的仓库代码双向同步功能。用户将不同平台的仓库分支进行绑定,当任意仓库分支有push事件被webhook监听时,代码推送将实时自动同步至另一个仓库,以实现不同平台之间分支、提交信息的自动同步(代码托管平台仅限于github与gitee)。
|
||||
跨平台仓库代码同步服务用于不同代码托管平台之间的仓库代码双向同步功能。用户将不同平台的仓库分支进行绑定,当任意仓库分支有push事件被webhook监听时,代码推送将实时自动同步至另一个仓库,以实现不同平台之间分支、提交信息的自动同步(代码托管平台仅限于 GitHub 与 Gitee)。
|
||||
|
||||
|
||||

|
||||
|
|
@ -57,7 +57,7 @@ Gitee配置方式为:个人头像→设置→私人令牌→生成新令牌→
|
|||
②查询两个分支间最新一次的同步时间及同步状态。若同步失败,可在同步记录中查询日志分析失败原因<br />
|
||||
③添加同步仓库,若已绑定了github的同步仓库,想在gitee导入一个仓库进行开发,并想完成实时多个仓库的分支同步。<br />
|
||||
④查看同步配置,可用于查询同步仓库的地址,GitLink 用于接受第三方webhook请求的地址,以及更新token。以防token过期<br />
|
||||
⑤查询同步记录,包括查看历次同步的代码变更方,同步时间,同步状态及对应commt id,查询同步日志。<br />
|
||||
⑤查询同步记录,包括查看历次同步的代码变更方,同步时间,同步状态及对应commit id,查询同步日志。<br />
|
||||
⑥中止、开启同步,相当于同步开关,可随时启停
|
||||
|
||||

|
||||
|
|
|
|||
|
|
@ -5,24 +5,24 @@ sidebar_position: 1
|
|||
|
||||
## 代码分析入口
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<center>代码分析入口</center><br/>
|
||||
页面说明:<br/>
|
||||
1、用户可点击"服务"菜单 tab 进入服务页面。<br/>
|
||||
2、仓库管理员可在"仓库设置" tab 中项目导航处开启/关闭代码分析菜单可见性,如下图:<br/>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<center>项目导航</center><br/>
|
||||
|
||||
## 代码分析检测
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<center>代码分析检测</center><br/>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<center>新建分析</center><br/>
|
||||
页面说明:<br/>
|
||||
|
|
@ -32,7 +32,7 @@ sidebar_position: 1
|
|||
|
||||
## 分支选择
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<center>分支选择</center><br/>
|
||||
页面说明:<br/>
|
||||
|
|
@ -42,7 +42,7 @@ sidebar_position: 1
|
|||
|
||||
## 列表展示
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<center>列表展示</center><br/>
|
||||
页面说明:<br/>
|
||||
|
|
@ -52,7 +52,7 @@ sidebar_position: 1
|
|||
|
||||
## 结果展示
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<center>结果展示</center><br/>
|
||||
页面说明:<br/>
|
||||
|
|
@ -62,6 +62,6 @@ sidebar_position: 1
|
|||
|
||||
## 用户操作流程
|
||||
|
||||
.png)<br/>
|
||||
.png)<br/>
|
||||
|
||||
<center>用户操作流程</center><br/>
|
||||
|
|
|
|||
|
|
@ -1,31 +1,31 @@
|
|||
---
|
||||
sidebar_label: '组织创建及设置'
|
||||
sidebar_position: 2
|
||||
sidebar_label: '组织创建及设置'
|
||||
sidebar_position: 2
|
||||
---
|
||||
# 组织创建(Organization Creation)
|
||||
|
||||
|
||||
在 *https://www.gitlink.org.cn* 页面点击顶部导航栏的“+”符号可以进行组织新建操作。
|
||||
|
||||

|
||||
|
||||
|
||||

|
||||
|
||||
在新建页面中输入**组织账号**、**组织名称**、**组织描述**、**所在地区**、**可见性**以及**组织头像**等信息后,点击“**创建组织**”按钮完成组织的创建。
|
||||
|
||||

|
||||

|
||||
|
||||
## 组织账号
|
||||
|
||||

|
||||

|
||||
|
||||
**注**:只能使用以字母、数字开头,包含字母、数字、下划线、横杠等,长度4到20个字符
|
||||
> **注**:只能使用以字母、数字开头,包含字母、数字、下划线、横杠等,长度4到20个字符
|
||||
|
||||
## 组织名称与组织描述
|
||||
|
||||

|
||||

|
||||
|
||||
**注**:此处为必填项,不得为空
|
||||
> **注**:此处为必填项,不得为空
|
||||
|
||||
## 可见性
|
||||
|
||||

|
||||

|
||||
|
||||
**注**:可见性预设三类组织:公开、受限(仅对登录用户可见)、私有(仅对组织成员可见)。
|
||||
> **注**:可见性预设三类组织:公开、受限(仅对登录用户可见)、私有(仅对组织成员可见)。
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
sidebar_label: '组织团队管理'
|
||||
sidebar_position: 3
|
||||
sidebar_label: '组织团队管理'
|
||||
sidebar_position: 3
|
||||
---
|
||||
# 组织团队管理
|
||||
|
||||
|
|
@ -9,26 +9,29 @@ sidebar_position: 3
|
|||
|
||||
在团队新建页面,输入团队标识、团队名称、团队描述、项目权限以及版本库权限等信息后,点击“新建团队”完成团队的创建。
|
||||
|
||||

|
||||

|
||||
|
||||
## 查看组织团队
|
||||
|
||||
点击组织信息页面中的某个团队名称可以查看该团队的详细信息,该页面包括团队的名称、描述等信息,此外还会列出该团队关联的成员以及项目。
|
||||
|
||||

|
||||

|
||||
|
||||
## 管理组织团队
|
||||
|
||||
点击团队信息页面中的“团队设置”按钮可以对团队进行管理
|
||||
点击团队信息页面中的”团队设置”按钮可以对团队进行管理
|
||||
|
||||
- 基本设置:修改项目的基本信息,如名称和描述等。
|
||||

|
||||
- 基本设置:修改团队的基本信息,如名称和描述等。
|
||||
|
||||
- 团队成员管理:为该团队添加新成员或者移除已有成员。
|
||||

|
||||

|
||||
|
||||
- 团队项目管理:为该团队关联新项目(该组织已经创建的项目)或者移除已关联项目。
|
||||

|
||||
- 团队成员管理:为该团队添加新成员或者移除已有成员。
|
||||
|
||||

|
||||
|
||||
- 团队项目管理:为该团队关联新项目(该组织已经创建的项目)或者移除已关联项目。
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,21 +1,24 @@
|
|||
---
|
||||
sidebar_label: '组织成员管理'
|
||||
sidebar_position: 4
|
||||
sidebar_label: '组织成员管理'
|
||||
sidebar_position: 4
|
||||
---
|
||||
# 成员管理(Members Management)
|
||||
|
||||
在 个人所管理的项目当中的**仓库设置**当中的**成员管理**可以进入成员管理界面
|
||||
|
||||

|
||||
|
||||
|
||||
在组织信息页面中的"成员管理"可以进入成员管理界面
|
||||
|
||||

|
||||
|
||||
## 成员查询和添加
|
||||
在成员管理界面当中,可以进行组织成员的查询和添加
|
||||
|
||||

|
||||

|
||||
|
||||
## 成员角色管理
|
||||
管理员可以对组织成员的角色进行管理,包括设置成员为管理员或普通成员
|
||||
|
||||
## 成员邀请权限设置
|
||||
管理员可以对邀请成员的权限进行设置
|
||||

|
||||

|
||||
此外,也可以复制邀请链接的方式来更方便地邀请成员加入到项目当中来
|
||||
|
||||
**注**:此项功能仅对管理员可见
|
||||
> **注**:此项功能仅对管理员可见
|
||||
|
|
@ -9,7 +9,7 @@ sidebar_position: 1
|
|||
|
||||
您的团队可以通过使用组织帐户在 GitLink 上进行协作,组织帐户充当共享工作的容器,并为工作赋予独特的名称和品牌。同时,平台支持组织在“组织详情”页面发布新闻动态,显示项目概览和仓库详情等内容
|
||||
|
||||

|
||||

|
||||
|
||||
## 作为组织拥有者
|
||||
|
||||
|
|
@ -19,10 +19,10 @@ sidebar_position: 1
|
|||
|
||||
为了简化访问管理并增强协作,您可以创建能体现组结构的嵌套团队。您可以根据他们的角色或项目将人员分组,并分配任务。
|
||||
|
||||

|
||||

|
||||
|
||||
平台同时支持组织拥有者管理对数据访问的自定义设置。
|
||||
|
||||
## 作为组织成员
|
||||
|
||||
您可以通过组织与无限数量的人在多个项目中协作,与志同道合的人一起,通过分工和写作,参与开发流程,发布或处理问题。
|
||||
您可以通过组织与无限数量的人在多个项目中协作,与志同道合的人一起,通过分工和协作,参与开发流程,发布或处理问题。
|
||||
|
|
|
|||
|
|
@ -1,12 +1,25 @@
|
|||
---
|
||||
sidebar_label: '组织项目管理'
|
||||
sidebar_position: 5
|
||||
sidebar_label: '组织项目管理'
|
||||
sidebar_position: 5
|
||||
---
|
||||
## 创建组织项目
|
||||
|
||||
点击组织信息页面中的“新建项目”按钮可以创建属于该组织的托管项目或者镜像项目。
|
||||
# 组织项目管理
|
||||
|
||||

|
||||
## 创建组织项目
|
||||
|
||||
**注**:在“拥有者”一栏的下拉选项中,可以选择:个人、组织、团队
|
||||
点击组织信息页面中的"新建项目"按钮可以创建属于该组织的托管项目或者镜像项目。
|
||||
|
||||

|
||||
|
||||
> **注**:在"拥有者"一栏的下拉选项中,可以选择:个人、组织、团队
|
||||
|
||||
## 管理组织项目
|
||||
|
||||
组织创建者和管理员可以对组织下的项目进行统一管理,主要包括以下操作:
|
||||
|
||||
- **查看项目列表**:在组织信息页面中可以查看该组织下的所有项目,包括项目名称、描述、可见性等信息。
|
||||
- **项目权限设置**:可以为不同的团队分配不同的项目访问权限,包括读取、写入和管理员权限。
|
||||
- **项目转移**:可以将个人项目转移到组织下,也可以将组织下的项目转移给其他组织或个人。
|
||||
|
||||
## 移除组织项目
|
||||
|
||||
如果需要将项目从组织中移除,可以在项目设置中进行操作。移除后项目将不再显示在组织的项目列表中,但项目本身不会被删除。
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
---
|
||||
sidebar_label: '模板导入及导出'
|
||||
sidebar_position: 2
|
||||
sidebar_label: '模板导入及导出'
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# 确实开源帮助中心
|
||||
# 模板导入及导出
|
||||
|
||||
## 模板的导入及导出管理
|
||||
#### 1.模板导入
|
||||
通过点击wiki界面的**导入模板**按钮即可导入本地的txt,markdown等格式文件进行Wiki文档的创建,当前平台此项功能尚在开发测试环节
|
||||
[](/img/wiki/img4.png)
|
||||

|
||||
#### 2.模板导出
|
||||
当前平台此项功能尚在开发测试环节
|
||||
<br/>
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
---
|
||||
sidebar_label: '维基页面管理'
|
||||
sidebar_position: 1
|
||||
sidebar_label: '维基页面管理'
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
|
||||
# 确实开源帮助中心
|
||||
# 维基页面管理
|
||||
|
||||
## 维基界面管理
|
||||
#### 1.维基界面
|
||||
GitLink项目安排了专门的**维基(Wiki)界面**来展示项目的一些情况
|
||||
[](/img/wiki/img1.png)
|
||||

|
||||
|
||||
#### 2.创建维基内容
|
||||
在初始化的维基界面当中点击**创建Wiki文档**或者**导入模板**即可进行Wiki文档的编辑
|
||||
|
|
@ -17,7 +17,7 @@ GitLink项目安排了专门的**维基(Wiki)界面**来展示项目的一些情
|
|||
|
||||
#### 3.进行Wiki文档的编辑
|
||||
点击**创建Wiki文档**后即跳转到编辑界面,初始内容为空白的文档
|
||||
[](/img/wiki/img3.png)
|
||||

|
||||
也可以通过勾选*添加模板*来进行模板的导入,平台提供的模板包括*周报*和*月报*的格式
|
||||
[](/img/wiki/img2.png)
|
||||
完成编辑后点击左下角的的保存即可
|
||||

|
||||
完成编辑后点击左下角的保存即可
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
---
|
||||
sidebar_label: '通知简介'
|
||||
sidebar_label: '通知简介'
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# 确实开源帮助中心
|
||||
# 通知简介
|
||||
|
||||
## 通知简介
|
||||
#### 1.通知类型
|
||||
|
|
@ -25,5 +25,5 @@ GitLink将通知分为“系统通知”和“@我”的两种类型:
|
|||
<br/>
|
||||
|
||||
|
||||
点击通知按钮可以进入消息通知界面,“我的通知”界面可以进行“进查看未读消息”和“所有消息一键已读”的选择。
|
||||
点击通知按钮可以进入消息通知界面,“我的通知”界面可以进行“仅查看未读消息”和“所有消息一键已读”的选择。
|
||||

|
||||
|
|
@ -1,10 +1,11 @@
|
|||
---
|
||||
sidebar_label: '通知设置'
|
||||
sidebar_position: 2
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
|
||||
# 确实开源帮助中心
|
||||
|
||||
|
||||
|
||||
# 通知设置
|
||||
|
||||
## 通知设置
|
||||
#### 1.进入通知设置界面
|
||||
|
|
@ -16,6 +17,6 @@ sidebar_position: 2
|
|||

|
||||
|
||||
#### 2.进行通知设置
|
||||
通过“通知管理”可以对接受通知的方式进行设置,默认所有通知都是通过站内信的方式接受,可以通过勾选为重要的通知类型增加邮件接受方式。
|
||||
通过“通知管理”可以对接收通知的方式进行设置,默认所有通知都是通过站内信的方式接收,可以通过勾选为重要的通知类型增加邮件接收方式。
|
||||
|
||||

|
||||
|
|
@ -1,35 +1,54 @@
|
|||
const lightCodeTheme = require('prism-react-renderer/themes/github');
|
||||
const darkCodeTheme = require('prism-react-renderer/themes/dracula');
|
||||
// import("@easyops-cn/docusaurus-search-local").PluginOptions;
|
||||
//Docusaurus v3写法
|
||||
// const { themes } = require('prism-react-renderer');
|
||||
// import { themes } from 'prism-react-renderer';
|
||||
|
||||
/** @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: 'ignore',
|
||||
onBrokenMarkdownLinks: 'ignore',
|
||||
onBrokenLinks: 'warn',
|
||||
onBrokenMarkdownLinks: 'warn',
|
||||
favicon: 'img/icon.ico',
|
||||
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:{
|
||||
sidebar:{
|
||||
hideable:true,
|
||||
},
|
||||
},
|
||||
tableOfContents: {
|
||||
minHeadingLevel: 2,
|
||||
maxHeadingLevel: 4,
|
||||
},
|
||||
metadata:[
|
||||
{name:"Keywords",content:"学位论文算法,帮助中心"},
|
||||
{name:"Keywords",content:"Gitlink,forgeplus,GitLink,gitLink,GitLink,gitlink,帮助中心"},
|
||||
{name:"hostname",content:"gitlink.org.cn"},
|
||||
{property:"og:site_name",content:"学位论文算法"},
|
||||
{property:"og:image:alt",content:"学位论文算法"}
|
||||
{property:"og:site_name",content:"GitLink"},
|
||||
{property:"og:image:alt",content:"GitLink(确实开源)是中国计算机学会CCF官方指定的开源创新服务平台 - Gitlink/forgeplus:GitLink(确实开源)是中国计算机学会CCF官方指定的开源创新服务平台 for gitlink Gitlink/forgeplus for git"}
|
||||
],
|
||||
prism: {
|
||||
theme: lightCodeTheme,
|
||||
theme:lightCodeTheme,
|
||||
darkTheme: darkCodeTheme,
|
||||
},
|
||||
zoom: {
|
||||
|
|
@ -53,9 +72,9 @@ module.exports = {
|
|||
logo: {
|
||||
// width: '32px',
|
||||
// height: '32px',
|
||||
alt: '学位论文',
|
||||
src: 'img/xuewei.png',
|
||||
href:"/"
|
||||
alt: 'gitlink',
|
||||
src: 'img/gitlink.png',
|
||||
href:"https://www.gitlink.org.cn/"
|
||||
// srcDark: 'img/logo-dark.png',
|
||||
},
|
||||
items: [
|
||||
|
|
@ -70,6 +89,26 @@ module.exports = {
|
|||
position: 'left',
|
||||
dropdownActiveClassDisabled: true
|
||||
},
|
||||
{
|
||||
type: 'localeDropdown',
|
||||
position: 'right',
|
||||
},
|
||||
// {
|
||||
// position: 'right',
|
||||
// component:'./src/components/LanguageSwitcher',
|
||||
// label:"language",
|
||||
// to:"#"
|
||||
// items: [
|
||||
// {
|
||||
// label: 'English',
|
||||
// onClick: () => handleLocaleChange('en')
|
||||
// },
|
||||
// {
|
||||
// label: '简体中文',
|
||||
// onClick: () => handleLocaleChange('zh-Hans')
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// {
|
||||
// href: 'https://github.com/boxyhq',
|
||||
// position: 'right',
|
||||
|
|
@ -78,23 +117,72 @@ module.exports = {
|
|||
],
|
||||
},
|
||||
footer: {
|
||||
// style: 'dark',
|
||||
// logo:{
|
||||
// src:"img/gitlink.png"
|
||||
// },
|
||||
// links: [
|
||||
// {
|
||||
// title: '关于我们',
|
||||
// items: [
|
||||
// {
|
||||
// label: '根据《“学位论文算法数据库”建设试点方案》及有关要求,计算机学院对第一阶段的数据类型、上传流程及数据描述等进行梳理,明确支持上传的数据类型主要包括代码、数据集、说明文档等,博士研究生应根据具体类型,完成相关数据整理和上传。',
|
||||
// to:""
|
||||
// }
|
||||
// ],
|
||||
// }
|
||||
// ],
|
||||
// copyright: `<p>©Copyright ${new Date().getFullYear()} CCF 开源发展委员会</p><p>Powered by Trustie& IntelliDE 京ICP备13000930号</p>`,
|
||||
copyright: `<img src="/img/xuewei.png" /><div class="text"><p>关于我们</p><p>根据《“学位论文算法数据库”建设试点方案》及有关要求,计算机学院对第一阶段的数据类型、上传流程及数据描述等进行梳理,明确支持上传的数据类型主要包括代码、数据集、说明文档等,博士研究生应根据具体类型,完成相关数据整理和上传。</p><p style="margin-top:20px">工具下载</p><p><a>git32位安装下载</a><a>git64位安装下载</a></p></div>`,
|
||||
style: 'dark',
|
||||
logo:{
|
||||
src:"img/gitlink.png"
|
||||
},
|
||||
links: [
|
||||
{
|
||||
title: '社区',
|
||||
items: [
|
||||
{
|
||||
label: '网站首页',
|
||||
href: 'https://www.gitlink.org.cn',
|
||||
},
|
||||
{
|
||||
label: '关于我们',
|
||||
href: 'https://www.gitlink.org.cn/aboutus',
|
||||
},
|
||||
{
|
||||
label: '教学实践',
|
||||
href: 'https://www.gitlink.org.cn/educoder',
|
||||
},
|
||||
{
|
||||
label: '合作伙伴',
|
||||
href: 'https://forum.trustie.net/forums/5030/detail',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '支持与服务',
|
||||
items: [
|
||||
{
|
||||
label: 'API文档',
|
||||
href: 'https://forgeplus.trustie.net/docs/api',
|
||||
},
|
||||
{
|
||||
label: 'Git常用命令',
|
||||
href: 'https://git-scm.com',
|
||||
},
|
||||
{
|
||||
label: '引擎使用手册',
|
||||
href: 'https://forum.gitlink.org.cn/forums/7487/detail',
|
||||
},
|
||||
{
|
||||
label: '服务协议',
|
||||
href: 'https://forum.trustie.net/forums/5029/detail',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '加入我们',
|
||||
items: [
|
||||
{
|
||||
label: '官网邮箱:gitlink@ccf.org.cn',
|
||||
href:"https://www.gitlink.org.cn"
|
||||
},
|
||||
{
|
||||
label:'QQ群',
|
||||
href:'https://www.gitlink.org.cn'
|
||||
},
|
||||
{
|
||||
label:'公众号',
|
||||
href:'https://www.gitlink.org.cn'
|
||||
}
|
||||
],
|
||||
},
|
||||
],
|
||||
copyright: `<p>©Copyright ${new Date().getFullYear()} CCF 开源发展委员会</p><p>Powered by Trustie& IntelliDE 京ICP备13000930号</p>`,
|
||||
},
|
||||
},
|
||||
presets: [
|
||||
|
|
@ -114,25 +202,35 @@ module.exports = {
|
|||
],
|
||||
themes: [
|
||||
// ... Your other themes.
|
||||
// [
|
||||
// require.resolve("@easyops-cn/docusaurus-search-local"),
|
||||
// {
|
||||
// // ... Your options.
|
||||
// // `hashed` is recommended as long-term-cache of index file is possible.
|
||||
// hashed: true,
|
||||
// language: ["en", "zh"],
|
||||
// highlightSearchTermsOnTargetPage: true,
|
||||
// blogRouteBasePath: "/",
|
||||
// explicitSearchResultPath: true,
|
||||
// // For Docs using Chinese, The `language` is recommended to set to:
|
||||
// // ```
|
||||
// // language: ["en", "zh"],
|
||||
// // ```
|
||||
// },
|
||||
// ],
|
||||
[
|
||||
require.resolve("@easyops-cn/docusaurus-search-local"),
|
||||
{
|
||||
// ... Your options.
|
||||
// `hashed` is recommended as long-term-cache of index file is possible.
|
||||
hashed: true,
|
||||
language: ["en", "zh"],
|
||||
highlightSearchTermsOnTargetPage: true,
|
||||
blogRouteBasePath: "/",
|
||||
explicitSearchResultPath: true,
|
||||
// For Docs using Chinese, The `language` is recommended to set to:
|
||||
// ```
|
||||
// language: ["en", "zh"],
|
||||
// ```
|
||||
},
|
||||
],
|
||||
],
|
||||
i18n: {
|
||||
defaultLocale: 'zh-cn',
|
||||
locales: ['zh-cn'],
|
||||
locales: ['zh-cn','en'],
|
||||
localeConfigs: {
|
||||
en: {
|
||||
label: 'English',
|
||||
htmlLang: 'en',
|
||||
},
|
||||
'zh-cn': {
|
||||
label: '简体中文',
|
||||
htmlLang: 'zh-CN',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
23e041136cc43bcfa244f806799f5640
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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).
|
||||
|
||||

|
||||
|
||||
# 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.
|
||||
|
|
@ -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 帮助中心"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
为了构建更好的平台生态环境,我们推出了基于OAuth2的API版本。
|
||||
API 接口使用方式以及Url都参照GitHub,为了各位开发者更好的兼容已经存在的第三方应用。
|
||||
|
||||
# API 使用条款
|
||||
|
||||
未经用户允许,不准爬取或存储用户的资源。
|
||||
禁止滥用 API,请求频率过快将导致请求终止。
|
||||
OAuth2 认证基本流程
|
||||

|
||||
|
||||
# OAuth2 获取 AccessToken 认证步骤
|
||||
|
||||
### 1. 授权码模式
|
||||
* 应用通过 浏览器 或 Webview 将用户引导到平台三方认证页面上( GET请求 )
|
||||
|
||||
`https://gitlink.org.cn/oauth/authorize?client_id={client_id}&redirect_uri={redirect_uri}&response_type=code`
|
||||
|
||||
* 用户对应用进行授权
|
||||
注意: 如果之前已经授权过的需要跳过授权页面,需要在上面第一步的 URL 加上 scope 参数,且 scope 的值需要和用户上次授权的勾选的一致。如用户在上次授权了user_info。则步骤A 中 GET 请求应为:
|
||||
|
||||
`https://gitlink.org.cn/oauth/authorize?client_id={client_id}&redirect_uri={redirect_uri}&response_type=code&scope=user_info`
|
||||
|
||||
* 平台认证服务器通过回调地址`{redirect_uri}`将 用户授权码 传递给 应用服务器 或者直接在 Webview 中跳转到携带 用户授权码的回调地址上,Webview 直接获取code即可(`{redirect_uri}?code=abc&state=xyz`)
|
||||
* 应用服务器 或 Webview 使用 access_token API 向平台认证服务器发送post请求传入 用户授权码 以及 回调地址( POST请求 )
|
||||
注:请求过程建议将 client_secret 放在 Body 中传值,以保证数据安全。
|
||||
|
||||
`https://gitlink.org.cn/oauth/token?grant_type=authorization_code&code={code}&client_id={client_id}&redirect_uri={redirect_uri}&client_secret={client_secret}`
|
||||
|
||||
* 平台认证服务器返回 access_token
|
||||
|
||||
应用通过 access_token 访问 Open API 使用用户数据。
|
||||
当 access_token 过期后(有效期为一天),你可以通过以下 refresh_token 方式重新获取 access_token( POST请求 )
|
||||
|
||||
`https://gitlink.org.cn/oauth/token?grant_type=refresh_token&refresh_token={refresh_token}`
|
||||
|
||||
> 注意:如果获取 access_token 返回 403,可能是没有设置User-Agent的原因。
|
||||
详见:获取Token时服务端响应状态403是什么情况
|
||||
|
||||
### 2. 密码模式
|
||||
* 用户向客户端提供邮箱地址和密码。客户端将邮箱地址和密码发给平台认证服务器,并向平台认证服务器请求令牌。( POST请求。Content-Type: application/x-www-form-urlencoded )
|
||||
|
||||
`curl -X POST --data-urlencode "grant_type=password" --data-urlencode "username={email}" --data-urlencode "password={password}" --data-urlencode "client_id={client_id}" --data-urlencode "client_secret={client_secret}" --data-urlencode "scope=user_info" https://gitlink.org.cn/oauth/token`
|
||||
|
||||
scope表示权限范围,有以下选项,请求时使用空格隔开
|
||||
`user_info` `projects` `pull_requests` `issues` `notes` `keys` `hook groups` `gists` `enterprises`
|
||||
* 认证服务器返回 access_token
|
||||
应用通过 access_token 访问 Open API 使用用户数据。
|
||||
|
After Width: | Height: | Size: 135 KiB |
24
package.json
|
|
@ -1,11 +1,12 @@
|
|||
{
|
||||
"name": "docusaurus",
|
||||
"version": "0.0.0",
|
||||
"version": "3.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"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,18 +15,21 @@
|
|||
"write-heading-ids": "docusaurus write-heading-ids"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cmfcmf/docusaurus-search-local": "1.1.0",
|
||||
"@docusaurus/core": "2.4.1",
|
||||
"@docusaurus/plugin-client-redirects": "2.4.1",
|
||||
"@docusaurus/preset-classic": "2.4.1",
|
||||
"@easyops-cn/docusaurus-search-local": "^0.49.2",
|
||||
"@mdx-js/react": "1.6.22",
|
||||
"@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.55.1",
|
||||
"@mdx-js/react": "^3.1.1",
|
||||
"@node-rs/jieba": "^1.7.0",
|
||||
"clsx": "1.2.1",
|
||||
"docusaurus-gtm-plugin": "0.0.2",
|
||||
"docusaurus-plugin-image-zoom": "1.0.1",
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2",
|
||||
"or": "^0.2.0",
|
||||
"prism-react-renderer": "^1.3.5",
|
||||
"prismjs": "^1.30.0",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-modal": "3.16.1"
|
||||
},
|
||||
"browserslist": {
|
||||
|
|
@ -41,6 +45,6 @@
|
|||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.18.1 <=18.x"
|
||||
"node": ">=18.x"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
@ -2,7 +2,7 @@ module.exports = {
|
|||
DocsSidebar: [
|
||||
{
|
||||
type: 'doc',
|
||||
id: 'index',
|
||||
id: 'intro',
|
||||
label: 'Docs',
|
||||
}
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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 转发服务,本项目结构无需大改。)
|
||||
|
|
@ -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 模式才需要访客自己填 Key;server 模式由后端持有 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
/**
|
||||
* LLM 调用模块
|
||||
*
|
||||
* 支持两种模式(由 cfg.mode 决定):
|
||||
* - 'server'(默认/推荐):调用同源后端代理 /api/ai,由服务器持有 Key 转发给大模型。
|
||||
* 访客无需填写任何 Key,Key 只存在服务器的环境变量里,不进代码、不上传。
|
||||
* - 'local'(兜底):浏览器直连大模型(OpenAI 兼容协议),Key 由用户在设置里填写、
|
||||
* 仅存于其本机 localStorage。适合本地开发或后端不可用时。
|
||||
*
|
||||
* 两种模式都强制“只依据帮助中心文档回答、无关则回『无关』”。
|
||||
*/
|
||||
|
||||
/* ---------------- 系统 prompt(local 兜底模式用;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);
|
||||
}
|
||||
|
|
@ -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 };
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
// src/components/LanguageSwitcher.js
|
||||
import React from 'react';
|
||||
import { useDocusaurusContext } from '@docusaurus/useDocusaurusContext';
|
||||
|
||||
export default function LanguageSwitcher() {
|
||||
const { i18n } = useDocusaurusContext();
|
||||
|
||||
const locales = [
|
||||
{ code: 'en', label: 'English' },
|
||||
{ code: 'zh-Hans', label: '简体中文' },
|
||||
];
|
||||
|
||||
return (
|
||||
<div style={{ marginRight: '1rem' }}>
|
||||
<select
|
||||
value={i18n.currentLocale}
|
||||
onChange={(e) => {
|
||||
localStorage.setItem('preferredLocale', e.target.value);
|
||||
window.location.href = `/${e.target.value}/`;
|
||||
}}
|
||||
style={{
|
||||
padding: '0.5rem 1rem',
|
||||
border: '1px solid #ddd',
|
||||
borderRadius: '4px',
|
||||
backgroundColor: 'white',
|
||||
fontSize: '0.9rem',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
>
|
||||
{locales.map((locale) => (
|
||||
<option key={locale.code} value={locale.code}>
|
||||
{locale.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -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)',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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,23 +38,19 @@ 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;
|
||||
}
|
||||
.footer{
|
||||
/* height:450px;
|
||||
height:450px;
|
||||
position: relative;
|
||||
background: #1e1e1e; */
|
||||
background: #1e1e1e;
|
||||
padding:0px;
|
||||
}
|
||||
.container{
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
.footer__bottom .margin-bottom--sm{
|
||||
position: absolute;
|
||||
|
|
@ -135,33 +128,12 @@ html[data-theme='dark'] .docusaurus-highlight-code-line {
|
|||
font-weight: 400;
|
||||
color: #bdc2d1;
|
||||
line-height: 28px;
|
||||
padding: 15px 0;
|
||||
text-align: center;
|
||||
background-color: #1b212c;
|
||||
padding:50px 0px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 17px;
|
||||
}
|
||||
.container .footer__copyright .text{
|
||||
text-align: left;
|
||||
}
|
||||
.container .footer__copyright img{
|
||||
width: 300px;
|
||||
}
|
||||
.container .footer__copyright .text p:nth-child(2n+1){
|
||||
font-size: 22px;
|
||||
margin-left: 50px;
|
||||
margin-bottom: 10px!important;
|
||||
}
|
||||
.container .footer__copyright .text p{
|
||||
.container .footer__copyright p{
|
||||
margin-bottom: 0px!important;
|
||||
max-width: 950px;
|
||||
margin-left: 50px;
|
||||
}
|
||||
.container .footer__copyright .text p a{
|
||||
color: #bdc2d1;
|
||||
margin-right: 25px;
|
||||
}
|
||||
.aa-Form{
|
||||
width: unset;
|
||||
|
|
@ -170,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);
|
||||
}
|
||||
|
|
@ -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 />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -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 />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
Before Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 8.9 KiB |
|
Before Width: | Height: | Size: 5.5 KiB |
|
|
@ -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 |
|
Before Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 5.6 KiB |