Compare commits

...

10 Commits

Author SHA1 Message Date
kirigaya
dfe436d178 update 2025-05-13 11:12:24 +08:00
kirigaya
72182b32f5 update 2025-05-11 20:31:47 +00:00
0525539ad8 完成 EDL 的构建,完成 QA 的拒答流 2024-06-09 23:49:25 +08:00
3423bddc7b 完成 EDL 的构建,完成 QA 的拒答流 2024-06-07 02:27:19 +08:00
b439c4de7e delete story 2024-06-06 15:30:54 +08:00
d72b2a213c delete story 2024-06-06 15:30:26 +08:00
b811246fba 完成单元测试 | 更新架构 2024-06-06 14:05:57 +08:00
c27bb27ec3 finish pipe 2024-06-01 02:14:46 +08:00
a13a6ba34f finish onebot interface 2024-05-30 22:54:40 +08:00
3dad495da9 finish onebot interface 2024-05-30 22:50:58 +08:00
97 changed files with 18437 additions and 928 deletions

8
.gitignore vendored
View File

@ -7,3 +7,11 @@ __pycache__
*.pyc
*.pyd
*.pyi
scripts/*.ts
audio
images
logs
*.sklearn
config/*.story.yml
config/story.yml
model/*.pth

101
README.md
View File

@ -1,44 +1,69 @@
![LagrangeRagBot](https://socialify.git.ci/LSTM-Kirigaya/Lagrange.RagBot/image?description=1&descriptionEditable=%E7%94%A8%E4%BA%8E%E5%B0%86%20RAG%20%E9%A9%B1%E5%8A%A8%E7%9A%84%E8%BE%85%E5%8A%A9%E6%9C%BA%E5%99%A8%E4%BA%BA%E6%8E%A5%E5%85%A5%20QQ%20%E7%9A%84%E7%B3%BB%E7%BB%9F&font=Jost&forks=1&issues=1&language=1&logo=https%3A%2F%2Fpicx.zhimg.com%2F80%2Fv2-bdae55043d61d7bcfeeabead6e953959_1440w.jpeg&name=1&pattern=Circuit%20Board&pulls=1&stargazers=1&theme=Light)
![Lagrange.RagBot](https://socialify.git.ci/LSTM-Kirigaya/Lagrange.RagBot/image?description=1&font=Jost&forks=1&issues=1&language=1&logo=https%3A%2F%2Fpicx.zhimg.com%2F0%2Fv2-bdae55043d61d7bcfeeabead6e953959_1440w.jpeg&name=1&pulls=1&stargazers=1&theme=Light)
<div align="center">
> 简体中文 < | <a href="https://www.google.com/search?q=best+website+to+learn+chinese&newwindow=1&sca_esv=a76695392a9980a7&rlz=1C1CHBD_zh-HKHK1072HK1072&sxsrf=ADLYWIKib09skMzGw8JqpTv2AWB6Xk8uZQ%3A1716986674152&ei=MiNXZsP3COrk2roPt-SrgA0&oq=best+website+to+learn+chin&gs_lp=Egxnd3Mtd2l6LXNlcnAiGmJlc3Qgd2Vic2l0ZSB0byBsZWFybiBjaGluKgIIADIIEAAYgAQYywEyCBAAGIAEGMsBMggQABiABBiiBDIIEAAYgAQYogQyCBAAGIAEGKIEMggQABiABBiiBDIIEAAYgAQYogRItDNQtgNY3C5wAXgAkAEFmAHMBKABlzaqAQwwLjE4LjUuNC4wLjK4AQPIAQD4AQGYAhigAosmwgINECMYgAQYsAMYJxiKBcICDBAAGIAEGLADGAoYDMICERAuGIAEGLADGMcBGMsBGK8BwgIHEAAYsAMYHsICCRAAGLADGAgYHsICBBAjGCfCAhAQLhiABBjRAxhDGMcBGIoFwgIFEAAYgATCAgsQABiABBixAxiDAcICExAuGIAEGNEDGMcBGMkDGAoYywHCAgsQABiABBiSAxiKBcICBhAAGAgYHsICIhAuGIAEGNEDGMcBGMkDGAoYywEYlwUY3AQY3gQY4ATYAQHCAhcQLhiABBjHARiYBRiZBRjLARieBRivAcICERAuGIAEGNEDGMcBGMkDGMsBwgImEC4YgAQYxwEYmAUYmQUYywEYngUYrwEYlwUY3AQY3gQY4ATYAQHCAgYQABgeGA-YAwCIBgGQBgq6BgYIARABGBSSBwoxLjE2LjUuMS4xoAf2rwE&sclient=gws-wiz-serp" target="_blank">English</a>
</div>
## 结构
## 环境搭建
每一个服务都放在 ./node 中,根据各自的 readme 分别配置。
你需要准备的环境变量:
```bash
git clone xxxxxx
npm install pm2 -g
yarn
pip install -r requirements.txt
export DEEPSEEK_API_TOKEN=xxx
export OMCP_DISCORD_SERVER_IP=xxx
export OMCP_DISCORD_SERVER_PORT=xxx
export OMCP_DISCORD_TOKEN=xxx
```
下载最新版本的 Lagrange.core ,并完成初始化。完成后根目录为
```
📁bot
📁rag
📁app
├─📁publish
│ ├─🏷️...
│ ├─🏷appsettings.json
│ └─🪛Lagrange.OneBot
└─🎗️...
```
---
## 架构
```mermaid
graph LR
a(拉格朗日 NTQQ server) <-->|http,ws| b(onebot layer)
graph TB
core(Lagrage.Core)
onebot(Lagrange.onebot)
vecdb(vecdb)
llm(LLM)
intent(intent tree)
c(vecdb) -->|http| b
d(LLM) -->|http| b
core(Lagrange.Core) --> onebot(Lagrange.onebot)
onebot -->|query| intent
intent -->|intent| onebot
subgraph Intent Recognition
intent -->|query| vecdb
vecdb -->|ktop| intent
intent -->|ktop,query| llm
llm -->|intent| intent
end
subgraph execution
onebot --> command{intent}
command --> query
command --> upload
command --> ...
end
subgraph third party
LLM
Google
server
end
query --> LLM
query --> Google
upload --> server
```
- `Lagrange.onebot` --> 📁bot
- `vecdb` --> 📁rag
- `intent tree` --> 📁prompt
---
## 接口规范
@ -65,3 +90,35 @@ interface CommonResponse<T> {
- 非必要,请不要随意宣传本项目。
- 虽然曾经无数个 QQ 相关的项目都死了,但是基本的 API 端口算是传承了下来。拉格朗日的返回类型,请参考 [go-cqhttp 帮助中心 - API 篇](https://docs.go-cqhttp.org/api/) 中的内容。
---
## 启动
```bash
# 1. 启动 拉格朗日
tsc
pm2 start dist/main.js --name Lagrange.onebot
pm2 start rag/main.py --name rag
```
---
## 测试命令
### 重训练 embedding -> intent 分类层
```bash
curl -X POST http://127.0.0.1:8081/intent/retrain-embedding-mapping
```
### 获取意图
```bash
curl -X POST -H "Content-Type: application/json" -d '{"query": "真的开线程是要tcl指令去改的"}' http://127.0.0.1:8081/intent/get-intent-recogition
```
### 获取向量数据库中的 topk
```bash
curl -X POST -H "Content-Type: application/json" -d '{"query": "这个插件有什么比较好的文档吗?"}' http://127.0.0.1:8081/vecdb/similarity_search_with_score
```

View File

@ -1,31 +0,0 @@
import { vecdbRequests as r } from './request';
interface CommonResponse<T> {
code: number,
data?: T,
msg?: string
}
type EmptyObject = Record<string, any>;
export const apiQueryVecdb = (req: apiQueryVecdbRequest) => r<CommonResponse<apiQueryVecdbData>>({
url: '/vecdb/similarity_search_with_score', method: 'POST',
data: req
});
export interface apiQueryVecdbRequest {
query: string,
k?: number
}
export interface apiQueryVecdbDataItem {
content: string,
score: number,
source: string,
meta: {
source: string,
start_index: number
}
}
export type apiQueryVecdbData = apiQueryVecdbDataItem[];

View File

@ -1,85 +0,0 @@
import assert from 'assert';
import type * as Lagrange from './type';
type PrivateUserInvoker = (message: Lagrange.PrivateMessage) => void;
type GroupUserInvoker = (message: Lagrange.GroupMessage) => void;
type MessageInvoker = PrivateUserInvoker | GroupUserInvoker;
interface CustomDescriptor<T extends MessageInvoker> {
value?: T;
configurable?: boolean;
enumerable?: boolean;
writable?: boolean;
get?(): any;
set?(v: any): void;
}
interface MessageInvokerStorage<T extends MessageInvoker> {
invoker: T;
config: Partial<Lagrange.CommonMessage>
}
class LagrangeMapper {
private _privateUserStorage: Map<number, MessageInvokerStorage<PrivateUserInvoker>>;
private _groupStorage: Map<number, MessageInvokerStorage<GroupUserInvoker>>;
constructor() {
this._privateUserStorage = new Map<number, MessageInvokerStorage<PrivateUserInvoker>>();
this._groupStorage = new Map<number, MessageInvokerStorage<GroupUserInvoker>>();
}
get privateUserStorage() {
return this._privateUserStorage;
}
get groupStorage() {
return this._groupStorage;
}
public matchPrivateUser(message: Lagrange.PrivateMessage) {
const user_id = message.user_id;
const userStorage = this._privateUserStorage.get(user_id);
if (userStorage) {
userStorage.invoker(message);
}
}
public onPrivateUser(config: Partial<Lagrange.CommonMessage>) {
assert(config.user_id, 'onPrivateUser 中 user_id 不能为空');
const _this = this;
return function(target: any, propertyKey: string, descriptor: CustomDescriptor<PrivateUserInvoker>) {
const user_id = config.user_id;
if (_this.privateUserStorage.has(user_id)) {
console.warn(`${propertyKey} -> 用户 ${user_id} 已经被注册过了,该操作将覆盖原本的!`);
}
const invoker = descriptor.value;
_this.privateUserStorage.set(user_id, { invoker, config });
}
}
public onGroupUser(config: Partial<Lagrange.CommonMessage>) {
assert(config.user_id, 'onGroupUser 中 user_id 不能为空');
assert(config.group_id, 'onGroupUser 中 group_id 不能为空');
const _this = this;
return function(target: any, propertyKey: string, descriptor: CustomDescriptor<GroupUserInvoker>) {
}
}
public onGroup(config: Partial<Lagrange.CommonMessage>) {
assert(config.group_id, 'onGroup 中 group_id 不能为空');
const _this = this;
return function(target: any, propertyKey: string, descriptor: CustomDescriptor<GroupUserInvoker>) {
const group_id = config.group_id;
if (_this.groupStorage.has(group_id)) {
console.warn(`${propertyKey} -> 群 ${group_id} 已经被注册过了,该操作将覆盖原本的!`);
}
const invoker = descriptor.value;
_this.groupStorage.set(group_id, { invoker, config });
}
}
}
const lagMapper = new LagrangeMapper();
export default lagMapper;

View File

@ -1,35 +0,0 @@
import * as fs from 'fs';
import WebSocket from 'ws';
import { onMessage, onClose } from './event';
import { apiQueryVecdb } from './api/vecdb';
const lagrangeBuffer = fs.readFileSync('./app/publish/appsettings.json', 'utf-8');
const lagrangeConfig = JSON.parse(lagrangeBuffer);
const impl = lagrangeConfig.Implementations[0];
const connectionParam = {
host: impl.Host,
port: impl.Port,
path: impl.Suffix
};
const socket = new WebSocket.Server(connectionParam);
socket.on('connection', (ws: WebSocket) => {
console.log('完成 ws 连接,启动参数如下');
console.table(connectionParam);
ws.on('message', onMessage);
ws.on('close', onClose);
const testMsg = {
action: 'send_private_msg',
params: {
user_id: 1193466151,
message: '你好'
}
}
ws.send(JSON.stringify(testMsg));
});

47
config.yaml Normal file
View File

@ -0,0 +1,47 @@
towardsdatascience:
route: /get-news-from-towards-science
prompt:
请通过 headless 的方式帮我搜集 https://towardsdatascience.com/tag/editors-pick/ 最热门的前三条信息,这些信息大概率在 main 元素的 ul li 下面。
请一步步寻找,先罗列所有元素剔除了 css class 后的 html 源代码,过滤可能是广告的元素后然后再从元素里面找到符合要求数量的有效元素。不要在第一步的时候就盲目获取元素的属性。
然后帮我进入这些网站后总结相关信息,文章通常也在 <main> 中,返回翻译好的简体中文,并且整理成一个简单的资讯。
按以下格式输出(严格禁止添加任何额外引导语或总结句):
[用一句吸引人的话开头,例如:"⌨️ 今日份 AI & CS 技术文章分享"]
📌 [翻译成简体中文的文章标题]
摘要:[文章的核心观点100字以内]
作者:[若存在]
发布时间:[若存在]
链接:[文章URL]
mcp:
command: node ~/project/Lagrange.RagBot/node/servers/my-browser/dist/browser.js
settings:
systemPrompt: 'you are a clever bot'
hacknews:
route: /get-news-from-hack-news
prompt:
请通过 headless 的方式帮我搜集 https://news.ycombinator.com/ 最热门的前三条信息。
请一步步寻找,先罗列所有元素剔除了 css class 后的 html 源代码,过滤可能是广告的元素后然后再从元素里面找到符合要求数量的有效元素。不要在第一步的时候就盲目获取元素的属性。
然后帮我进入这些网站后总结相关信息,返回翻译好的简体中文,并且整理成一个简单的资讯。
按以下格式输出(严格禁止添加任何额外引导语或总结句):
[用一句吸引人的话开头,例如:"⌨️ 今日份 AI & CS 技术文章分享"]
📌 [翻译成简体中文的文章标题]
摘要:[文章的核心观点100字以内]
作者:[若存在]
发布时间:[若存在]
链接:[文章URL]`.
mcp:
command: node ~/project/Lagrange.RagBot/node/servers/my-browser/dist/browser.js
settings:
systemPrompt: 'you are a clever bot'

View File

@ -0,0 +1,2 @@
addr: 127.0.0.1
port: 8082

0
node/.gitignore vendored Normal file
View File

5
node/Lagrange.Core/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
*.json
*.db
*.png
*.OneBot
lagrange-0-db

View File

@ -0,0 +1,5 @@
```bash
wget -c https://github.com/LagrangeDev/Lagrange.Core/releases/download/nightly/Lagrange.OneBot_linux-x64_net9.0_SelfContained.tar.gz
tar -xvf Lagrange.OneBot_linux-x64_net9.0_SelfContained.tar.gz && rm *.tar.gz
```

1
node/bot/README.md Normal file
View File

@ -0,0 +1 @@
执行 mcp 部分的模块

View File

@ -0,0 +1,2 @@
addr: 127.0.0.1
port: 8081

4598
node/bot/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -10,17 +10,33 @@
"scripts": {
"build": "tsc",
"start": "serve dist",
"serve": "tsc -w & serve dist"
"serve": "tsc -w & serve dist",
"test": "mocha"
},
"dependencies": {
"@ptkdev/logger": "^1.8.0",
"@types/node": "^20.12.12",
"axios": "^1.7.2",
"fs": "^0.0.1-security",
"json-bigint": "^1.0.0",
"lagrange.onebot": "^1.0.8",
"node-schedule": "^2.1.1",
"ws": "^8.17.0",
"yaml": "^2.4.2"
},
"devDependencies": {
"@sinonjs/referee-sinon": "^12.0.0",
"@types/node-schedule": "^2.1.7",
"@types/ws": "^8.5.10",
"chai": "4.3.4",
"jsverify": "^0.8.4",
"knuth-shuffle": "^1.0.8",
"mocha": "^10.4.0",
"require-uncached": "^2.0.0",
"serve": "^14.2.3",
"shelljs": "^0.8.5",
"should": "^13.2.3",
"sinon": "^18.0.0",
"typescript": "^5.4.5"
}
}

1
node/bot/src/README.md Normal file
View File

@ -0,0 +1 @@

71
node/bot/src/api/llm.ts Normal file
View File

@ -0,0 +1,71 @@
// 配置大模型 请求参数
import axios from "axios";
import { logger } from "lagrange.onebot";
interface LlmMessageItem {
role: 'user' | 'assistant',
content: string
}
// 已经废弃
class ErineLLM {
apiKey: string = process.env.BAIDU_API_KEY;
secretKey: string = process.env.BAIDU_SECRET_KEY;
accessToken: string = '';
constructor() {
if (this.apiKey === '') {
throw Error('百度 api_key 为空');
}
if (this.secretKey === '') {
throw Error('百度 secret_key 为空');
}
// this.getAccessToken();
}
public async getAccessToken() {
const url = `https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=${this.apiKey}&client_secret=${this.secretKey}`;
const payload = "";
const headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
};
const { data } = await axios.post(url, payload, { headers });
const accessToken = data.access_token;
if (accessToken) {
this.accessToken = accessToken;
logger.info('成功获取大模型访问令牌');
} else {
logger.error('大模型令牌获取失败:' + JSON.stringify(data))
}
}
public async answer(message: LlmMessageItem[]): Promise<string | undefined> {
if (message.length % 2 === 0) {
logger.error('大模型的 message 长度必须是奇数,目前为 ' + message.length);
return undefined;
}
const url = 'https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/ernie-lite-8k?access_token=' + this.accessToken;
const payload = {
messages: message,
system: '你是锦恢开发的用于进行问答的 QA 机器人。主要负责解决 Digital IDE 用户群的答疑问题'
}
const headers = {
'Content-Type': 'application/json'
}
const { data } = await axios.post(url, payload, { headers });
const result = data.result;
if (result) {
return result;
} else {
logger.error('大模型返回结果有误: ' + JSON.stringify(data));
return undefined;
}
}
}
export const llm = new ErineLLM();

View File

@ -4,6 +4,8 @@ import * as fs from 'fs';
import * as yaml from 'yaml';
import axios from 'axios';
// 配置 向量数据库 的请求参数
// 得到配置文件,组装基础路由
const vecdbBuffer = fs.readFileSync('./config/vecdb.yml', 'utf-8');
const vecdbConfig = yaml.parse(vecdbBuffer);
@ -37,6 +39,7 @@ vecdbRequests.interceptors.response.use(
}
);
export {
vecdbRequests,

57
node/bot/src/api/vecdb.ts Normal file
View File

@ -0,0 +1,57 @@
import { vecdbRequests as r } from './request';
interface CommonResponse<T> {
code: number,
data?: T,
msg?: string
}
type EmptyObject = Record<string, any>;
export const apiQueryVecdb = (req: apiQueryVecdbRequest) => r<CommonResponse<apiQueryVecdbData>>({
url: '/vecdb/similarity_search_with_score', method: 'POST',
data: req
});
export const apiGetIntentRecogition = (req: apiGetIntentRecogitionRequest) => r<CommonResponse<apiGetIntentRecogitionData>>({
url: '/intent/get-intent-recogition', method: 'POST',
data: req
});
export const apiIntentRetrain = (req: apiIntentRetrainRequest) => r<CommonResponse<apiIntentRetrainData>>({
url: '/intent/retrain-embedding-mapping', method: 'POST',
});
export interface apiQueryVecdbRequest {
query: string,
k?: number
}
export interface apiQueryVecdbDataItem {
content: string,
score: number,
source: string,
meta: {
source: string,
start_index: number
}
}
export type apiQueryVecdbData = apiQueryVecdbDataItem[];
export interface apiGetIntentRecogitionRequest {
query: string
}
export interface apiGetIntentRecogitionData {
id: number,
name: string,
uncertainty: number
}
export interface apiIntentRetrainRequest {
}
export type apiIntentRetrainData = string;

18
node/bot/src/global.ts Normal file
View File

@ -0,0 +1,18 @@
export const qq_groups = {
/**
* OpenMCP正式级技术组
*/
OPENMCP_DEV: 782833642,
/**
* Tiphereth
*/
TEST_CHANNEL: 1046693162
};
export const qq_users = {
/**
* dayo !
*/
JIN_HUI: 1193466151
};

37
node/bot/src/main.ts Normal file
View File

@ -0,0 +1,37 @@
import * as fs from 'fs';
import { server } from 'lagrange.onebot';
import { TestChannel } from './test-channel/test-channel.controller';
import { OpenMcpChannel } from './openmcp-dev/openmcp-dev.controller';
import { qq_users } from './global';
// 注册的模块
export const InstallChannels = [
TestChannel,
OpenMcpChannel
];
server.onMounted(c => {
c.sendPrivateMsg(qq_users.JIN_HUI, '成功上线');
});
server.onUnmounted(c => {
c.sendPrivateMsg(qq_users.JIN_HUI, '成功下线');
});
const buffer = fs.readFileSync('../Lagrange.Core/appsettings.json', 'utf-8');
const config = JSON.parse(buffer);
const impl = config.Implementations[0];
server.run({
host: impl.Host,
port: impl.Port,
path: impl.Suffix,
qq: 1542544558
});
InstallChannels.forEach(channel => {
console.log('上线模块:' + channel.name);
});

View File

@ -0,0 +1,97 @@
import '../plugins/image';
import * as path from 'path';
import { mapper, plugins, LagrangeContext, PrivateMessage, GroupMessage, Send, logger, ApproveMessage, Message } from 'lagrange.onebot';
import { getNews, publishOpenMCP } from './openmcp-dev.service';
import { qq_groups, qq_users } from '../global';
import { parseCommand, sendMessageToDiscord } from '../util';
import axios from 'axios';
console.log('activate ' + path.basename(__filename));
const visitCache = new Map<string, number>();
export class OpenMcpChannel {
@mapper.onGroup(qq_groups.OPENMCP_DEV, { onlyAdmin: true })
async handleTestChannel(c: LagrangeContext<GroupMessage>) {
const text = c.getRawText();
const commandResult = parseCommand(text);
if (commandResult !== undefined) {
// 校验身份
if (c.message.user_id === qq_users.JIN_HUI) {
const now = Date.now();
const lastVisit = visitCache.get(c.message.user_id.toString());
const info = await c.getGroupMemberInfo(c.message.group_id, c.message.user_id);
const role = info['data'].role;
const name = info['data'].nickname;
if (!lastVisit || (now - lastVisit) > 10 * 60 * 1000) {
c.sendMessage('检测到超级管理员TIP 系统允许访问,正在执行 ' + JSON.stringify(commandResult));
visitCache.set(c.message.user_id.toString(), now);
}
}
const { command, args } = commandResult;
switch (command) {
case 'news':
await getNews(c);
break;
case 'ping':
c.sendMessage('ping');
break;
case 'file':
if (args.length === 0) {
c.sendMessage('usage\n:file filename');
} else {
const filename = args[0];
const file = path.join('/home/kirigaya/download/', filename);
c.uploadGroupFile(c.message.group_id, file, filename);
}
break;
case 'notice':
c.sendGroupNotice(c.message.group_id, 'hello world');
break;
case 'get-notice':
const notice = await c.getGroupNotice(c.message.group_id);
c.sendMessage('当前的公告内容为:' + JSON.stringify(notice, null, 2));
break;
case 'pub':
await publishOpenMCP(c);
break;
case 'discord':
await sendMessageToDiscord('hello from qq');
break;
default:
break;
}
}
}
@mapper.onGroupIncrease(qq_groups.OPENMCP_DEV)
async handleTestChannelIncrease(c: LagrangeContext<ApproveMessage>) {
console.log(c.message);
// c.setGroupAddRequest('', c.message.sub_type, true, '');
}
@mapper.createTimeSchedule('0 0 10 * * *')
async handleTestChannelSchedule(c: LagrangeContext<Message>) {
const res = await axios.post('http://localhost:3000/get-news-from-hack-news');
const data = res.data;
const message = data.msg;
c.sendGroupMsg(qq_groups.OPENMCP_DEV, message);
}
}

View File

@ -0,0 +1,32 @@
import '../plugins/image';
import axios from 'axios';
import { LagrangeContext, PrivateMessage, GroupMessage } from 'lagrange.onebot'
import path from 'path';
import { sendMessageToDiscord, wait } from '../util';
export async function getNews(c: LagrangeContext<PrivateMessage | GroupMessage>) {
const res = await axios.post('http://localhost:3000/get-news-from-hack-news');
const data = res.data;
const message = data.msg;
console.log('message', message);
c.sendMessage(message);
}
export async function publishOpenMCP(c: LagrangeContext<GroupMessage>) {
const res = await axios.post('http://localhost:3000/publish-openmcp-client');
const data = res.data;
const message = data.msg;
console.log('message', message);
const { vsix, content } = message;
await c.uploadGroupFile(c.message.group_id, vsix, path.basename(vsix));
await wait(2000);
await c.sendGroupNotice(c.message.group_id, content);
await sendMessageToDiscord(content);
}

View File

@ -0,0 +1,28 @@
import { plugins, LagrangeContext, Message } from 'lagrange.onebot';
export class Impl {
@plugins.register('wget-image')
async wgetImage(c: LagrangeContext<Message>) {
// 判断一下,只解析 message 类型的数据
if (c.message.post_type === 'message') {
const text = c.message.raw_message;
if (text.startsWith('\\wget-image')) {
const url = text.substring('\\wget-image'.length).trim();
c.sendMessage([
{
type: 'image',
data: {
file: url,
timeout: 10000
}
}
]);
// 插件中使用 finishSession 会让被装饰的事务函数不再被执行,直接结束对话
c.finishSession();
}
}
}
}

View File

@ -0,0 +1,48 @@
import '../plugins/image';
import { mapper, plugins, LagrangeContext, PrivateMessage, GroupMessage, Send } from 'lagrange.onebot'
import { apiGetIntentRecogition, apiQueryVecdb } from '../api/vecdb';
import { handleGroupIntent } from './intent';
let lastCall = undefined;
console.log('activate ' + __filename);
export class Impl {
@mapper.onGroup(932987873, { at: false })
async handleDigitalGroup(c: LagrangeContext<GroupMessage>) {
const texts = [];
const message = c.message;
for (const msg of message.message) {
if (msg.type === 'text') {
texts.push(msg.data.text);
}
}
const reply: Send.Default[] = [];
const axiosRes = await apiGetIntentRecogition({ query: texts.join('\n') });
const res = axiosRes.data;
if (res.code == 200) {
const intentResult = res.data;
// 如果 不确定性 太高,就将意图修改为
if (intentResult.uncertainty >= 0.33) {
intentResult.name = 'others';
}
const uncertainty = Math.round(intentResult.uncertainty * 1000) / 1000;
const intentDebug = `【意图: ${intentResult.name} 不确定度: ${uncertainty}`;
const anwser = await handleGroupIntent(c, intentResult);
if (anwser !== undefined) {
c.sendMessage(anwser + '\n' + intentDebug);
}
} else {
const now = Date.now();
if (lastCall === undefined || (now - lastCall) >= 60 * 10 * 1000) {
c.sendMessage('RAG 系统目前离线');
}
lastCall = Date.now();
}
}
}

View File

@ -0,0 +1,73 @@
import { mapper, plugins, LagrangeContext, PrivateMessage, GroupMessage, Send, logger } from 'lagrange.onebot'
import { llm } from '../api/llm';
import { apiQueryVecdb, apiQueryVecdbDataItem } from '../api/vecdb';
interface IntentResult {
id: number,
name: string,
uncertainty: number
}
function makePrompt(messages: apiQueryVecdbDataItem[]): string {
const texts = ['你是一个很聪明的AI下面是你的知识库和参考内容请根据参考内容回答上述的问题'];
for (const msg of messages) {
texts.push(msg.content);
}
return texts.join('\n');
}
async function useRagLLM(c: LagrangeContext<GroupMessage>, intentResult: IntentResult) {
const texts = [];
for (const msg of c.message.message) {
if (msg.type === 'text') {
texts.push(msg.data.text);
}
}
const query = texts.join(' ').trim();
if (query.length === 0) {
return undefined;
}
const { data } = await apiQueryVecdb({ query, k: 3 });
if (data.code === 200) {
const messages: apiQueryVecdbDataItem[] = data.data.filter(m => m.score <= 0.7);
if (messages.length === 0) {
return '未在数据库中检索到相关内容。';
} else {
const query = makePrompt(messages);
const res = await llm.answer([
{
role: 'user',
content: query
}
]);
if (typeof res === 'string') {
const links = messages.map(m => m.source);
const linkSet = new Set<string>(links);
const reference = ['参考链接:', ...linkSet].join('\n');
const anwser = res + '\n\n' + reference;
return anwser;
}
}
} else {
logger.error('apiQueryVecdb 接口访问失败: ' + JSON.stringify(data));
return undefined;
}
}
export async function handleGroupIntent(c: LagrangeContext<GroupMessage>, intentResult: IntentResult) {
switch (intentResult.name) {
case 'usage':
return await useRagLLM(c, intentResult);
break;
case 'bug':
return await useRagLLM(c, intentResult);
break;
case 'others':
break;
default:
break;
}
}

View File

@ -0,0 +1,38 @@
import { mapper, plugins, LagrangeContext, PrivateMessage, GroupMessage, Send, logger } from 'lagrange.onebot';
console.log('activate ' + __filename);
export class Impl {
@mapper.onGroup(782833642)
@plugins.use('echo')
@plugins.use('pm')
@plugins.use('wget-image')
async handleOpenMCP(c: LagrangeContext<GroupMessage>) {
const text = c.getRawText();
console.log('[receive] ' + text);
if (c.qq) {
}
if (text.startsWith(':')) {
const command = text.substring(1);
switch (command) {
case 'news':
break;
default:
break;
}
}
// c.sendMessage([{
// type: 'image',
// data: {
// file: 'file:///data/zhelonghuang/project/rag-llm/images/bird.png',
// timeout: 10000
// }
// }])
c.finishSession();
}
}

View File

@ -0,0 +1,106 @@
import '../plugins/image';
import axios from 'axios';
import { mapper, plugins, LagrangeContext, PrivateMessage, GroupMessage, Send, logger } from 'lagrange.onebot'
console.log('activate ' + __filename);
export class Impl {
@mapper.onPrivateUser(1193466151)
@plugins.use('echo')
@plugins.use('pm')
@plugins.use('wget-image')
async handleJinhui(c: LagrangeContext<PrivateMessage>) {
const text = c.getRawText();
console.log('[receive] ' + text);
if (text.startsWith(':')) {
const command = text.substring(1);
switch (command) {
case 'news':
await getNews(c);
break;
default:
break;
}
}
// c.sendMessage([{
// type: 'image',
// data: {
// file: 'file:///data/zhelonghuang/project/rag-llm/images/bird.png',
// timeout: 10000
// }
// }])
c.finishSession();
}
// @mapper.onGroup(956419963, { at: false })
// async handleTestGroup(c: LagrangeContext<GroupMessage>) {
// const texts = [];
// const message = c.message;
// for (const msg of message.message) {
// if (msg.type === 'text') {
// texts.push(msg.data.text);
// }
// }
// const reply: Send.Default[] = [];
// const axiosRes = await apiGetIntentRecogition({ query: texts.join('\n') });
// const res = axiosRes.data;
// if (res.code == 200) {
// const intentResult = res.data;
// // 如果 不确定性 太高,就将意图修改为
// if (intentResult.uncertainty >= 0.33) {
// intentResult.name = 'others';
// }
// const uncertainty = Math.round(intentResult.uncertainty * 1000) / 1000;
// const intentDebug = `【意图: ${intentResult.name} 不确定度: ${uncertainty}】`;
// const anwser = await handleGroupIntent(c, intentResult);
// if (anwser === undefined) {
// c.sendMessage('拒答' + '\n' + intentDebug);
// } else {
// c.sendMessage(anwser + '\n' + intentDebug);
// }
// } else {
// c.sendMessage('RAG 系统目前离线');
// }
// }
}
async function getNews(c: LagrangeContext<PrivateMessage>) {
const res = await axios.post('http://localhost:3000/get-news-from-towards-science');
const data = res.data;
const message = data.msg;
console.log('message', message);
c.sendMessage(message);
}
async function getFile(c: LagrangeContext<PrivateMessage>) {
c.sendMessage([
// {
// type: 'text',
// data: {
// file: 'file:///data/zhelonghuang/project/rag-llm/images/bird.png',
// timeout: 10000
// }
// }
])
}
async function publishOpenMCP(c: LagrangeContext<PrivateMessage>) {
const res = await axios.post('http://localhost:3000/get-news-from-towards-science');
const data = res.data;
const message = data.msg;
console.log('message', message);
c.sendMessage(message);
}

View File

@ -0,0 +1,121 @@
import '../plugins/image';
import * as path from 'path';
import { mapper, plugins, LagrangeContext, PrivateMessage, GroupMessage, Send, logger, ApproveMessage, Message } from 'lagrange.onebot';
import { getNews, publishOpenMCP } from './test-channel.service';
import { qq_groups, qq_users } from '../global';
import { parseCommand, sendMessageToDiscord } from '../util';
console.log('activate ' + path.basename(__filename));
const visitCache = new Map<string, number>();
export class TestChannel {
@mapper.onPrivateUser(qq_users.JIN_HUI)
@plugins.use('echo')
@plugins.use('pm')
@plugins.use('wget-image')
async handleJinhui(c: LagrangeContext<PrivateMessage>) {
const text = c.getRawText();
console.log('[receive] ' + text);
if (text.startsWith(':')) {
const command = text.substring(1);
switch (command) {
case 'news':
await getNews(c);
break;
case 'ping':
c.sendMessage('ping');
break;
default:
break;
}
}
c.finishSession();
}
@mapper.onGroup(qq_groups.TEST_CHANNEL, { onlyAdmin: true })
async handleTestChannel(c: LagrangeContext<GroupMessage>) {
const text = c.getRawText();
const commandResult = parseCommand(text);
if (commandResult !== undefined) {
// 校验身份
if (c.message.user_id === qq_users.JIN_HUI) {
const now = Date.now();
const lastVisit = visitCache.get(c.message.user_id.toString());
const info = await c.getGroupMemberInfo(c.message.group_id, c.message.user_id);
const role = info['data'].role;
const name = info['data'].nickname;
if (!lastVisit || (now - lastVisit) > 10 * 60 * 1000) {
c.sendMessage('检测到超级管理员TIP 系统允许访问,正在执行 ' + JSON.stringify(commandResult));
visitCache.set(c.message.user_id.toString(), now);
}
}
const { command, args } = commandResult;
switch (command) {
case 'news':
await getNews(c);
break;
case 'ping':
c.sendMessage('ping');
break;
case 'file':
if (args.length === 0) {
c.sendMessage('usage\n:file filename');
} else {
const filename = args[0];
const file = path.join('/home/kirigaya/download/', filename);
c.uploadGroupFile(c.message.group_id, file, filename);
}
break;
case 'notice':
c.sendGroupNotice(c.message.group_id, 'hello world');
break;
case 'get-notice':
const notice = await c.getGroupNotice(c.message.group_id);
c.sendMessage('当前的公告内容为:' + JSON.stringify(notice, null, 2));
break;
case 'pub':
await publishOpenMCP(c);
break;
case 'discord':
await sendMessageToDiscord('hello from qq');
break;
default:
break;
}
}
}
@mapper.onGroupIncrease(qq_groups.TEST_CHANNEL)
async handleTestChannelIncrease(c: LagrangeContext<ApproveMessage>) {
console.log(c.message);
c.setGroupAddRequest('', c.message.sub_type, true, '');
}
@mapper.createTimeSchedule('0 11 20 * * *')
async handleTestChannelSchedule(c: LagrangeContext<Message>) {
c.sendPrivateMsg(qq_users.JIN_HUI, 'hello');
}
}

View File

@ -0,0 +1,32 @@
import '../plugins/image';
import axios from 'axios';
import { LagrangeContext, PrivateMessage, GroupMessage } from 'lagrange.onebot'
import path from 'path';
import { sendMessageToDiscord, wait } from '../util';
export async function getNews(c: LagrangeContext<PrivateMessage | GroupMessage>) {
const res = await axios.post('http://localhost:3000/get-news-from-hack-news');
const data = res.data;
const message = data.msg;
console.log('message', message);
c.sendMessage(message);
}
export async function publishOpenMCP(c: LagrangeContext<GroupMessage>) {
const res = await axios.post('http://localhost:3000/publish-openmcp-client');
const data = res.data;
const message = data.msg;
console.log('message', message);
const { vsix, content } = message;
await c.uploadGroupFile(c.message.group_id, vsix, path.basename(vsix));
await wait(2000);
await c.sendGroupNotice(c.message.group_id, content);
await sendMessageToDiscord(content);
}

53
node/bot/src/util.ts Normal file
View File

@ -0,0 +1,53 @@
import axios from "axios";
export function parseCommand(text: string) {
if (text.startsWith(':')) {
text = text.substring(1);
} else {
return;
}
const command = text.split(' ')[0];
const args = text.split(' ').slice(1);
return {
command,
args
}
}
export function wait(timeout: number) {
return new Promise(resolve => {
setTimeout(() => {
resolve(void 0);
}, timeout);
})
}
export async function sendMessageToDiscord(message: string) {
const ip = process.env['OMCP_DISCORD_SERVER_IP'];
const port = process.env['OMCP_DISCORD_SERVER_PORT'];
const token = process.env['OMCP_DISCORD_TOKEN'];
try {
const response = await axios({
method: 'post',
url: `http://${ip}:${port}/api/message`,
headers: {
'Authorization': token,
'User-Agent': 'Apifox/1.0.0 (https://apifox.com)',
'Content-Type': 'application/json',
'Accept': '*/*',
'Host': `${ip}:${port}`,
'Connection': 'keep-alive'
},
data: {
content: message,
channel: '1368185363549651107'
}
});
console.log('Response:', response.data);
} catch (error) {
console.error('Error:', error.response ? error.response.data : error.message);
}
}

View File

@ -4,9 +4,12 @@
"target": "ES2020",
"outDir": "dist",
"esModuleInterop": true,
"experimentalDecorators": true
"experimentalDecorators": true,
"declaration": true,
"declarationDir": "dist",
"typeRoots": ["./types"]
},
"include": [
"bot/**/*"
"src/**/*"
]
}

1
node/faas/README.md Normal file
View File

@ -0,0 +1 @@
执行 mcp 部分的模块

2429
node/faas/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

29
node/faas/package.json Normal file
View File

@ -0,0 +1,29 @@
{
"name": "faas",
"version": "1.0.0",
"description": "执行 mcp 部分的模块",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"body-parser": "^2.2.0",
"bson": "^6.10.3",
"cors": "^2.8.5",
"express": "^5.1.0",
"js-yaml": "^4.1.0",
"morgan": "^1.10.0",
"openmcp-sdk": "^0.0.4"
},
"devDependencies": {
"@types/cors": "^2.8.18",
"@types/express": "^5.0.1",
"@types/js-yaml": "^4.0.9",
"@types/morgan": "^1.9.9",
"@types/node": "^22.15.17",
"ts-node": "^10.9.2",
"typescript": "^5.8.3"
}
}

View File

@ -0,0 +1,287 @@
{
"messages": [
{
"role": "user",
"content": "请通过 headless 的方式帮我搜集 https://news.ycombinator.com/ 最热门的前三条信息。\n\n请一步步寻找先罗列所有元素剔除了 css class 后的 html 源代码,过滤可能是广告的元素后然后再从元素里面找到符合要求数量的有效元素。不要在第一步的时候就盲目获取元素的属性。\n\n然后帮我进入这些网站后总结相关信息返回翻译好的简体中文并且整理成一个简单的资讯。\n按以下格式输出严格禁止添加任何额外引导语或总结句\n\n[用一句吸引人的话开头,例如:\"⌨️ 今日份 AI & CS 技术文章分享\"]\n\n📌 [翻译成简体中文的文章标题]\n摘要[文章的核心观点100字以内]\n作者[若存在]\n发布时间[若存在]\n链接[文章URL]",
"extraInfo": {
"created": 1747073959697,
"state": "success",
"serverName": "deepseek"
}
},
{
"role": "assistant",
"content": "",
"tool_calls": [
{
"id": "call_0_a0f34b9b-d93a-437d-af28-bf96d68555f3",
"index": 0,
"type": "function",
"function": {
"name": "k_navigate",
"arguments": "{\"url\":\"https://news.ycombinator.com/\",\"launchOptions\":{\"headless\":true}}"
}
}
],
"extraInfo": {
"created": 1747073967399,
"state": "success",
"serverName": "deepseek"
}
},
{
"role": "tool",
"index": 0,
"tool_call_id": "call_0_a0f34b9b-d93a-437d-af28-bf96d68555f3",
"content": [
{
"type": "error",
"text": "McpError: MCP error -32603: Navigation timeout of 30000 ms exceeded"
}
],
"extraInfo": {
"created": 1747073997731,
"state": "tool call failed",
"serverName": "deepseek",
"usage": {
"prompt_tokens": 1071,
"completion_tokens": 36,
"total_tokens": 1107,
"prompt_tokens_details": {
"cached_tokens": 1024
},
"prompt_cache_hit_tokens": 1024,
"prompt_cache_miss_tokens": 47
}
}
},
{
"role": "assistant",
"content": "",
"tool_calls": [
{
"id": "call_0_0e73ade3-e100-4f12-b3cd-505cf31a3e26",
"index": 0,
"type": "function",
"function": {
"name": "k_navigate",
"arguments": "{\"url\":\"https://news.ycombinator.com/\",\"launchOptions\":{\"headless\":true,\"args\":[\"--no-sandbox\"]},\"allowDangerous\":true}"
}
}
],
"extraInfo": {
"created": 1747074003265,
"state": "success",
"serverName": "deepseek"
}
},
{
"role": "tool",
"index": 0,
"tool_call_id": "call_0_0e73ade3-e100-4f12-b3cd-505cf31a3e26",
"content": [
{
"type": "error",
"text": "McpError: MCP error -32603: Navigation timeout of 30000 ms exceeded"
}
],
"extraInfo": {
"created": 1747074034143,
"state": "tool call failed",
"serverName": "deepseek",
"usage": {
"prompt_tokens": 1141,
"completion_tokens": 54,
"total_tokens": 1195,
"prompt_tokens_details": {
"cached_tokens": 1088
},
"prompt_cache_hit_tokens": 1088,
"prompt_cache_miss_tokens": 53
}
}
},
{
"role": "assistant",
"content": "很抱歉,我无法直接访问该网站以获取信息。您可以尝试手动访问 [Hacker News](https://news.ycombinator.com/) 并查看最热门的前三条信息,然后我可以帮助您整理和翻译相关内容。",
"extraInfo": {
"created": 1747074039413,
"state": "success",
"serverName": "deepseek",
"usage": {
"prompt_tokens": 1229,
"completion_tokens": 49,
"total_tokens": 1278,
"prompt_tokens_details": {
"cached_tokens": 1216
},
"prompt_cache_hit_tokens": 1216,
"prompt_cache_miss_tokens": 13
}
}
}
],
"settings": {
"temperature": 0.7,
"enableTools": [
{
"name": "k_navigate",
"description": "Navigate to a URL",
"inputSchema": {
"type": "object",
"properties": {
"url": {
"type": "string",
"description": "URL to navigate to"
},
"launchOptions": {
"type": "object",
"description": "PuppeteerJS LaunchOptions. Default null. If changed and not null, browser restarts. Example: { headless: true, args: ['--no-sandbox'] }"
},
"allowDangerous": {
"type": "boolean",
"description": "Allow dangerous LaunchOptions that reduce security. When false, dangerous args like --no-sandbox will throw errors. Default false."
}
},
"required": [
"url"
]
},
"enabled": true
},
{
"name": "k_screenshot",
"description": "Take a screenshot of the current page or a specific element",
"inputSchema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Name for the screenshot"
},
"selector": {
"type": "string",
"description": "CSS selector for element to screenshot"
},
"width": {
"type": "number",
"description": "Width in pixels (default: 800)"
},
"height": {
"type": "number",
"description": "Height in pixels (default: 600)"
}
},
"required": [
"name"
]
},
"enabled": true
},
{
"name": "k_click",
"description": "Click an element on the page",
"inputSchema": {
"type": "object",
"properties": {
"selector": {
"type": "string",
"description": "CSS selector for element to click"
}
},
"required": [
"selector"
]
},
"enabled": true
},
{
"name": "k_fill",
"description": "Fill out an input field",
"inputSchema": {
"type": "object",
"properties": {
"selector": {
"type": "string",
"description": "CSS selector for input field"
},
"value": {
"type": "string",
"description": "Value to fill"
}
},
"required": [
"selector",
"value"
]
},
"enabled": true
},
{
"name": "k_select",
"description": "Select an element on the page with Select tag",
"inputSchema": {
"type": "object",
"properties": {
"selector": {
"type": "string",
"description": "CSS selector for element to select"
},
"value": {
"type": "string",
"description": "Value to select"
}
},
"required": [
"selector",
"value"
]
},
"enabled": true
},
{
"name": "k_hover",
"description": "Hover an element on the page",
"inputSchema": {
"type": "object",
"properties": {
"selector": {
"type": "string",
"description": "CSS selector for element to hover"
}
},
"required": [
"selector"
]
},
"enabled": true
},
{
"name": "k_evaluate",
"description": "Execute JavaScript in the browser console",
"inputSchema": {
"type": "object",
"properties": {
"script": {
"type": "string",
"description": "JavaScript code to execute"
}
},
"required": [
"script"
]
},
"enabled": true
}
],
"systemPrompt": "",
"contextLength": 20
},
"mcp": {
"connectionType": "STDIO",
"command": "node",
"args": [
"~/project/Lagrange.RagBot/node/servers/my-browser/dist/browser.js"
]
}
}

View File

@ -0,0 +1,287 @@
{
"messages": [
{
"role": "user",
"content": "请通过 headless 的方式帮我搜集 https://news.ycombinator.com/ 最热门的前三条信息。\n\n请一步步寻找先罗列所有元素剔除了 css class 后的 html 源代码,过滤可能是广告的元素后然后再从元素里面找到符合要求数量的有效元素。不要在第一步的时候就盲目获取元素的属性。\n\n然后帮我进入这些网站后总结相关信息返回翻译好的简体中文并且整理成一个简单的资讯。\n按以下格式输出严格禁止添加任何额外引导语或总结句\n\n[用一句吸引人的话开头,例如:\"⌨️ 今日份 AI & CS 技术文章分享\"]\n\n📌 [翻译成简体中文的文章标题]\n摘要[文章的核心观点100字以内]\n作者[若存在]\n发布时间[若存在]\n链接[文章URL]",
"extraInfo": {
"created": 1747074265643,
"state": "success",
"serverName": "deepseek"
}
},
{
"role": "assistant",
"content": "",
"tool_calls": [
{
"id": "call_0_27a3e2af-e707-4eaf-909c-2fa9f1075667",
"index": 0,
"type": "function",
"function": {
"name": "k_navigate",
"arguments": "{\"url\":\"https://news.ycombinator.com/\",\"launchOptions\":{\"headless\":true}}"
}
}
],
"extraInfo": {
"created": 1747074272702,
"state": "success",
"serverName": "deepseek"
}
},
{
"role": "tool",
"index": 0,
"tool_call_id": "call_0_27a3e2af-e707-4eaf-909c-2fa9f1075667",
"content": [
{
"type": "error",
"text": "McpError: MCP error -32603: Navigation timeout of 30000 ms exceeded"
}
],
"extraInfo": {
"created": 1747074303017,
"state": "tool call failed",
"serverName": "deepseek",
"usage": {
"prompt_tokens": 1071,
"completion_tokens": 36,
"total_tokens": 1107,
"prompt_tokens_details": {
"cached_tokens": 1024
},
"prompt_cache_hit_tokens": 1024,
"prompt_cache_miss_tokens": 47
}
}
},
{
"role": "assistant",
"content": "",
"tool_calls": [
{
"id": "call_0_273aedd5-6187-403d-bb53-404b293360b7",
"index": 0,
"type": "function",
"function": {
"name": "k_navigate",
"arguments": "{\"url\":\"https://news.ycombinator.com/\",\"launchOptions\":{\"headless\":true,\"args\":[\"--no-sandbox\"]},\"allowDangerous\":true}"
}
}
],
"extraInfo": {
"created": 1747074308989,
"state": "success",
"serverName": "deepseek"
}
},
{
"role": "tool",
"index": 0,
"tool_call_id": "call_0_273aedd5-6187-403d-bb53-404b293360b7",
"content": [
{
"type": "error",
"text": "McpError: MCP error -32603: Navigation timeout of 30000 ms exceeded"
}
],
"extraInfo": {
"created": 1747074339889,
"state": "tool call failed",
"serverName": "deepseek",
"usage": {
"prompt_tokens": 1141,
"completion_tokens": 54,
"total_tokens": 1195,
"prompt_tokens_details": {
"cached_tokens": 1088
},
"prompt_cache_hit_tokens": 1088,
"prompt_cache_miss_tokens": 53
}
}
},
{
"role": "assistant",
"content": "很抱歉,我无法访问该网站以获取所需的信息。可能是由于网站的反爬机制或其他技术限制导致无法加载页面。如果您有其他需求或可以尝试其他方法,请告诉我!",
"extraInfo": {
"created": 1747074347931,
"state": "success",
"serverName": "deepseek",
"usage": {
"prompt_tokens": 1229,
"completion_tokens": 39,
"total_tokens": 1268,
"prompt_tokens_details": {
"cached_tokens": 1216
},
"prompt_cache_hit_tokens": 1216,
"prompt_cache_miss_tokens": 13
}
}
}
],
"settings": {
"temperature": 0.7,
"enableTools": [
{
"name": "k_navigate",
"description": "Navigate to a URL",
"inputSchema": {
"type": "object",
"properties": {
"url": {
"type": "string",
"description": "URL to navigate to"
},
"launchOptions": {
"type": "object",
"description": "PuppeteerJS LaunchOptions. Default null. If changed and not null, browser restarts. Example: { headless: true, args: ['--no-sandbox'] }"
},
"allowDangerous": {
"type": "boolean",
"description": "Allow dangerous LaunchOptions that reduce security. When false, dangerous args like --no-sandbox will throw errors. Default false."
}
},
"required": [
"url"
]
},
"enabled": true
},
{
"name": "k_screenshot",
"description": "Take a screenshot of the current page or a specific element",
"inputSchema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Name for the screenshot"
},
"selector": {
"type": "string",
"description": "CSS selector for element to screenshot"
},
"width": {
"type": "number",
"description": "Width in pixels (default: 800)"
},
"height": {
"type": "number",
"description": "Height in pixels (default: 600)"
}
},
"required": [
"name"
]
},
"enabled": true
},
{
"name": "k_click",
"description": "Click an element on the page",
"inputSchema": {
"type": "object",
"properties": {
"selector": {
"type": "string",
"description": "CSS selector for element to click"
}
},
"required": [
"selector"
]
},
"enabled": true
},
{
"name": "k_fill",
"description": "Fill out an input field",
"inputSchema": {
"type": "object",
"properties": {
"selector": {
"type": "string",
"description": "CSS selector for input field"
},
"value": {
"type": "string",
"description": "Value to fill"
}
},
"required": [
"selector",
"value"
]
},
"enabled": true
},
{
"name": "k_select",
"description": "Select an element on the page with Select tag",
"inputSchema": {
"type": "object",
"properties": {
"selector": {
"type": "string",
"description": "CSS selector for element to select"
},
"value": {
"type": "string",
"description": "Value to select"
}
},
"required": [
"selector",
"value"
]
},
"enabled": true
},
{
"name": "k_hover",
"description": "Hover an element on the page",
"inputSchema": {
"type": "object",
"properties": {
"selector": {
"type": "string",
"description": "CSS selector for element to hover"
}
},
"required": [
"selector"
]
},
"enabled": true
},
{
"name": "k_evaluate",
"description": "Execute JavaScript in the browser console",
"inputSchema": {
"type": "object",
"properties": {
"script": {
"type": "string",
"description": "JavaScript code to execute"
}
},
"required": [
"script"
]
},
"enabled": true
}
],
"systemPrompt": "",
"contextLength": 20
},
"mcp": {
"connectionType": "STDIO",
"command": "node",
"args": [
"~/project/Lagrange.RagBot/node/servers/my-browser/dist/browser.js"
]
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,563 @@
{
"messages": [
{
"role": "user",
"content": "请通过 headless 的方式帮我搜集 https://news.ycombinator.com/ 最热门的前三条信息。\n\n请一步步寻找先罗列所有元素剔除了 css class 后的 html 源代码,过滤可能是广告的元素后然后再从元素里面找到符合要求数量的有效元素。不要在第一步的时候就盲目获取元素的属性。\n\n然后帮我进入这些网站后总结相关信息返回翻译好的简体中文并且整理成一个简单的资讯。\n按以下格式输出严格禁止添加任何额外引导语或总结句\n\n[用一句吸引人的话开头,例如:\"⌨️ 今日份 AI & CS 技术文章分享\"]\n\n📌 [翻译成简体中文的文章标题]\n摘要[文章的核心观点100字以内]\n作者[若存在]\n发布时间[若存在]\n链接[文章URL]",
"extraInfo": {
"created": 1747101600320,
"state": "success",
"serverName": "deepseek"
}
},
{
"role": "assistant",
"content": "",
"tool_calls": [
{
"id": "call_0_d1b875c1-5a16-4136-a2f4-53648127051f",
"index": 0,
"type": "function",
"function": {
"name": "k_navigate",
"arguments": "{\"url\":\"https://news.ycombinator.com/\",\"launchOptions\":{\"headless\":true}}"
}
}
],
"extraInfo": {
"created": 1747101606670,
"state": "success",
"serverName": "deepseek"
}
},
{
"role": "tool",
"index": 0,
"tool_call_id": "call_0_d1b875c1-5a16-4136-a2f4-53648127051f",
"content": [
{
"type": "error",
"text": "McpError: MCP error -32603: net::ERR_CONNECTION_RESET at https://news.ycombinator.com/"
}
],
"extraInfo": {
"created": 1747101616953,
"state": "tool call failed",
"serverName": "deepseek",
"usage": {
"prompt_tokens": 1071,
"completion_tokens": 36,
"total_tokens": 1107,
"prompt_tokens_details": {
"cached_tokens": 1024
},
"prompt_cache_hit_tokens": 1024,
"prompt_cache_miss_tokens": 47
}
}
},
{
"role": "assistant",
"content": "",
"tool_calls": [
{
"id": "call_0_16d05805-8a72-4fb2-b002-6acc27237b94",
"index": 0,
"type": "function",
"function": {
"name": "k_navigate",
"arguments": "{\"url\":\"https://news.ycombinator.com/\",\"launchOptions\":{\"headless\":true,\"args\":[\"--no-sandbox\"]},\"allowDangerous\":true}"
}
}
],
"extraInfo": {
"created": 1747101623731,
"state": "success",
"serverName": "deepseek"
}
},
{
"role": "tool",
"index": 0,
"tool_call_id": "call_0_16d05805-8a72-4fb2-b002-6acc27237b94",
"content": [
{
"type": "text",
"text": "Navigated to https://news.ycombinator.com/"
}
],
"extraInfo": {
"created": 1747101628493,
"state": "success",
"serverName": "deepseek",
"usage": {
"prompt_tokens": 1150,
"completion_tokens": 54,
"total_tokens": 1204,
"prompt_tokens_details": {
"cached_tokens": 1088
},
"prompt_cache_hit_tokens": 1088,
"prompt_cache_miss_tokens": 62
}
}
},
{
"role": "assistant",
"content": "",
"tool_calls": [
{
"id": "call_0_c1b1ae98-17cd-4ef6-b4a7-5209e80c7695",
"index": 0,
"type": "function",
"function": {
"name": "k_evaluate",
"arguments": "{\"script\":\"document.documentElement.outerHTML\"}"
}
}
],
"extraInfo": {
"created": 1747101633965,
"state": "success",
"serverName": "deepseek"
}
},
{
"role": "tool",
"index": 0,
"tool_call_id": "call_0_c1b1ae98-17cd-4ef6-b4a7-5209e80c7695",
"content": [
{
"type": "text",
"text": "Execution result:\n\"<html><head></head><body></body></html>\"\n\nConsole output:\n"
}
],
"extraInfo": {
"created": 1747101634989,
"state": "success",
"serverName": "deepseek",
"usage": {
"prompt_tokens": 1230,
"completion_tokens": 25,
"total_tokens": 1255,
"prompt_tokens_details": {
"cached_tokens": 1152
},
"prompt_cache_hit_tokens": 1152,
"prompt_cache_miss_tokens": 78
}
}
},
{
"role": "assistant",
"content": "",
"tool_calls": [
{
"id": "call_0_4d783557-5cfc-428b-ae7c-8535ce181d57",
"index": 0,
"type": "function",
"function": {
"name": "k_evaluate",
"arguments": "{\"script\":\"document.body.outerHTML\"}"
}
}
],
"extraInfo": {
"created": 1747101640718,
"state": "success",
"serverName": "deepseek"
}
},
{
"role": "tool",
"index": 0,
"tool_call_id": "call_0_4d783557-5cfc-428b-ae7c-8535ce181d57",
"content": [
{
"type": "text",
"text": "Execution result:\n\"<body></body>\"\n\nConsole output:\n"
}
],
"extraInfo": {
"created": 1747101640724,
"state": "success",
"serverName": "deepseek",
"usage": {
"prompt_tokens": 1296,
"completion_tokens": 23,
"total_tokens": 1319,
"prompt_tokens_details": {
"cached_tokens": 1216
},
"prompt_cache_hit_tokens": 1216,
"prompt_cache_miss_tokens": 80
}
}
},
{
"role": "assistant",
"content": "",
"tool_calls": [
{
"id": "call_0_53fcd540-014c-4bc3-9ba6-15a9d5b65b3b",
"index": 0,
"type": "function",
"function": {
"name": "k_evaluate",
"arguments": "{\"script\":\"document.querySelectorAll('a.storylink').length\"}"
}
}
],
"extraInfo": {
"created": 1747101646038,
"state": "success",
"serverName": "deepseek"
}
},
{
"role": "tool",
"index": 0,
"tool_call_id": "call_0_53fcd540-014c-4bc3-9ba6-15a9d5b65b3b",
"content": [
{
"type": "text",
"text": "Execution result:\n0\n\nConsole output:\n"
}
],
"extraInfo": {
"created": 1747101646042,
"state": "success",
"serverName": "deepseek",
"usage": {
"prompt_tokens": 1352,
"completion_tokens": 28,
"total_tokens": 1380,
"prompt_tokens_details": {
"cached_tokens": 1280
},
"prompt_cache_hit_tokens": 1280,
"prompt_cache_miss_tokens": 72
}
}
},
{
"role": "assistant",
"content": "",
"tool_calls": [
{
"id": "call_0_7ec588aa-9a81-41ad-9d5e-1cab03e5e7ef",
"index": 0,
"type": "function",
"function": {
"name": "k_evaluate",
"arguments": "{\"script\":\"document.querySelectorAll('a').length\"}"
}
}
],
"extraInfo": {
"created": 1747101651231,
"state": "success",
"serverName": "deepseek"
}
},
{
"role": "tool",
"index": 0,
"tool_call_id": "call_0_7ec588aa-9a81-41ad-9d5e-1cab03e5e7ef",
"content": [
{
"type": "text",
"text": "Execution result:\n0\n\nConsole output:\n"
}
],
"extraInfo": {
"created": 1747101651236,
"state": "success",
"serverName": "deepseek",
"usage": {
"prompt_tokens": 1406,
"completion_tokens": 25,
"total_tokens": 1431,
"prompt_tokens_details": {
"cached_tokens": 1344
},
"prompt_cache_hit_tokens": 1344,
"prompt_cache_miss_tokens": 62
}
}
},
{
"role": "assistant",
"content": "",
"tool_calls": [
{
"id": "call_0_77caa460-fd9a-41a9-9db6-98c7d65bd59f",
"index": 0,
"type": "function",
"function": {
"name": "k_evaluate",
"arguments": "{\"script\":\"document.querySelectorAll('*').length\"}"
}
}
],
"extraInfo": {
"created": 1747101656646,
"state": "success",
"serverName": "deepseek"
}
},
{
"role": "tool",
"index": 0,
"tool_call_id": "call_0_77caa460-fd9a-41a9-9db6-98c7d65bd59f",
"content": [
{
"type": "text",
"text": "Execution result:\n3\n\nConsole output:\n"
}
],
"extraInfo": {
"created": 1747101656651,
"state": "success",
"serverName": "deepseek",
"usage": {
"prompt_tokens": 1457,
"completion_tokens": 25,
"total_tokens": 1482,
"prompt_tokens_details": {
"cached_tokens": 1408
},
"prompt_cache_hit_tokens": 1408,
"prompt_cache_miss_tokens": 49
}
}
},
{
"role": "assistant",
"content": "",
"tool_calls": [
{
"id": "call_0_379c9e3f-c525-4cde-965c-dea7635f116b",
"index": 0,
"type": "function",
"function": {
"name": "k_evaluate",
"arguments": "{\"script\":\"Array.from(document.querySelectorAll('*')).map(el => el.outerHTML).join('\\\\n')\"}"
}
}
],
"extraInfo": {
"created": 1747101663316,
"state": "success",
"serverName": "deepseek"
}
},
{
"role": "tool",
"index": 0,
"tool_call_id": "call_0_379c9e3f-c525-4cde-965c-dea7635f116b",
"content": [
{
"type": "text",
"text": "Execution result:\n\"<html><head></head><body></body></html>\\n<head></head>\\n<body></body>\"\n\nConsole output:\n"
}
],
"extraInfo": {
"created": 1747101663320,
"state": "success",
"serverName": "deepseek",
"usage": {
"prompt_tokens": 1508,
"completion_tokens": 39,
"total_tokens": 1547,
"prompt_tokens_details": {
"cached_tokens": 1472
},
"prompt_cache_hit_tokens": 1472,
"prompt_cache_miss_tokens": 36
}
}
},
{
"role": "assistant",
"content": "由于页面加载失败或内容未被正确渲染,无法获取 Hacker News 的热门信息。建议稍后重试或检查网络设置。",
"extraInfo": {
"created": 1747101669791,
"state": "success",
"serverName": "deepseek",
"usage": {
"prompt_tokens": 1600,
"completion_tokens": 30,
"total_tokens": 1630,
"prompt_tokens_details": {
"cached_tokens": 1536
},
"prompt_cache_hit_tokens": 1536,
"prompt_cache_miss_tokens": 64
}
}
}
],
"settings": {
"temperature": 0.7,
"enableTools": [
{
"name": "k_navigate",
"description": "Navigate to a URL",
"inputSchema": {
"type": "object",
"properties": {
"url": {
"type": "string",
"description": "URL to navigate to"
},
"launchOptions": {
"type": "object",
"description": "PuppeteerJS LaunchOptions. Default null. If changed and not null, browser restarts. Example: { headless: true, args: ['--no-sandbox'] }"
},
"allowDangerous": {
"type": "boolean",
"description": "Allow dangerous LaunchOptions that reduce security. When false, dangerous args like --no-sandbox will throw errors. Default false."
}
},
"required": [
"url"
]
},
"enabled": true
},
{
"name": "k_screenshot",
"description": "Take a screenshot of the current page or a specific element",
"inputSchema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Name for the screenshot"
},
"selector": {
"type": "string",
"description": "CSS selector for element to screenshot"
},
"width": {
"type": "number",
"description": "Width in pixels (default: 800)"
},
"height": {
"type": "number",
"description": "Height in pixels (default: 600)"
}
},
"required": [
"name"
]
},
"enabled": true
},
{
"name": "k_click",
"description": "Click an element on the page",
"inputSchema": {
"type": "object",
"properties": {
"selector": {
"type": "string",
"description": "CSS selector for element to click"
}
},
"required": [
"selector"
]
},
"enabled": true
},
{
"name": "k_fill",
"description": "Fill out an input field",
"inputSchema": {
"type": "object",
"properties": {
"selector": {
"type": "string",
"description": "CSS selector for input field"
},
"value": {
"type": "string",
"description": "Value to fill"
}
},
"required": [
"selector",
"value"
]
},
"enabled": true
},
{
"name": "k_select",
"description": "Select an element on the page with Select tag",
"inputSchema": {
"type": "object",
"properties": {
"selector": {
"type": "string",
"description": "CSS selector for element to select"
},
"value": {
"type": "string",
"description": "Value to select"
}
},
"required": [
"selector",
"value"
]
},
"enabled": true
},
{
"name": "k_hover",
"description": "Hover an element on the page",
"inputSchema": {
"type": "object",
"properties": {
"selector": {
"type": "string",
"description": "CSS selector for element to hover"
}
},
"required": [
"selector"
]
},
"enabled": true
},
{
"name": "k_evaluate",
"description": "Execute JavaScript in the browser console",
"inputSchema": {
"type": "object",
"properties": {
"script": {
"type": "string",
"description": "JavaScript code to execute"
}
},
"required": [
"script"
]
},
"enabled": true
}
],
"systemPrompt": "",
"contextLength": 20
},
"mcp": {
"connectionType": "STDIO",
"command": "node",
"args": [
"~/project/Lagrange.RagBot/node/servers/my-browser/dist/browser.js"
]
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,16 @@
import express, { Request, Response } from 'express';
import morgan from 'morgan';
import cors from 'cors';
const corsOptions = {
// 一些旧版浏览器(如 IE11、各种 SmartTV在 204 状态下会有问题
optionsSuccessStatus: 200
};
export const app = express();
export const testRouter = express.Router();
app.use(express.json());
app.use(cors(corsOptions));
app.use(morgan('dev'));
app.use('/test', testRouter);

16
node/faas/src/main.ts Normal file
View File

@ -0,0 +1,16 @@
import express, { Request, Response } from 'express';
import { app, testRouter } from './common';
import "./service/news";
import "./openmcp/openmcp";
// 路由为 localhost:3000
app.get('/', (req: Request, res: Response) => {
res.send('<h1>Hello, World!</h1><br><img src="https://picx.zhimg.com/v2-b4251de7d2499e942c7ebf447a90d2eb_l.jpg"/>');
});
// 运行在 3000 端口
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});

View File

@ -0,0 +1,54 @@
import { Request, Response } from "express";
import { app } from "../common";
import { executeCommand } from "../util";
import * as fs from 'fs';
import * as path from 'path';
import { execSync, spawnSync } from 'child_process';
import os from 'os';
export const OPENMCP_CLIENT = os.homedir() + '/project/openmcp-client';
async function publishOpenMCP() {
const updateResult = execSync('git pull origin main', { cwd: OPENMCP_CLIENT, env: process.env });
console.log(updateResult.toString());
const rmResult = execSync('rm *.vsix', { cwd: OPENMCP_CLIENT });
console.log(rmResult.toString());
const packageResult = execSync('vsce package', { cwd: OPENMCP_CLIENT });
console.log(packageResult.toString());
// 找到 OPENMCP_CLIENT 下的第一个 vsix 文件,返回绝对路径
const vsixFile = fs.readdirSync(OPENMCP_CLIENT).find(file => file.endsWith('.vsix'));
const vsixPath = path.join(OPENMCP_CLIENT, vsixFile);
return vsixPath;
}
function getLastChangeLog() {
const changelog = fs.readFileSync(path.join(OPENMCP_CLIENT, 'CHANGELOG.md'), { encoding: 'utf-8' });
const newContent = changelog.split('## [main]')[1];
const version = newContent.split('\n')[0];
const updateContent = newContent.split('\n').slice(1).join('\n');
const content = `✴️ openmcp client ${version} 更新内容\n\n` + updateContent.trim() + '\n\n在 vscode/trae/cursor 等编辑器的插件商城搜索【openmcp】就可以下载最新的版本了';
return content
}
app.post('/publish-openmcp-client', async (req: Request, res: Response) => {
try {
const filePath = await publishOpenMCP();
const updateContent = getLastChangeLog();
res.send({
code: 200,
msg: {
vsix: filePath,
content: updateContent
}
})
} catch (error) {
console.log(error);
res.send(error.toString());
}
})

View File

@ -0,0 +1,25 @@
import { TaskLoop } from 'openmcp-sdk/task-loop';
import { TaskLoopAdapter } from 'openmcp-sdk/service';
export async function createTaskContext() {
const adapter = new TaskLoopAdapter();
await adapter.connectMcpServer({
connectionType: 'STDIO',
command: 'node',
args: ['~/project/Lagrange.RagBot/node/servers/my-browser/dist/browser.js']
});
const taskLoop = new TaskLoop({ adapter });
taskLoop.setLlmConfig({
id: 'deepseek',
baseUrl: 'https://api.deepseek.com/v1',
userToken: process.env['DEEPSEEK_API_TOKEN'],
userModel: 'deepseek-chat'
});
return {
taskLoop, adapter
}
}

View File

@ -0,0 +1,32 @@
import * as fs from 'fs';
import * as yaml from 'js-yaml';
import * as path from 'path';
const CONFIG_PATH = path.join(__dirname, '../../../config.yaml');
interface McpConfig {
prompt: string;
mcp: {
command?: string;
url?: string;
oauth?: string;
};
settings: {
systemPrompt?: string;
};
}
export function getConfig() {
try {
const fileContent = fs.readFileSync(CONFIG_PATH, 'utf8');
return yaml.load(fileContent);
} catch (error) {
console.error('Error reading config file:', error);
throw error;
}
}
// 使用示例
// const config = getConfig();
// console.log(config.towardsdatascience.prompt);

View File

@ -0,0 +1,195 @@
import { Request, Response } from 'express';
import { app, testRouter } from "../common";
import { createTaskContext } from "./common";
import * as fs from 'fs';
import * as path from 'path';
import { markdownToPlainText, getFormatedTime } from '../util';
export async function getNewsFromTowardsScience() {
const { taskLoop, adapter } = await createTaskContext();
const tools = await adapter.listTools();
// 创建当前事件循环对应的上下文,并且配置当前上下文的设置
const storage = {
messages: [],
settings: {
temperature: 0.7,
enableTools: tools,
systemPrompt: '',
contextLength: 20
}
};
// 本次发出的问题
const message = `
headless https://towardsdatascience.com/tag/editors-pick/ 最热门的前三条信息,这些信息大概率在 main 元素的 ul li 下面。
css class html 广
<main>
["⌨️ 今日份 AI & CS 技术文章分享"]
📌 []
[100]
[]
[]
[URL]`.trim();
// 开启事件循环
await taskLoop.start(storage, message);
// 保存数据到本地
const filename = getFormatedTime();
const savePath = path.join(__dirname, '..', '..', 'runtime-data', 'get-news-from-towards-science', `${filename}.json`);
if (!fs.existsSync(path.dirname(savePath))) {
fs.mkdirSync(path.dirname(savePath));
}
fs.writeFileSync(savePath, JSON.stringify(
{
messages: storage.messages,
settings: storage.settings,
mcp: {
connectionType: 'STDIO',
command: 'node',
args: ['~/project/Lagrange.RagBot/node/servers/my-browser/dist/browser.js']
}
},
null, 2
));
const lastMessage = storage.messages.at(-1);
console.log('lastMessage', lastMessage);
if (lastMessage?.role === 'assistant') {
return markdownToPlainText(lastMessage.content);
}
return '';
}
export async function getNewsFromHackNews() {
const { taskLoop, adapter } = await createTaskContext();
const tools = await adapter.listTools();
// 创建当前事件循环对应的上下文,并且配置当前上下文的设置
const storage = {
messages: [],
settings: {
temperature: 0.7,
enableTools: tools,
systemPrompt: '',
contextLength: 20
}
};
// 本次发出的问题
const message = `
headless https://news.ycombinator.com/ 最热门的前三条信息。
css class html 广
["⌨️ 今日份 AI & CS 技术文章分享"]
📌 []
[100]
[]
[]
[URL]`.trim();
// 开启事件循环
await taskLoop.start(storage, message);
// 保存数据到本地
const filename = getFormatedTime();
const savePath = path.join(__dirname, '..', '..', 'runtime-data', 'get-news-from-towards-science', `${filename}.json`);
console.log('savePath', savePath);
if (!fs.existsSync(path.dirname(savePath))) {
fs.mkdirSync(path.dirname(savePath), { recursive: true });
}
fs.writeFileSync(savePath, JSON.stringify(
{
messages: storage.messages,
settings: storage.settings,
mcp: {
connectionType: 'STDIO',
command: 'node',
args: ['~/project/Lagrange.RagBot/node/servers/my-browser/dist/browser.js']
}
},
null, 2
));
console.log('write data to ' + savePath);
const lastMessage = storage.messages.at(-1);
console.log('lastMessage', lastMessage);
if (lastMessage?.role === 'assistant') {
return markdownToPlainText(lastMessage.content);
}
return '';
}
app.post('/get-news-from-towards-science', async (req: Request, res: Response) => {
try {
const news = await getNewsFromTowardsScience();
res.send({
code: 200,
msg: news
});
} catch (error) {
res.send(error);
}
});
app.post('/get-news-from-hack-news', async (req: Request, res: Response) => {
try {
const news = await getNewsFromHackNews();
res.send({
code: 200,
msg: news
});
} catch (error) {
res.send(error);
}
});
testRouter.post('/get-news-from-towards-science', async (req: Request, res: Response) => {
try {
res.send({
code: 200,
msg: `以下是整理好的咨询信息,并已翻译为简体中文:
1Uh-Uh, Not Guilty
Uh-Uh, Not Guilty
2Regression Discontinuity Design: How It Works and When to Use It
RDD使
Regression Discontinuity Design
`
});
} catch (error) {
res.send(error);
}
});

View File

@ -0,0 +1,7 @@
import { getNewsFromTowardsScience } from "../service/news";
async function main() {
const res = await getNewsFromTowardsScience();
}
main();

94
node/faas/src/util.ts Normal file
View File

@ -0,0 +1,94 @@
import { spawn, SpawnOptions, execSync } from 'child_process';
export function executeCommand(
command: string,
cwd?: string
): Promise<{ stdout: string; stderr: string; code: number | null }> {
return new Promise((resolve, reject) => {
// 自动拆分命令和参数(支持带引号的参数)
const [cmd, ...args] = command.match(/(?:[^\s"']+|['"][^'"]*['"])+/g) || [];
if (!cmd) {
reject(new Error('Empty command'));
return;
}
// 移除参数中的引号(如果存在)
const processedArgs = args.map(arg =>
arg.replace(/^['"](.*)['"]$/, '$1')
);
// 将 cwd 的 ~ 替换为用户的 home 目录
if (cwd && cwd.startsWith('~')) {
cwd = cwd.replace('~', process.env.HOME || '');
}
const options: SpawnOptions = {
cwd,
stdio: 'pipe',
shell: process.platform === 'win32',
env: {
...process.env, // 继承父进程所有环境变量
}
};
const child = spawn(cmd, processedArgs, options);
let stdout = '';
let stderr = '';
child.stdout?.on('data', (data) => {
stdout += data.toString();
});
child.stderr?.on('data', (data) => {
stderr += data.toString();
});
child.on('close', (code) => {
resolve({
stdout: stdout.trim(),
stderr: stderr.trim(),
code,
});
});
child.on('error', (error) => {
reject(error);
});
});
}
export function markdownToPlainText(markdown: string): string {
// 移除 Markdown 标题符号
let plainText = markdown.replace(/^#+\s*/gm, '');
// 移除 Markdown 链接格式
plainText = plainText.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1');
// 移除 Markdown 加粗和斜体符号
plainText = plainText.replace(/\*\*([^*]+)\*\*/g, '$1');
plainText = plainText.replace(/\*([^*]+)\*/g, '$1');
// 移除 Markdown 代码块符号
plainText = plainText.replace(/`([^`]+)`/g, '$1');
// 移除 Markdown 分割线
plainText = plainText.replace(/^-{3,}/gm, '');
// 移除多余的空行
plainText = plainText.replace(/\n{3,}/g, '\n\n');
return plainText.trim();
}
export function getFormatedTime() {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const seconds = String(now.getSeconds()).padStart(2, '0');
return `${year}_${month}_${day}_${hours}_${minutes}_${seconds}`;
}

15
node/faas/tsconfig.json Normal file
View File

@ -0,0 +1,15 @@
{
"compilerOptions": {
"module": "CommonJS",
"target": "ES2020",
"outDir": "dist",
"esModuleInterop": true,
"experimentalDecorators": true,
"declaration": true,
"declarationDir": "dist",
"typeRoots": ["./types"]
},
"include": [
"src/**/*"
]
}

1
node/servers/my-browser/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
node

View File

@ -0,0 +1,213 @@
# Puppeteer
A Model Context Protocol server that provides browser automation capabilities using Puppeteer. This server enables LLMs to interact with web pages, take screenshots, and execute JavaScript in a real browser environment.
## Components
### Tools
- **puppeteer_navigate**
- Navigate to any URL in the browser
- Inputs:
- `url` (string, required): URL to navigate to
- `launchOptions` (object, optional): PuppeteerJS LaunchOptions. Default null. If changed and not null, browser restarts. Example: `{ headless: true, args: ['--user-data-dir="C:/Data"'] }`
- `allowDangerous` (boolean, optional): Allow dangerous LaunchOptions that reduce security. When false, dangerous args like `--no-sandbox`, `--disable-web-security` will throw errors. Default false.
- **puppeteer_screenshot**
- Capture screenshots of the entire page or specific elements
- Inputs:
- `name` (string, required): Name for the screenshot
- `selector` (string, optional): CSS selector for element to screenshot
- `width` (number, optional, default: 800): Screenshot width
- `height` (number, optional, default: 600): Screenshot height
- **puppeteer_click**
- Click elements on the page
- Input: `selector` (string): CSS selector for element to click
- **puppeteer_hover**
- Hover elements on the page
- Input: `selector` (string): CSS selector for element to hover
- **puppeteer_fill**
- Fill out input fields
- Inputs:
- `selector` (string): CSS selector for input field
- `value` (string): Value to fill
- **puppeteer_select**
- Select an element with SELECT tag
- Inputs:
- `selector` (string): CSS selector for element to select
- `value` (string): Value to select
- **puppeteer_evaluate**
- Execute JavaScript in the browser console
- Input: `script` (string): JavaScript code to execute
### Resources
The server provides access to two types of resources:
1. **Console Logs** (`console://logs`)
- Browser console output in text format
- Includes all console messages from the browser
2. **Screenshots** (`screenshot://<name>`)
- PNG images of captured screenshots
- Accessible via the screenshot name specified during capture
## Key Features
- Browser automation
- Console log monitoring
- Screenshot capabilities
- JavaScript execution
- Basic web interaction (navigation, clicking, form filling)
- Customizable Puppeteer launch options
## Configuration to use Puppeteer Server
### Usage with Claude Desktop
Here's the Claude Desktop configuration to use the Puppeter server:
### Docker
**NOTE** The docker implementation will use headless chromium, where as the NPX version will open a browser window.
```json
{
"mcpServers": {
"puppeteer": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"--init",
"-e",
"DOCKER_CONTAINER=true",
"mcp/puppeteer"
]
}
}
}
```
### NPX
```json
{
"mcpServers": {
"puppeteer": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-puppeteer"]
}
}
}
```
### Usage with VS Code
For quick installation, use one of the one-click install buttons below...
[![Install with NPX in VS Code](https://img.shields.io/badge/VS_Code-NPM-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=puppeteer&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40modelcontextprotocol%2Fserver-puppeteer%22%5D%7D) [![Install with NPX in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-NPM-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=puppeteer&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40modelcontextprotocol%2Fserver-puppeteer%22%5D%7D&quality=insiders)
[![Install with Docker in VS Code](https://img.shields.io/badge/VS_Code-Docker-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=puppeteer&config=%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22-i%22%2C%22--rm%22%2C%22--init%22%2C%22-e%22%2C%22DOCKER_CONTAINER%3Dtrue%22%2C%22mcp%2Fpuppeteer%22%5D%7D) [![Install with Docker in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Docker-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=puppeteer&config=%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22-i%22%2C%22--rm%22%2C%22--init%22%2C%22-e%22%2C%22DOCKER_CONTAINER%3Dtrue%22%2C%22mcp%2Fpuppeteer%22%5D%7D&quality=insiders)
For manual installation, add the following JSON block to your User Settings (JSON) file in VS Code. You can do this by pressing `Ctrl + Shift + P` and typing `Preferences: Open User Settings (JSON)`.
Optionally, you can add it to a file called `.vscode/mcp.json` in your workspace. This will allow you to share the configuration with others.
> Note that the `mcp` key is not needed in the `.vscode/mcp.json` file.
For NPX installation (opens a browser window):
```json
{
"mcp": {
"servers": {
"puppeteer": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-puppeteer"]
}
}
}
}
```
For Docker installation (uses headless chromium):
```json
{
"mcp": {
"servers": {
"puppeteer": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"--init",
"-e",
"DOCKER_CONTAINER=true",
"mcp/puppeteer"
]
}
}
}
}
```
### Launch Options
You can customize Puppeteer's browser behavior in two ways:
1. **Environment Variable**: Set `PUPPETEER_LAUNCH_OPTIONS` with a JSON-encoded string in the MCP configuration's `env` parameter:
```json
{
"mcpServers": {
"mcp-puppeteer": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-puppeteer"],
"env": {
"PUPPETEER_LAUNCH_OPTIONS": "{ \"headless\": false, \"executablePath\": \"C:/Program Files/Google/Chrome/Application/chrome.exe\", \"args\": [] }",
"ALLOW_DANGEROUS": "true"
}
}
}
}
```
2. **Tool Call Arguments**: Pass `launchOptions` and `allowDangerous` parameters to the `puppeteer_navigate` tool:
```json
{
"url": "https://example.com",
"launchOptions": {
"headless": false,
"defaultViewport": { "width": 1280, "height": 720 }
}
}
```
## Build
Docker build:
```bash
docker build -t mcp/puppeteer -f src/puppeteer/Dockerfile .
```
## License
This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository.

View File

@ -0,0 +1,494 @@
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListResourcesRequestSchema,
ListToolsRequestSchema,
ReadResourceRequestSchema,
CallToolResult,
TextContent,
ImageContent,
Tool,
} from "@modelcontextprotocol/sdk/types.js";
import puppeteer, { Browser, Page } from "puppeteer";
// Define the tools once to avoid repetition
const TOOLS: Tool[] = [
{
name: "k_navigate",
description: "Navigate to a URL",
inputSchema: {
type: "object",
properties: {
url: { type: "string", description: "URL to navigate to" },
launchOptions: { type: "object", description: "PuppeteerJS LaunchOptions. Default null. If changed and not null, browser restarts. Example: { headless: true, args: ['--no-sandbox'] }" },
allowDangerous: { type: "boolean", description: "Allow dangerous LaunchOptions that reduce security. When false, dangerous args like --no-sandbox will throw errors. Default false." },
},
required: ["url"],
},
},
{
name: "k_screenshot",
description: "Take a screenshot of the current page or a specific element",
inputSchema: {
type: "object",
properties: {
name: { type: "string", description: "Name for the screenshot" },
selector: { type: "string", description: "CSS selector for element to screenshot" },
width: { type: "number", description: "Width in pixels (default: 800)" },
height: { type: "number", description: "Height in pixels (default: 600)" },
},
required: ["name"],
},
},
{
name: "k_click",
description: "Click an element on the page",
inputSchema: {
type: "object",
properties: {
selector: { type: "string", description: "CSS selector for element to click" },
},
required: ["selector"],
},
},
{
name: "k_fill",
description: "Fill out an input field",
inputSchema: {
type: "object",
properties: {
selector: { type: "string", description: "CSS selector for input field" },
value: { type: "string", description: "Value to fill" },
},
required: ["selector", "value"],
},
},
{
name: "k_select",
description: "Select an element on the page with Select tag",
inputSchema: {
type: "object",
properties: {
selector: { type: "string", description: "CSS selector for element to select" },
value: { type: "string", description: "Value to select" },
},
required: ["selector", "value"],
},
},
{
name: "k_hover",
description: "Hover an element on the page",
inputSchema: {
type: "object",
properties: {
selector: { type: "string", description: "CSS selector for element to hover" },
},
required: ["selector"],
},
},
{
name: "k_evaluate",
description: "Execute JavaScript in the browser console",
inputSchema: {
type: "object",
properties: {
script: { type: "string", description: "JavaScript code to execute" },
},
required: ["script"],
},
},
];
// Global state
let browser: Browser | null;
let page: Page | null;
const consoleLogs: string[] = [];
const screenshots = new Map<string, string>();
let previousLaunchOptions: any = null;
async function ensureBrowser({ launchOptions, allowDangerous }: any) {
const DANGEROUS_ARGS = [
'--no-sandbox',
'--disable-setuid-sandbox',
'--single-process',
'--disable-web-security',
'--ignore-certificate-errors',
'--disable-features=IsolateOrigins',
'--disable-site-isolation-trials',
'--allow-running-insecure-content'
];
// Parse environment config safely
let envConfig = {};
try {
envConfig = JSON.parse(process.env.PUPPETEER_LAUNCH_OPTIONS || '{}');
} catch (error: any) {
console.warn('Failed to parse PUPPETEER_LAUNCH_OPTIONS:', error?.message || error);
}
launchOptions = launchOptions || { headless: true };
if (launchOptions.headless === undefined) {
launchOptions.headless = true;
}
// Deep merge environment config with user-provided options
const mergedConfig = deepMerge(envConfig, launchOptions || {});
// Security validation for merged config
if (mergedConfig?.args) {
const dangerousArgs = mergedConfig.args?.filter?.((arg: string) => DANGEROUS_ARGS.some((dangerousArg: string) => arg.startsWith(dangerousArg)));
if (dangerousArgs?.length > 0 && !(allowDangerous || (process.env.ALLOW_DANGEROUS === 'true'))) {
throw new Error(`Dangerous browser arguments detected: ${dangerousArgs.join(', ')}. Fround from environment variable and tool call argument. ` +
'Set allowDangerous: true in the tool call arguments to override.');
}
}
try {
if ((browser && !browser.connected) ||
(launchOptions && (JSON.stringify(launchOptions) != JSON.stringify(previousLaunchOptions)))) {
await browser?.close();
browser = null;
}
}
catch (error) {
browser = null;
}
previousLaunchOptions = launchOptions;
if (!browser) {
const npx_args = {
headless: false,
args: [ '--proxy-server=127.0.0.1:7890' ]
}
const docker_args = { headless: true, args: ["--no-sandbox", "--single-process", "--no-zygote"] }
browser = await puppeteer.launch(deepMerge(
process.env.DOCKER_CONTAINER ? docker_args : npx_args,
mergedConfig
));
const pages = await browser.pages();
page = pages[0];
page.on("console", (msg) => {
const logEntry = `[${msg.type()}] ${msg.text()}`;
consoleLogs.push(logEntry);
server.notification({
method: "notifications/resources/updated",
params: { uri: "console://logs" },
});
});
}
return page!;
}
// Deep merge utility function
function deepMerge(target: any, source: any): any {
const output = Object.assign({}, target);
if (typeof target !== 'object' || typeof source !== 'object') return source;
for (const key of Object.keys(source)) {
const targetVal = target[key];
const sourceVal = source[key];
if (Array.isArray(targetVal) && Array.isArray(sourceVal)) {
// Deduplicate args/ignoreDefaultArgs, prefer source values
output[key] = [...new Set([
...(key === 'args' || key === 'ignoreDefaultArgs' ?
targetVal.filter((arg: string) => !sourceVal.some((launchArg: string) => arg.startsWith('--') && launchArg.startsWith(arg.split('=')[0]))) :
targetVal),
...sourceVal
])];
} else if (sourceVal instanceof Object && key in target) {
output[key] = deepMerge(targetVal, sourceVal);
} else {
output[key] = sourceVal;
}
}
return output;
}
declare global {
interface Window {
mcpHelper: {
logs: string[],
originalConsole: Partial<typeof console>,
}
}
}
export async function handleToolCall(name: string, args: any): Promise<CallToolResult> {
const page = await ensureBrowser(args);
switch (name) {
case "k_navigate":
await page.goto(args.url, {
waitUntil: 'networkidle2'
});
return {
content: [{
type: "text",
text: `Navigated to ${args.url}`,
}],
isError: false,
};
case "k_screenshot": {
const width = args.width ?? 800;
const height = args.height ?? 600;
await page.setViewport({ width, height });
const screenshot = await (args.selector ?
(await page.$(args.selector))?.screenshot({ encoding: "base64" }) :
page.screenshot({ encoding: "base64", fullPage: false }));
if (!screenshot) {
return {
content: [{
type: "text",
text: args.selector ? `Element not found: ${args.selector}` : "Screenshot failed",
}],
isError: true,
};
}
screenshots.set(args.name, screenshot as string);
server.notification({
method: "notifications/resources/list_changed",
});
return {
content: [
{
type: "text",
text: `Screenshot '${args.name}' taken at ${width}x${height}`,
} as TextContent,
{
type: "image",
data: screenshot,
mimeType: "image/png",
} as ImageContent,
],
isError: false,
};
}
case "k_click":
try {
await page.click(args.selector);
return {
content: [{
type: "text",
text: `Clicked: ${args.selector}`,
}],
isError: false,
};
} catch (error) {
return {
content: [{
type: "text",
text: `Failed to click ${args.selector}: ${(error as Error).message}`,
}],
isError: true,
};
}
case "k_fill":
try {
await page.waitForSelector(args.selector);
await page.type(args.selector, args.value);
return {
content: [{
type: "text",
text: `Filled ${args.selector} with: ${args.value}`,
}],
isError: false,
};
} catch (error) {
return {
content: [{
type: "text",
text: `Failed to fill ${args.selector}: ${(error as Error).message}`,
}],
isError: true,
};
}
case "k_select":
try {
await page.waitForSelector(args.selector);
await page.select(args.selector, args.value);
return {
content: [{
type: "text",
text: `Selected ${args.selector} with: ${args.value}`,
}],
isError: false,
};
} catch (error) {
return {
content: [{
type: "text",
text: `Failed to select ${args.selector}: ${(error as Error).message}`,
}],
isError: true,
};
}
case "k_hover":
try {
await page.waitForSelector(args.selector);
await page.hover(args.selector);
return {
content: [{
type: "text",
text: `Hovered ${args.selector}`,
}],
isError: false,
};
} catch (error) {
return {
content: [{
type: "text",
text: `Failed to hover ${args.selector}: ${(error as Error).message}`,
}],
isError: true,
};
}
case "k_evaluate":
try {
await page.evaluate(() => {
window.mcpHelper = {
logs: [],
originalConsole: { ...console },
};
['log', 'info', 'warn', 'error'].forEach(method => {
(console as any)[method] = (...args: any[]) => {
window.mcpHelper.logs.push(`[${method}] ${args.join(' ')}`);
(window.mcpHelper.originalConsole as any)[method](...args);
};
});
});
const result = await page.evaluate(args.script);
const logs = await page.evaluate(() => {
Object.assign(console, window.mcpHelper.originalConsole);
const logs = window.mcpHelper.logs;
delete (window as any).mcpHelper;
return logs;
});
return {
content: [
{
type: "text",
text: `Execution result:\n${JSON.stringify(result, null, 2)}\n\nConsole output:\n${logs.join('\n')}`,
},
],
isError: false,
};
} catch (error) {
return {
content: [{
type: "text",
text: `Script execution failed: ${(error as Error).message}`,
}],
isError: true,
};
}
default:
return {
content: [{
type: "text",
text: `Unknown tool: ${name}`,
}],
isError: true,
};
}
}
const server = new Server(
{
name: "my-browser",
version: "0.1.0",
},
{
capabilities: {
resources: {},
tools: {},
},
},
);
// Setup request handlers
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
resources: [
{
uri: "console://logs",
mimeType: "text/plain",
name: "Browser console logs",
},
...Array.from(screenshots.keys()).map(name => ({
uri: `screenshot://${name}`,
mimeType: "image/png",
name: `Screenshot: ${name}`,
})),
],
}));
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const uri = request.params.uri.toString();
if (uri === "console://logs") {
return {
contents: [{
uri,
mimeType: "text/plain",
text: consoleLogs.join("\n"),
}],
};
}
if (uri.startsWith("screenshot://")) {
const name = uri.split("://")[1];
const screenshot = screenshots.get(name);
if (screenshot) {
return {
contents: [{
uri,
mimeType: "image/png",
blob: screenshot,
}],
};
}
}
throw new Error(`Resource not found: ${uri}`);
});
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: TOOLS,
}));
server.setRequestHandler(CallToolRequestSchema, async (request) =>
handleToolCall(request.params.name, request.params.arguments ?? {})
);
async function runServer() {
const transport = new StdioServerTransport();
await server.connect(transport);
}
runServer().catch(console.error);
process.stdin.on("close", () => {
console.error("Puppeteer MCP Server closed");
server.close();
});

2351
node/servers/my-browser/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,28 @@
{
"name": "@modelcontextprotocol/server-puppeteer",
"version": "0.6.2",
"description": "MCP server for browser automation using Puppeteer",
"license": "MIT",
"author": "Anthropic, PBC (https://anthropic.com)",
"homepage": "https://modelcontextprotocol.io",
"bugs": "https://github.com/modelcontextprotocol/servers/issues",
"bin": {
"mcp-server-puppeteer": "dist/index.js"
},
"files": [
"dist"
],
"scripts": {
"build": "tsc && shx chmod +x dist/*.js",
"prepare": "npm run build",
"watch": "tsc --watch"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.10.2",
"puppeteer": "^23.4.0"
},
"devDependencies": {
"shx": "^0.3.4",
"typescript": "^5.6.2"
}
}

View File

@ -0,0 +1,14 @@
import { handleToolCall } from "./browser";
async function main() {
const res = await handleToolCall(
'k_navigate',
{
url: 'https://towardsdatascience.com/tag/editors-pick/'
}
)
console.log(res);
}
main();

View File

@ -0,0 +1,19 @@
{
"compilerOptions": {
"outDir": "./dist",
"rootDir": ".",
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true
},
"include": [
"./**/*.ts",
"src/**/*"
],
"exclude": ["node_modules"]
}

99
notebook/clear-logs.ipynb Normal file
View File

@ -0,0 +1,99 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"import zipfile\n",
"from typing import *\n",
"import os\n",
"import json\n",
"import yaml\n",
"\n",
"log_home = '../logs'"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"def get_log_file_line(file: str, level='DEBUG') -> Generator:\n",
" if file.endswith('.log'):\n",
" for line in open(file, 'r', encoding='utf-8'):\n",
" if level in line:\n",
" yield line\n",
" elif file.endswith('.zip'):\n",
" with zipfile.ZipFile(file, 'r') as zip:\n",
" for zip_file in zip.namelist():\n",
" file_bytes = zip.read(zip_file)\n",
" for line in file_bytes.decode('utf-8').split('\\n'):\n",
" if level in line:\n",
" yield line\n",
" "
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"rag_log_files = [os.path.join(log_home, file) for file in os.listdir(log_home) if file.startswith('rag')]\n",
"\n",
"interesting_data = []\n",
"\n",
"for rag_file in rag_log_files:\n",
" for line in get_log_file_line(rag_file, level='DEBUG'):\n",
" try:\n",
" data = line.split('|')[-1].strip()\n",
" data = eval(data)\n",
" if data['intent']['name'] != 'others':\n",
" interesting_data.append(data)\n",
" except Exception:\n",
" pass"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"story = {'stories': []}\n",
"for d in interesting_data:\n",
" story['stories'].append({\n",
" 'message': d['query'],\n",
" 'intent': d['intent']['name']\n",
" })\n",
"\n",
"with open('../config/qq.story.yml', 'w', encoding='utf-8') as fp:\n",
" yaml.dump(story, fp, allow_unicode=True)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "base",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.5"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

918
notebook/experiment.ipynb Normal file

File diff suppressed because one or more lines are too long

478
notebook/github-issue.ipynb Normal file
View File

@ -0,0 +1,478 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"import requests as r\n",
"from bs4 import BeautifulSoup, Tag\n",
"import json\n",
"import os\n",
"from threading import Thread\n",
"from urllib.parse import urlparse"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"def download_worker(media_url, save_path):\n",
" res = r.get(media_url)\n",
" with open(save_path, 'wb') as fp:\n",
" fp.write(res.content)\n",
" print('[crawler] 图像已经保存至', save_path)"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [],
"source": [
"titles = []\n",
"fail_ids = []\n",
"\n",
"def crawler_issue(issue_id: str):\n",
" folder = 'issue-' + str(issue_id)\n",
" os.makedirs('../docs/digital-issue/' + folder, exist_ok=True)\n",
" res = r.get('https://github.com/Digital-EDA/Digital-IDE/issues/' + str(issue_id), timeout=10)\n",
" soup = BeautifulSoup(res.text, 'html.parser')\n",
"\n",
" texts = []\n",
" el = soup.find('bdi')\n",
" if isinstance(el, Tag):\n",
" title = el.text\n",
" titles.append(title)\n",
" texts.append('问题标题 '+ title)\n",
" \n",
" for td in soup.find_all('td', attrs={ 'class': 'js-comment-body' }):\n",
" if isinstance(td, Tag):\n",
" for p in td.find_all('p'):\n",
" if isinstance(p, Tag):\n",
" text = p.text\n",
" texts.append(text)\n",
" for a in p.find_all('a'):\n",
" href = a.attrs['href']\n",
" if href and '.png' in href:\n",
" urlp = urlparse(href)\n",
" name = urlp.path.split('/')[-1]\n",
" save_path = '../docs/digital-issue/' + folder + '/' + name\n",
" t = Thread(target=download_worker, args=(href, save_path))\n",
" t.start()\n",
"\n",
" text = '\\n'.join(texts)\n",
" with open('../docs/digital-issue/{}/issue.md'.format(folder), 'w', encoding='utf-8') as fp:\n",
" fp.write(text) "
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-67/331660157-920d7143-f262-42d5-af57-a817bf3aee01.png\n"
]
}
],
"source": [
"crawler_issue(67)"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [],
"source": [
"def crawler_issue_page(page_url: str):\n",
" res = r.get(page_url)\n",
" if res.status_code != 200:\n",
" print('page url {} return 404'.format(page_url))\n",
" return\n",
" html = res.text\n",
" soup = BeautifulSoup(html, 'html.parser')\n",
" issue_container = soup.find('div', { 'class': 'js-navigation-container js-active-navigation-container' })\n",
" issue_ids = []\n",
" for div in issue_container.children:\n",
" if isinstance(div, Tag):\n",
" id = div.attrs['id'].split('_')[-1]\n",
" issue_ids.append(int(id))\n",
" \n",
" for issue_id in issue_ids:\n",
" try:\n",
" print('爬取 issue-{} 中 ...'.format(issue_id))\n",
" crawler_issue(issue_id)\n",
" except Exception as e:\n",
" fail_ids.append(issue_id)\n",
" print('爬取 {} 失败'.format(issue_id))"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"爬取 issue-71 中 ...\n",
"爬取 issue-70 中 ...\n",
"爬取 issue-69 中 ...\n",
"爬取 issue-68 中 ...\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-69/331220146-9deeccbf-cc0e-4810-bdd9-80e11d083c15.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-69/331220242-9abe8b7b-5985-4c1a-ac0f-30aba75ef8d2.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-69/331220203-3bb8aefd-e04a-4eb7-ae87-ca48f1daa120.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-69/331220080-b0c5f0af-e38c-4819-9efa-7491650ddb92.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-69/331219978-d22a5a5d-da00-430c-b966-68517ab264c0.png\n",
"爬取 issue-67 中 ...\n",
"爬取 issue-66 中 ...\n",
"爬取 66 失败\n",
"爬取 issue-65 中 ...\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-67/331660157-920d7143-f262-42d5-af57-a817bf3aee01.png\n",
"爬取 issue-64 中 ...\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-65/328185054-e08c66b2-7e87-4238-88cb-e0672b2de530.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-65/328184842-2e13483e-4ece-4eb6-8c8a-3d9c92a97651.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-65/328185191-305e8b86-a9de-434b-a1cf-80c441c51df2.png\n",
"爬取 issue-63 中 ...\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-64/327033289-dc79968e-8279-43aa-b6a1-a6f1acd4155f.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-64/327033187-1b9134e0-387e-491d-a478-3ea6438728a4.png\n",
"爬取 issue-62 中 ...\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-63/326496750-53a0c779-8a4b-418d-b21c-4ea2151edd92.png\n",
"爬取 issue-61 中 ...\n",
"爬取 issue-60 中 ...\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-61/325593801-bd5c4229-f47a-4d6e-99a1-0cc912378f0e.png\n",
"爬取 issue-55 中 ...\n",
"爬取 issue-54 中 ...\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-55/316435757-14c497a5-6ecc-4f97-850b-7e13988ec7aa.png\n",
"爬取 54 失败\n",
"爬取 issue-53 中 ...\n",
"爬取 issue-52 中 ...\n",
"爬取 issue-51 中 ...\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-53/305870587-69d44e21-edfc-4fc0-9ad7-daaec393caac.png\n",
"爬取 issue-50 中 ...\n",
"爬取 50 失败\n",
"爬取 issue-49 中 ...\n",
"爬取 issue-48 中 ...\n",
"爬取 issue-47 中 ...\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-49/300495384-94077fee-624f-48cc-98fd-d6e6fe16251b.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-49/300495316-f8c98f42-a320-4c4e-84f7-2aaab4fb39f1.png\n",
"爬取 issue-46 中 ...\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-49/300495541-23556932-5526-4428-b1c2-25c840352422.png\n",
"爬取 issue-45 中 ...\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-46/294358431-92b4f252-91a9-4326-ae14-9d21037d3478.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-46/294358432-2bbec170-1400-49c3-a30e-a0acc4bf3f66.png\n",
"爬取 issue-44 中 ...\n",
"爬取 issue-43 中 ...\n",
"爬取 issue-42 中 ...\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-43/292665566-dc8dd944-f464-4606-8900-4562cdf404c7.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-43/292665655-3341f355-0ca4-4757-9814-5702515922e7.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-43/335602119-bd06d356-3356-45a8-8556-b9b60fdb337b.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-43/292665721-e4730448-1588-424c-9a98-c661dfb5237d.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-43/335602049-bade0794-1653-42f7-8816-d3f3484009e3.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-43/335601886-3731689f-7ddb-4d4b-9e73-9e1a631e403f.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-43/335599172-7ea754a3-1dea-428f-baf4-e04c400e2744.png\n",
"爬取 issue-41 中 ...\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-43/292665609-f6bf03cd-857b-4156-8795-6e41416d96e4.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-42/292425880-bc7f4792-6c68-45e0-862f-23b7b1232dce.png\n",
"爬取 issue-40 中 ...\n",
"爬取 issue-39 中 ...\n",
"爬取 issue-38 中 ...\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-39/291777353-268ce49b-78c4-46a8-b543-542db0ef6dcc.png\n",
"爬取 issue-37 中 ...\n",
"爬取 issue-36 中 ...\n",
"爬取 issue-35 中 ...\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-37/291228777-6d65928c-5ffd-4aab-af19-03291f31473a.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-36/291268509-e9c687a4-2b28-46f1-8670-827359df792e.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-36/291268221-1c380a09-76b5-45a6-aff3-d8a873868402.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-36/291268429-e0517629-7b5a-4751-a431-330f04d8c1ee.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-36/291268113-20044b19-d508-47ea-9f07-f675bc72a2cb.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-36/291268011-69f0a3ef-d509-47c9-b949-36d280edc4f8.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-36/291267929-d697e859-8ad0-4cc3-aa15-e50d0a26dc53.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-36/291268654-d8a5686c-c109-461b-b68a-ff00bcd9f462.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-36/291225163-78241e4c-400a-4d75-a008-3c34ca26ae4a.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-36/291225347-63167ad5-6896-4afe-a6d7-197532a23f8f.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-36/291268318-27f79c67-8e8d-419a-a0b7-e744b416b704.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-36/291267797-b00bdf44-c648-47d3-9bd9-eb1e68b12193.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-36/291267690-67d01501-ab54-4274-8425-e876b7035391.png\n",
"爬取 issue-34 中 ...\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-35/291224673-a3f70f16-271c-4905-b1f0-1c011b56d3bf.png\n",
"爬取 issue-33 中 ...\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-34/291140726-cba47a2d-dd99-49e5-b302-733e266d0c44.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-34/291140726-cba47a2d-dd99-49e5-b302-733e266d0c44.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-34/291310957-8b8f17a0-ec66-4009-9657-2433d51319c8.png\n",
"爬取 issue-32 中 ...\n",
"爬取 issue-31 中 ...\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-32/290024305-41e868d5-e737-4f1d-93af-558db2abba91.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-32/290024353-47c9297c-6160-402a-b3bf-e08bd9c923ea.png\n",
"爬取 31 失败\n",
"爬取 issue-30 中 ...\n",
"爬取 issue-29 中 ...\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-30/290022070-0e449a39-5360-474e-bc18-1c1729071f66.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-30/290022151-dc253b33-fbfe-4f9c-8023-e00e180015d6.png\n",
"爬取 issue-28 中 ...\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-29/290020867-53b8d764-f8f8-41ab-8262-0ac17702fa42.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-29/290018445-3d43470c-5b8b-4466-b8c2-800694771143.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-29/290018445-3d43470c-5b8b-4466-b8c2-800694771143.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-29/290020867-53b8d764-f8f8-41ab-8262-0ac17702fa42.png\n",
"爬取 issue-27 中 ...\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-28/288112763-1dfaa7f2-f52b-42b5-ba40-c47c16205265.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-28/288112141-a1c08800-b6dd-4215-9ecf-288759cc0174.png\n",
"爬取 issue-26 中 ...\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-27/287924096-490bef45-fc31-4ffc-a3a5-d077c350ff88.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-27/287923928-5e817c41-c54d-409c-be36-576efb0a299a.png\n",
"爬取 issue-25 中 ...\n",
"爬取 issue-24 中 ...\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-25/282802849-27fc9f7a-f7f5-4b8d-84e5-1060166b0ad7.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-25/282800215-946fad7c-54ae-46c9-be9a-d5a69b4fbf7b.png\n",
"爬取 issue-23 中 ...\n",
"爬取 issue-22 中 ...\n",
"爬取 issue-21 中 ...\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-22/275505102-406e3256-7a3e-4deb-9456-2a49b41ca85d.png\n",
"爬取 issue-20 中 ...\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-21/275300475-444cf824-5489-461d-9678-440901554f68.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-21/279390992-4a04af63-c176-49a8-a60e-5c3e95c07f8b.png\n",
"爬取 issue-19 中 ...\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-20/268173022-80623e60-fba6-4f4c-85eb-5fb542ba8170.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-20/268173043-927f355a-37b5-45f5-bd88-78317549bf54.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-20/268173050-b186e855-4ad3-44c0-8708-59b11c5506a7.png\n",
"爬取 19 失败\n",
"爬取 issue-17 中 ...\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-17/264944498-f609f333-53a1-40f3-8bd5-a320b21398df.png\n",
"爬取 issue-16 中 ...\n",
"爬取 issue-15 中 ...\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-16/264304291-1fbb69c3-02fa-4d50-9dec-cc6da46c1dd2.png\n",
"爬取 issue-14 中 ...\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-15/264302335-b7b9d42e-aa16-474d-8c49-5573e397c374.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-15/264302196-e355b398-1ba7-4b7e-aa0b-b1d67646182a.png\n",
"爬取 issue-13 中 ...\n",
"爬取 issue-12 中 ...\n",
"爬取 issue-11 中 ...\n",
"爬取 issue-10 中 ...\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-12/263475138-92d989d2-2b5e-432c-bfde-8bd8f3524b6e.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-12/263475180-595e8d91-2645-47bf-a4db-24aad89d12ae.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-12/289706401-d79d32f8-5738-4088-bc92-74e19da24885.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-14/263748729-d0d2d005-019b-404f-a720-8f75b19a52ba.png\n",
"爬取 issue-9 中 ...\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-11/263142498-1af4cb41-c431-4de0-9d26-65729d3dfe65.png\n",
"爬取 issue-8 中 ...\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-9/261163571-1caa7264-3702-4467-9986-49e0557b0edc.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-10/263140095-8d3beafa-ad35-405d-bcf7-3964853174b2.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-9/261163518-42173b79-b7b3-41c3-8860-1007f140fe86.png\n",
"爬取 issue-7 中 ...\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-8/260685640-0c9db5e7-ae1e-4558-b3f7-72ebb4f67043.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-9/261163490-45ab4e0e-7175-4a65-9a70-4e51b4c1366a.png\n",
"爬取 issue-6 中 ...\n",
"爬取 issue-5 中 ...\n",
"爬取 issue-4 中 ...\n",
"爬取 issue-3 中 ...\n",
"爬取 issue-2 中 ...\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-6/260248721-a56ad686-d1ac-4fa4-9fe7-fb9007f7a1e3.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-3/255603702-8379ccc3-4d7a-407f-8777-aba9666e7c58.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-4/255386123-e5e990fa-af1f-439b-bb74-aa20af50366c.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-6/256976429-8ad21695-2397-4a79-8fab-43fa01da5e24.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-5/260307745-b545d146-a49b-4ebf-af88-ce3982a2e0ff.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-3/260107703-9e01db47-6c15-4d41-b823-a1896be68af7.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-3/260107481-5c0127b4-2273-4a14-b996-2d109a947a5e.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-4/255386114-73e4b52e-3eee-4652-971e-4bf123d6c9aa.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-5/255646125-b5035137-6df5-4189-95c2-199970dfbe8d.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-6/256976410-22019e06-df93-48b1-93a6-05901197b277.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-6/256976463-9b61e743-536e-4d53-af74-f8015b104a36.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-4/255386053-b536d9d7-fe3b-4c83-a581-0884e3cf04f6.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-7/260630437-8d34c4a0-cc48-44b5-bbb8-94742c2e0776.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-5/255643011-49b2efa2-09f4-463e-908b-4510d2110429.png\n",
"爬取 issue-1 中 ...\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-3/255342825-dae63d0c-05b8-4965-b2e0-19df84778a5e.png\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-7/260630481-0cbc73cf-f516-4b3d-92f5-17598f089297.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-4/255386339-56413f1c-bb32-49c0-aa85-dceeceb8594a.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-6/260716833-f3d89a67-7b4f-4daa-9a0a-8313dcf9caaa.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-2/253884964-cbaf42fa-fa7d-48ed-8353-184dd0895a12.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-2/256974788-7c2e9bf0-d239-4022-9aac-f8f160afad6f.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-2/255247553-45d154cc-37d1-459d-80d3-adad6324de4c.png\n",
"[crawler] 图像已经保存至 ../docs/digital-issue/issue-2/256974788-7c2e9bf0-d239-4022-9aac-f8f160afad6f.png\n"
]
}
],
"source": [
"page_urls = [\n",
" 'https://github.com/Digital-EDA/Digital-IDE/issues?page=1&q=',\n",
" 'https://github.com/Digital-EDA/Digital-IDE/issues?page=2&q=',\n",
" 'https://github.com/Digital-EDA/Digital-IDE/issues?page=3&q='\n",
"]\n",
"\n",
"titles.clear()\n",
"fail_ids.clear()\n",
"for url in page_urls:\n",
" crawler_issue_page(url)"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"66\n",
"54\n",
"50\n",
"31\n",
"19\n"
]
}
],
"source": [
"for issue_id in fail_ids:\n",
" print(issue_id)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"Exception in thread Thread-66 (download_worker):\n",
"Traceback (most recent call last):\n",
" File \"/data/zhelonghuang/miniconda3/lib/python3.11/site-packages/urllib3/connectionpool.py\", line 467, in _make_request\n",
" self._validate_conn(conn)\n",
" File \"/data/zhelonghuang/miniconda3/lib/python3.11/site-packages/urllib3/connectionpool.py\", line 1099, in _validate_conn\n",
" conn.connect()\n",
" File \"/data/zhelonghuang/miniconda3/lib/python3.11/site-packages/urllib3/connection.py\", line 653, in connect\n",
" sock_and_verified = _ssl_wrap_socket_and_match_hostname(\n",
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
" File \"/data/zhelonghuang/miniconda3/lib/python3.11/site-packages/urllib3/connection.py\", line 806, in _ssl_wrap_socket_and_match_hostname\n",
" ssl_sock = ssl_wrap_socket(\n",
" ^^^^^^^^^^^^^^^^\n",
" File \"/data/zhelonghuang/miniconda3/lib/python3.11/site-packages/urllib3/util/ssl_.py\", line 465, in ssl_wrap_socket\n",
" ssl_sock = _ssl_wrap_socket_impl(sock, context, tls_in_tls, server_hostname)\n",
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
" File \"/data/zhelonghuang/miniconda3/lib/python3.11/site-packages/urllib3/util/ssl_.py\", line 509, in _ssl_wrap_socket_impl\n",
" return ssl_context.wrap_socket(sock, server_hostname=server_hostname)\n",
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
" File \"/data/zhelonghuang/miniconda3/lib/python3.11/ssl.py\", line 517, in wrap_socket\n",
" return self.sslsocket_class._create(\n",
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
" File \"/data/zhelonghuang/miniconda3/lib/python3.11/ssl.py\", line 1108, in _create\n",
" self.do_handshake()\n",
" File \"/data/zhelonghuang/miniconda3/lib/python3.11/ssl.py\", line 1379, in do_handshake\n",
" self._sslobj.do_handshake()\n",
"TimeoutError: [Errno 110] Connection timed out\n",
"\n",
"The above exception was the direct cause of the following exception:\n",
"\n",
"Traceback (most recent call last):\n",
" File \"/data/zhelonghuang/miniconda3/lib/python3.11/site-packages/requests/adapters.py\", line 486, in send\n",
" resp = conn.urlopen(\n",
" ^^^^^^^^^^^^^\n",
" File \"/data/zhelonghuang/miniconda3/lib/python3.11/site-packages/urllib3/connectionpool.py\", line 847, in urlopen\n",
" retries = retries.increment(\n",
" ^^^^^^^^^^^^^^^^^^\n",
" File \"/data/zhelonghuang/miniconda3/lib/python3.11/site-packages/urllib3/util/retry.py\", line 470, in increment\n",
" raise reraise(type(error), error, _stacktrace)\n",
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
" File \"/data/zhelonghuang/miniconda3/lib/python3.11/site-packages/urllib3/util/util.py\", line 39, in reraise\n",
" raise value\n",
" File \"/data/zhelonghuang/miniconda3/lib/python3.11/site-packages/urllib3/connectionpool.py\", line 793, in urlopen\n",
" response = self._make_request(\n",
" ^^^^^^^^^^^^^^^^^^^\n",
" File \"/data/zhelonghuang/miniconda3/lib/python3.11/site-packages/urllib3/connectionpool.py\", line 491, in _make_request\n",
" raise new_e\n",
" File \"/data/zhelonghuang/miniconda3/lib/python3.11/site-packages/urllib3/connectionpool.py\", line 469, in _make_request\n",
" self._raise_timeout(err=e, url=url, timeout_value=conn.timeout)\n",
" File \"/data/zhelonghuang/miniconda3/lib/python3.11/site-packages/urllib3/connectionpool.py\", line 370, in _raise_timeout\n",
" raise ReadTimeoutError(\n",
"urllib3.exceptions.ReadTimeoutError: HTTPSConnectionPool(host='private-user-images.githubusercontent.com', port=443): Read timed out. (read timeout=None)\n",
"\n",
"During handling of the above exception, another exception occurred:\n",
"\n",
"Traceback (most recent call last):\n",
" File \"/data/zhelonghuang/miniconda3/lib/python3.11/threading.py\", line 1038, in _bootstrap_inner\n",
" self.run()\n",
" File \"/data/zhelonghuang/miniconda3/lib/python3.11/threading.py\", line 975, in run\n",
" self._target(*self._args, **self._kwargs)\n",
" File \"/tmp/ipykernel_1083457/2148945449.py\", line 2, in download_worker\n",
" File \"/data/zhelonghuang/miniconda3/lib/python3.11/site-packages/requests/api.py\", line 73, in get\n",
" return request(\"get\", url, params=params, **kwargs)\n",
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
" File \"/data/zhelonghuang/miniconda3/lib/python3.11/site-packages/requests/api.py\", line 59, in request\n",
" return session.request(method=method, url=url, **kwargs)\n",
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
" File \"/data/zhelonghuang/miniconda3/lib/python3.11/site-packages/requests/sessions.py\", line 589, in request\n",
" resp = self.send(prep, **send_kwargs)\n",
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
" File \"/data/zhelonghuang/miniconda3/lib/python3.11/site-packages/requests/sessions.py\", line 703, in send\n",
" r = adapter.send(request, **kwargs)\n",
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
" File \"/data/zhelonghuang/miniconda3/lib/python3.11/site-packages/requests/adapters.py\", line 532, in send\n",
" raise ReadTimeout(e, request=request)\n",
"requests.exceptions.ReadTimeout: HTTPSConnectionPool(host='private-user-images.githubusercontent.com', port=443): Read timed out. (read timeout=None)\n"
]
}
],
"source": [
"import yaml\n",
"target_yml = '../config/github-issue.story.yml'\n",
"\n",
"story_config = {\n",
" 'stories': []\n",
"}\n",
"\n",
"for title in titles:\n",
" story_config['stories'].append({\n",
" 'message': title,\n",
" 'intent': 'bug'\n",
" })\n",
"with open(target_yml, 'w', encoding='utf-8') as fp:\n",
" yaml.dump(story_config, fp, Dumper=yaml.Dumper, indent=2, allow_unicode=True)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "base",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.5"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

View File

@ -2,7 +2,7 @@
"cells": [
{
"cell_type": "code",
"execution_count": 24,
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
@ -19,29 +19,39 @@
},
{
"cell_type": "code",
"execution_count": 29,
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"/data/zhelonghuang/miniconda3/lib/python3.11/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
" from .autonotebook import tqdm as notebook_tqdm\n",
"/data/zhelonghuang/miniconda3/lib/python3.11/site-packages/huggingface_hub/file_download.py:1132: FutureWarning: `resume_download` is deprecated and will be removed in version 1.0.0. Downloads always resume when possible. If you want to force a new download, use `force_download=True`.\n",
" warnings.warn(\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"整理得到 238 个文档\n",
"分块得到 1206 个文档\n",
"数据库已存储到 blog-vecdb 中\n"
"整理得到 304 个文档\n",
"分块得到 1276 个文档\n",
"数据库已存储到 ../blog-vecdb 中\n"
]
}
],
"source": [
"embedding = HuggingFaceEmbeddings(model_name='maidalun1020/bce-embedding-base_v1')\n",
"\n",
"db_persistent_dir = 'blog-vecdb'\n",
"db_persistent_dir = '../blog-vecdb'\n",
"\n",
"if os.path.exists(db_persistent_dir):\n",
" db = FAISS.load_local(db_persistent_dir, embedding, allow_dangerous_deserialization=True)\n",
" print('成功从 {} 中提取数据'.format(db_persistent_dir))\n",
"else:\n",
" loader = DirectoryLoader('./docs', glob='**/*.md')\n",
" loader = DirectoryLoader('../docs', glob='**/*.md')\n",
" docs = loader.load()\n",
" print('整理得到 {} 个文档'.format(len(docs)))\n",
"\n",
@ -60,21 +70,21 @@
},
{
"cell_type": "code",
"execution_count": 11,
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[(Document(page_content='完整的 VCD 语法,可以参考中科大资源网站上的 IEEE 1364 标准 第 18 章的内容。\\n\\n基本信息\\n\\n一个 vcd 文件会描述波形产生的基本信息,包括,日期,版本,单位时间,注释等等。\\n\\n日期 date\\n\\n$date\\n Sat Apr 20 20:06:14 2024\\n$end\\n\\n与 verilog 类似,$end$ 是一切 scope 的结束符号,你也可以写成这样:\\n\\n$date Sat Apr 20 20:06:14 2024 $end\\n\\n版本 version\\n\\n$version\\n Icarus Verilog\\n$end\\n\\n时间单位 timescale\\n\\n$timescale\\n 1ns\\n$end\\n\\n注释 comment\\n\\n$comment\\n Show the parameter values.\\n$end\\n\\n变量申明\\n\\nvcd 中通过如下的方式申明一个 module 内的变量,,每条的格式为 $var type bitwidth id name比如\\n\\n$scope module ID_EX $end\\n$var wire 2 ! AluOp [1:0] $end\\n$var wire 1 \" AluSrc $end\\n$var wire 1 # MemRead $end\\n$upscope $end\\n\\n以上语句申明了一个模块 ID_EX 和内部的三个信号 AluOpAluSrc 和 MemRead。这三个变量在接下来的 vcd 描述中会被 !\"# 替代,这也被称为 vcd 描述中,变量的 id.\\n\\n单个 module 的变量申明通过 $upscope $end 结束。\\n\\n所有 module 的变量申明通过 $enddefinitions $end 结束。\\n\\nparameter 赋值', metadata={'source': 'docs/224.md', 'start_index': 0}),\n",
"[(Document(page_content='完整的 VCD 语法,可以参考中科大资源网站上的 IEEE 1364 标准 第 18 章的内容。\\n\\n基本信息\\n\\n一个 vcd 文件会描述波形产生的基本信息,包括,日期,版本,单位时间,注释等等。\\n\\n日期 date\\n\\n$date\\n Sat Apr 20 20:06:14 2024\\n$end\\n\\n与 verilog 类似,$end$ 是一切 scope 的结束符号,你也可以写成这样:\\n\\n$date Sat Apr 20 20:06:14 2024 $end\\n\\n版本 version\\n\\n$version\\n Icarus Verilog\\n$end\\n\\n时间单位 timescale\\n\\n$timescale\\n 1ns\\n$end\\n\\n注释 comment\\n\\n$comment\\n Show the parameter values.\\n$end\\n\\n变量申明\\n\\nvcd 中通过如下的方式申明一个 module 内的变量,,每条的格式为 $var type bitwidth id name比如\\n\\n$scope module ID_EX $end\\n$var wire 2 ! AluOp [1:0] $end\\n$var wire 1 \" AluSrc $end\\n$var wire 1 # MemRead $end\\n$upscope $end\\n\\n以上语句申明了一个模块 ID_EX 和内部的三个信号 AluOpAluSrc 和 MemRead。这三个变量在接下来的 vcd 描述中会被 !\"# 替代,这也被称为 vcd 描述中,变量的 id.\\n\\n单个 module 的变量申明通过 $upscope $end 结束。\\n\\n所有 module 的变量申明通过 $enddefinitions $end 结束。\\n\\nparameter 赋值', metadata={'source': '../docs/kirigaya.cn/224.md', 'start_index': 0}),\n",
" 0.4351002),\n",
" (Document(page_content='TODO\\n\\n找到所有 vcd item 的 type 类型可参考https://pyvcd.readthedocs.io/en/latest/vcd.common.html\\n\\nbug\\n\\n详见飞书文档https://nc-ai-lab.feishu.cn/wiki/Z4AxwU1SdilATAk7GuvcYkIDnwh\\n\\n流程\\n\\n目前需要为Digital-IDE设计一个render用于显示VCD文件。\\n\\nIEEE 1364定义VCD(value change dump)文件是含已选变量(信号)的值变化信息存储文件。\\n\\nwavedrom在GitHub上开发过vcd和hdl的wavedrom脚本格式可以用于进行DIDE的vcd可视化的二次开发目前发现为了实现vcd文件基本的仓库主要是下面这两个\\n\\nJS层与一个简单的前端https://github.com/wavedrom/vcdrom\\n\\nwasm 解析https://github.com/wavedrom/vcd\\n\\n打算基于这两个仓库二次开发一个好用的vcd渲染模块再加入DIDE中。\\n\\n开发思路\\n\\nvcd 的渲染器分为如下几步进行开发。\\n\\nmermaid\\ngraph LR\\na(读取)-->b(渲染)-->c(解析)\\n\\nVCD 读取和解析并不难,难在如何快速安全地读取,对于一些长时间的模拟和仿真而言, vcd 文件可能会非常大。因此vcd 需要分块读取,分块解析,为了避免这些不必要的麻烦,我使用了 https://github.com/wavedrom/vcdrom 这个项目的后端进行修改。\\n\\nwasm 解析器\\n\\n原项目写得过于紧凑并不适合进行修改因此需要修改一部分代码。\\n\\n整合项目在 https://github.com/Digital-EDA/digital-vcd-parser', metadata={'source': 'docs/72.md', 'start_index': 0}),\n",
" (Document(page_content='TODO\\n\\n找到所有 vcd item 的 type 类型可参考https://pyvcd.readthedocs.io/en/latest/vcd.common.html\\n\\nbug\\n\\n详见飞书文档https://nc-ai-lab.feishu.cn/wiki/Z4AxwU1SdilATAk7GuvcYkIDnwh\\n\\n流程\\n\\n目前需要为Digital-IDE设计一个render用于显示VCD文件。\\n\\nIEEE 1364定义VCD(value change dump)文件是含已选变量(信号)的值变化信息存储文件。\\n\\nwavedrom在GitHub上开发过vcd和hdl的wavedrom脚本格式可以用于进行DIDE的vcd可视化的二次开发目前发现为了实现vcd文件基本的仓库主要是下面这两个\\n\\nJS层与一个简单的前端https://github.com/wavedrom/vcdrom\\n\\nwasm 解析https://github.com/wavedrom/vcd\\n\\n打算基于这两个仓库二次开发一个好用的vcd渲染模块再加入DIDE中。\\n\\n开发思路\\n\\nvcd 的渲染器分为如下几步进行开发。\\n\\nmermaid\\ngraph LR\\na(读取)-->b(渲染)-->c(解析)\\n\\nVCD 读取和解析并不难,难在如何快速安全地读取,对于一些长时间的模拟和仿真而言, vcd 文件可能会非常大。因此vcd 需要分块读取,分块解析,为了避免这些不必要的麻烦,我使用了 https://github.com/wavedrom/vcdrom 这个项目的后端进行修改。\\n\\nwasm 解析器\\n\\n原项目写得过于紧凑并不适合进行修改因此需要修改一部分代码。\\n\\n整合项目在 https://github.com/Digital-EDA/digital-vcd-parser', metadata={'source': '../docs/kirigaya.cn/72.md', 'start_index': 0}),\n",
" 0.55531096),\n",
" (Document(page_content='cap.release()\\nout.release()\\ncv2.destroyAllWindows()\\n```\\n\\n其中FORMAT代表目标视频的编码格式目前我还遇到了很多bug已经确定的FORMAT和编码关系的对照表如下\\n\\n目标视频编码格式 FORMAT 取值 mp4 \"mp4v\" aiv \"MJPG\"\\n\\n参考\\n\\n[1] python opencv写视频——cv2.VideoWriter()_翟羽嚄的博客-CSDN博客_cv2.videowriter', metadata={'source': 'docs/21.md', 'start_index': 711}),\n",
" (Document(page_content='cap.release()\\nout.release()\\ncv2.destroyAllWindows()\\n```\\n\\n其中FORMAT代表目标视频的编码格式目前我还遇到了很多bug已经确定的FORMAT和编码关系的对照表如下\\n\\n目标视频编码格式 FORMAT 取值 mp4 \"mp4v\" aiv \"MJPG\"\\n\\n参考\\n\\n[1] python opencv写视频——cv2.VideoWriter()_翟羽嚄的博客-CSDN博客_cv2.videowriter', metadata={'source': '../docs/kirigaya.cn/21.md', 'start_index': 711}),\n",
" 0.71963197)]"
]
},
"execution_count": 11,
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
@ -88,10 +98,31 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": []
"outputs": [
{
"data": {
"text/plain": [
"[(Document(page_content='问题标题 基础教程太少\\n除了b站的两个教程外建议多出文档以及示例工程一个刚接触FPGA但有不熟悉vivado的萌新就指望这个加快学习进度了\\n请问B站教程标题是啥我搜digital-ide啥也搜不到\\n请问B站教程标题是啥我搜digital-ide啥也搜不到\\n《Digital-IDE使用教程(一个DDS的实现)》\\n《Digital-IDE应用之FM调制解调》\\n请问B站教程标题是啥我搜digital-ide啥也搜不到\\n《Digital-IDE使用教程(一个DDS的实现)》 《Digital-IDE应用之FM调制解调》\\n好的谢谢\\n那个教程是好早之前我出的了新的版本一直不稳定今年稳定之后会出后续教程。\\nDigital IDE 的使用教程可以看\\n官方文档为 https://sterben.nitcloud.cn/zh/ 但是目前文档不是很完善。', metadata={'source': '../docs/digital-issue/issue-47/issue.md', 'start_index': 0}),\n",
" 0.63593847),\n",
" (Document(page_content='home: true\\nheroImage: /icon.png\\ndescription: Vscode 平台上的 ASIC & FPGA 开发扩展\\nactionText: 快速开始 🐳\\nactionLink: /zh/guide/introduction\\nfeatures:\\n- title: ✨ HDL 语言支持\\n details: 支持 verilog, vhdl, systemverilog, tcl 脚本等\\n- title: 🎯 项目管理\\n details: 在你的项目中查看结构化的 HDL 文件\\n- title: 🛠️ 额外的工具\\n details: FSM, Netlist, 一键仿真, 文档化,让你的编程体验更加舒坦。\\n\\n::: slot footer\\nMIT Licensed | Copyright © 2018-present Digital-EDA\\n:::', metadata={'source': '../docs/digital-document/index.md', 'start_index': 0}),\n",
" 0.7582667),\n",
" (Document(page_content='TODO\\n\\n找到所有 vcd item 的 type 类型可参考https://pyvcd.readthedocs.io/en/latest/vcd.common.html\\n\\nbug\\n\\n详见飞书文档https://nc-ai-lab.feishu.cn/wiki/Z4AxwU1SdilATAk7GuvcYkIDnwh\\n\\n流程\\n\\n目前需要为Digital-IDE设计一个render用于显示VCD文件。\\n\\nIEEE 1364定义VCD(value change dump)文件是含已选变量(信号)的值变化信息存储文件。\\n\\nwavedrom在GitHub上开发过vcd和hdl的wavedrom脚本格式可以用于进行DIDE的vcd可视化的二次开发目前发现为了实现vcd文件基本的仓库主要是下面这两个\\n\\nJS层与一个简单的前端https://github.com/wavedrom/vcdrom\\n\\nwasm 解析https://github.com/wavedrom/vcd\\n\\n打算基于这两个仓库二次开发一个好用的vcd渲染模块再加入DIDE中。\\n\\n开发思路\\n\\nvcd 的渲染器分为如下几步进行开发。\\n\\nmermaid\\ngraph LR\\na(读取)-->b(渲染)-->c(解析)\\n\\nVCD 读取和解析并不难,难在如何快速安全地读取,对于一些长时间的模拟和仿真而言, vcd 文件可能会非常大。因此vcd 需要分块读取,分块解析,为了避免这些不必要的麻烦,我使用了 https://github.com/wavedrom/vcdrom 这个项目的后端进行修改。\\n\\nwasm 解析器\\n\\n原项目写得过于紧凑并不适合进行修改因此需要修改一部分代码。\\n\\n整合项目在 https://github.com/Digital-EDA/digital-vcd-parser', metadata={'source': '../docs/kirigaya.cn/72.md', 'start_index': 0}),\n",
" 0.83628875)]"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"db.similarity_search_with_score(\n",
" query='digital ide 有什么教程吗',\n",
" k=3\n",
")"
]
},
{
"cell_type": "code",

6
package-lock.json generated Normal file
View File

@ -0,0 +1,6 @@
{
"name": "Lagrange.RagBot",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}

1
prompt/__init__.py Normal file
View File

@ -0,0 +1 @@
from prompt.core import PromptEngine

270
prompt/core.py Normal file
View File

@ -0,0 +1,270 @@
from __future__ import annotations
from dataclasses import dataclass
import warnings
import random
import math
from abc import ABC, abstractmethod
import yaml
import json5
from loguru import logger
logger.add(
sink='./logs/prompt.log',
level='DEBUG',
rotation='00:00',
retention='7 days',
compression='zip',
encoding='utf-8',
enqueue=True,
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}"
)
@dataclass(frozen=True)
class IntentNode:
name: str
description: str | None
children: list[IntentNode]
parent: IntentNode | None
stories: list[Story]
@dataclass(frozen=True)
class Story:
message: str
intent: str
class PromptEngine:
path: str
schema: IntentNode | None
stories: list[Story]
rejects: list[str]
intent2id: dict[str, int]
id2intent: dict[int, str]
name2node: dict[str, IntentNode]
def __init__(self, path: str) -> None:
self.path = path
self.config = yaml.load(open(path, 'r', encoding='utf-8'), yaml.Loader)
self.intent2id = {}
self.id2intent = {}
self.name2node = {}
self.schema = self.handle_schema(self.config['schema'])
self.stories = self.handle_stories(self.config['stories'])
self.rejects = self.handle_rejects(self.config['rejects'])
def handle_schema(self, raw_schema: dict) -> IntentNode:
raw_root = raw_schema.get('root', None)
if raw_root is None:
warnings.warn('schema must have a root node as the beginning, otherwise intent recogition will not work')
return None
current_layers: list[tuple[dict, IntentNode | None]] = [(raw_root, None)]
nodes: list[IntentNode] = []
# 层次遍历
while len(current_layers) > 0:
new_current_layers: list[tuple[dict, IntentNode | None]] = []
for raw_node, intent_node in current_layers:
name = raw_node.get('name', None)
children = raw_node.get('children', None)
description = raw_node.get('description', None)
if name is None:
raise NameError('you must specify a name in story item, current item : {}'.format(raw_node))
if children is None:
children = []
if name != 'root' and name not in self.intent2id:
assign_id = len(self.intent2id)
self.intent2id[name] = assign_id
self.id2intent[assign_id] = name
node = IntentNode(name, description, [], intent_node, [])
self.name2node[name] = node
nodes.append(node)
if intent_node:
intent_node.children.append(node)
for raw_node in children:
new_current_layers.append((raw_node, node))
current_layers.clear()
current_layers.extend(new_current_layers)
return nodes[0]
def handle_stories(self, raw_stories: list[dict]) -> list[Story]:
stories: list[Story] = []
for pair in raw_stories:
message = pair.get('message', None)
intent = pair.get('intent', None)
if intent not in self.intent2id:
warnings.warn('{} is not the intent you declare in schema, so this pair will be ignored'.format(intent))
continue
if message and intent:
story = Story(message, intent)
node = self.name2node.get(intent)
node.stories.append(story)
stories.append(story)
return stories
def merge_stories_from_yml(self, path: str):
config = yaml.load(open(path, 'r', encoding='utf-8'), Loader=yaml.Loader)
stories = config.get('stories', [])
self.merge_stories(stories)
def merge_stories(self, raw_stories: list[dict]):
stories = self.handle_stories(raw_stories)
self.stories.extend(stories)
def handle_rejects(self, raw_rejects: list[str]) -> list[str]:
rejects = []
for reject in raw_rejects:
rejects.append(reject)
return rejects
def generate_chunk(self, stories: list[Story]) -> tuple[str]:
prompts = []
for story in stories:
prompts.append('Message: ' + story.message.strip())
intent_id = self.intent2id.get(story.intent)
prompts.append('Intent: { id: %s }' % (intent_id))
prompts.pop()
user_content = '\n'.join(prompts) + '\n' + 'Intent: '
assistant_content = '{id : %s}' % (intent_id)
return user_content, assistant_content
def generate_llm_message(self, question: str, intent: IntentNode = None, chunk_size: int = 5, max_chunk_num: int = 10) -> list[dict]:
if intent is None:
intent = self.schema
story_cache = []
for node in intent.children:
story_cache.extend(node.stories)
random.shuffle(story_cache)
chunk_num = math.ceil(len(story_cache) / chunk_size)
message = []
for chunk_id in range(chunk_num):
start = chunk_id * chunk_size
end = min(len(story_cache), start + chunk_size)
chunk = story_cache[start: end]
user_content, assistant_content = self.generate_chunk(chunk)
message.append({
'role': 'user',
'content': user_content
})
message.append({
'role': 'assistant',
'content': assistant_content
})
if len(message) / 2 >= max_chunk_num:
break
message.append({
'role': 'user',
'content': question + '\nIntent: '
})
# 创建开头的预设
preset = 'Label a users message from a conversation with an intent. Reply ONLY with the name of the intent.'
intent_preset = ['The intent should be one of the following:']
for node in intent.children:
intent_id = self.intent2id.get(node.name)
intent_preset.append('- {}'.format(intent_id))
intent_preset = '\n'.join(intent_preset)
message[0]['content'] = preset + '\n' + intent_preset + '\n' + message[0]['content']
return message
class TreeIntent(ABC):
path: str
engine: PromptEngine
def __init__(self, path: str) -> None:
self.path = path
self.engine = PromptEngine(path)
@abstractmethod
def call_llm(self, message: list[dict]) -> str:
"""
example of message:
[
{
"role": "user",
"content": "Message: 大佬们为啥我的digital ide启动之后所有功能都没启动捏我配置了property文件然后插件的vivado路经和modelsim路经都加上了\nIntent: "
},
{
"role": "assistant",
"content": "{ id: 0 }"
},
{
"role": "user",
"content": "话说digital-ide打开大的verilog卡死了\nIntent: "
},
{
"role": "assistant",
"content": "{ id: 1 }"
}
]
"""
pass
def purify_json(self, json_string: str):
stack = []
start_index = None
for i, ch in enumerate(json_string):
if ch == '{':
if len(stack) == 0:
start_index = i
stack.append(ch)
elif ch == '}':
stack.pop()
if len(stack) == 0:
return json_string[start_index: i + 1]
else:
pass
return json_string
def try_generate_intent_id(self, question: str, intent: IntentNode = None, chunk_size: int = 5, max_chunk_num: int = 10, retry: int = 3) -> int | None:
engine = self.engine
for i in range(retry):
try:
message = engine.generate_llm_message(question, intent, chunk_size, max_chunk_num)
result = self.call_llm(message)
result = self.purify_json(result)
result = json5.loads(result)
intent_id = result['id']
return int(intent_id)
except Exception as e:
continue
return None
def inference(self, question: str, chunk_size: int = 5, max_chunk_num: int = 10) -> list[IntentNode] | None:
root_node = self.engine.schema
results: list[IntentNode] = []
engine = self.engine
stack: list[IntentNode] = [root_node]
while len(stack) > 0:
node = stack.pop()
intent_id = self.try_generate_intent_id(question, node)
if intent_id is None:
logger.warning('fail to generate intent id from message, check log file for details')
logger.debug(json5.dumps({ 'question': question, 'node.name': node.name }, ensure_ascii=False))
return None
if intent_id not in engine.id2intent:
logger.warning('inferred intent id {} not in the list of engine.id2intent {}'.format(intent_id, list(engine.id2intent.keys())))
logger.debug(json5.dumps({ 'question': question, 'node.name': node.name, 'intent_id': intent_id }, ensure_ascii=False))
return None
intent_name = engine.id2intent[intent_id]
intent_node = engine.name2node[intent_name]
results.append(intent_node)
if len(intent_node.children) >= 2:
stack.append(intent_node)
return results
if __name__ == '__main__':
prompt_engine = PromptEngine('./config/story.yml')
prompt_engine.merge_stories_from_yml('./config/github-issue.story.yml')
print(prompt_engine.id2intent)

83
prompt/erine.py Normal file
View File

@ -0,0 +1,83 @@
import os
import json
import requests as r
from core import TreeIntent, logger
class ErineIntent(TreeIntent):
api_key: str
secret_key: str
access_token: str
def __init__(self, path: str, api_key: str = None, secret_key: str = None) -> None:
super().__init__(path)
self.api_key = api_key or os.environ['BAIDU_API_KEY']
self.secret_key = secret_key or os.environ['BAIDU_SECRET_KEY']
try:
self.access_token = self.get_access_token()
except Exception as e:
raise ValueError('fail to get access token in initialization')
def get_access_token(self):
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
api_key = self.api_key
secret_key = self.secret_key
url = f'https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id={api_key}&client_secret={secret_key}'
payload = json.dumps("")
res = r.post(
url=url,
data=payload,
headers=headers
)
resJson = res.json()
access_token = resJson.get('access_token')
assert isinstance(access_token, str), 'access_token 获取失败,详细信息' + str(resJson)
return access_token
def post_message(self, message: list[dict]):
headers = {
'Content-Type': 'application/json'
}
payload = json.dumps({
'messages': message,
'penalty_score': 2.0
})
url = 'https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/ernie-lite-8k?access_token=' + self.access_token
return r.post(url, headers=headers, data=payload)
def call_llm(self, message: list[dict]) -> str:
try:
res = self.post_message(message)
except Exception:
self.access_token = self.get_access_token()
res = self.post_message(message)
try:
return res.json()['result']
except Exception as e:
logger.error('get error when parse response of wenxinyiyan: ' + str(e))
logger.debug(res.json())
return None
if __name__ == '__main__':
erine = ErineIntent('./config/story.yml')
result = []
for i in range(20):
nodes = erine.inference('那不就是rv芯片往上堆扩展吗')
if nodes is None:
print('none -> ohters')
else:
node = nodes[0]
result.append(node.name)
print(node.name)
from collections import Counter
print(Counter(result))

68
prompt/log.py Normal file
View File

@ -0,0 +1,68 @@
from __future__ import annotations
from typing import Callable
import asyncio
import sys
from weakref import WeakSet
import json
class AsyncWorker:
worker_fn: Callable
task_pool: WeakSet
loop: asyncio.AbstractEventLoop | None
cb: Callable | None
def __init__(self, worker_fn: Callable, cb: Callable = None) -> None:
self.worker_fn = worker_fn
self.loop = None
self.cb = cb
self.task_pool = WeakSet()
def dispatch(self, *args):
try:
loop = self.loop or asyncio.get_event_loop()
except RuntimeError:
return
coro = self.worker_fn(*args)
task = loop.create_task(coro)
def coor_cb(future: asyncio.Future):
pass
task.add_done_callback(coor_cb)
self.task_pool.add(task)
def stop(self):
for task in self.task_pool:
task.cancel()
def complete_all_tasks(self):
return [self.complete_task(task) for task in self.task_pool]
async def complete_task(self, task: asyncio.Task):
loop = asyncio.get_event_loop()
if task.get_loop() != loop:
return
try:
await task
except Exception:
pass
async def worker(n, m):
for i in range(n):
for j in range(m):
pass
print('finish')
async_worker = AsyncWorker(worker)
async_worker.dispatch(1000, 1000)
async_worker.complete_all_tasks()
import time
while True:
time.sleep(1)
print('load')

0
rag/__init__.py Normal file
View File

11
rag/api/__init__.py Normal file
View File

@ -0,0 +1,11 @@
from .admin import logger, app
from .config import necessary_files
from .constant import StatusCode, MsgCode
__all__ = [
logger,
app,
necessary_files,
StatusCode,
MsgCode
]

15
rag/api/admin.py Normal file
View File

@ -0,0 +1,15 @@
from flask import Flask, request, jsonify
from loguru import logger
logger.add(
sink='./logs/rag.log',
level='DEBUG',
rotation='00:00',
retention='7 days',
compression='zip',
encoding='utf-8',
enqueue=True,
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}"
)
app = Flask(__file__)

8
rag/api/config.py Normal file
View File

@ -0,0 +1,8 @@
necessary_files = {
'vecdb-config': './config/vecdb.yml',
'intent-story': './config/story.yml',
'issue-story': './config/github-issue.story.yml',
'blog-vecdb-data': './blog-vecdb/index.faiss',
'blog-vecdb-model': './blog-vecdb/index.pkl',
'intent-classifier': './model/intent.enn.pth'
}

View File

@ -6,6 +6,7 @@ class StatusCode(Enum):
server_error = 4002
resource_not_found = 4003
timeout = 4004
process_error = 4005
class MsgCode(Enum):
success = '请求处理成功'

6
rag/db/__init__.py Normal file
View File

@ -0,0 +1,6 @@
from .embedding import db, embedding
__all__ = [
db,
embedding
]

View File

@ -1,58 +1,26 @@
from flask import Flask, request, jsonify
import json
import os
import sys
sys.path.append(os.path.abspath('.'))
from embedding import db
from url_mapping import urlmapping
from constant import StatusCode, MsgCode
from rag.api.admin import app, logger
from rag.api.config import necessary_files
from rag.services.intent import *
from rag.services.vecdb import *
app = Flask(__file__)
def assert_resource(path: str):
assert os.path.exists(path), '{} 不存在'.format(file)
@app.route('/vecdb/similarity_search_with_score', methods=['post'])
def post_similarity_search_with_score():
params = request.data.decode('utf-8')
params: dict = json.loads(params)
result_data = []
query = params.get('query', None)
if query is None:
response = jsonify({
'code': StatusCode.user_error.value,
'data': result_data,
'msg': MsgCode.query_not_empty.value
})
response.status_code = StatusCode.success.value
return response
k = int(params.get('k', 3))
results = db.similarity_search_with_score(query=query, k=k)
for doc, score in results:
page_content = doc.page_content
meta = doc.metadata
source = meta.get('source', '')
if len(source) > 0:
source = urlmapping.url_from_mapping(source)
result_data.append({
'content': page_content.strip(),
'meta': meta,
'source': source,
'score': float(score)
})
response = jsonify({
'code': StatusCode.success.value,
'data': result_data,
'msg': StatusCode.success.value
})
response.status_code = StatusCode.success.value
return response
for file in necessary_files.values():
assert_resource(file)
if __name__ == '__main__':
from gevent import pywsgi
import yaml
config: dict = yaml.load(open('./config/vecdb.yml'), Loader=yaml.Loader)
addr = config.get('addr', '127.0.0.1')
port = int(config.get('port', 8081))
server = pywsgi.WSGIServer((addr, port), app)
logger.info('RAG 系统运行在 http://{}:{}'.format(addr, port))
server.serve_forever()

5
rag/model/__init__.py Normal file
View File

@ -0,0 +1,5 @@
from .enn import LinearEnn
__all__ = [
LinearEnn
]

88
rag/model/enn.py Normal file
View File

@ -0,0 +1,88 @@
import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np
class LinearEnn(nn.Module):
in_dim: int
out_dim: int
alpha_kl: float
def __init__(self, in_dim: int, out_dim: int, focal: int, alpha_kl: float):
super().__init__()
self.in_dim = in_dim
self.out_dim = out_dim
self.alpha_kl = alpha_kl
self.focal = focal
self.classifier = nn.Sequential(
nn.Linear(in_dim, out_dim),
nn.ELU(),
)
def forward(self, inputs: torch.FloatTensor) -> tuple[torch.FloatTensor, torch.FloatTensor]:
logits = self.classifier(inputs)
evidence = torch.exp(logits)
prob = F.normalize(evidence + 1, p=1, dim=1)
return evidence, prob
def criterion(self, evidence: torch.FloatTensor, label: torch.LongTensor) -> torch.FloatTensor:
if len(label.shape) == 1:
label = F.one_hot(label, self.out_dim)
alpha = evidence + 1
alpha_0 = alpha.sum(1).unsqueeze(-1).repeat(1, self.out_dim)
loss_ece = torch.sum(label * (torch.digamma(alpha_0) - torch.digamma(alpha)), dim=1)
loss_ece = torch.mean(loss_ece)
if self.alpha_kl > 0:
tilde_alpha = label + (1 - label) * alpha
uncertainty_alpha = torch.ones_like(tilde_alpha).cuda()
estimate_dirichlet = torch.distributions.Dirichlet(tilde_alpha)
uncertainty_dirichlet = torch.distributions.Dirichlet(uncertainty_alpha)
kl = torch.distributions.kl_divergence(estimate_dirichlet, uncertainty_dirichlet)
loss_kl = torch.mean(kl)
else:
loss_kl = 0
return loss_ece + self.alpha_kl * loss_kl
def predict(self, inputs: torch.FloatTensor | list[list[float]] | np.ndarray) -> tuple[torch.FloatTensor, torch.FloatTensor]:
"""
返回每个类别的预测概率 当前的预测的不确定度 uncertainty
"""
if not isinstance(inputs, torch.FloatTensor):
inputs = torch.FloatTensor(inputs)
with torch.no_grad():
evidence, prob = self.forward(inputs)
alpha = evidence + 1
S = alpha.sum(dim=1)
u = self.out_dim / S
return prob, u
def train_enn(enn_model: LinearEnn, embedding: np.ndarray | torch.FloatTensor, labels: np.ndarray | torch.LongTensor, bs: int = 64, lr: float = 1e-3, epoch: int = 100):
optimizer = torch.optim.AdamW(enn_model.parameters(), lr=lr)
sample_num = len(embedding)
sample_indice = np.arange(sample_num)
bs_num = int(np.ceil(sample_num / bs))
training_losses = []
for i in range(epoch):
alpha_kl = min(0.9, i / 10)
np.random.shuffle(sample_indice)
train_loss = 0
for bs_i in range(bs_num):
start = bs_i * bs
end = min(sample_num, start + bs)
data_indice = sample_indice[start: end]
data = torch.FloatTensor(embedding[data_indice])
label = torch.LongTensor(labels[data_indice])
evidence, prob = enn_model(data)
loss = enn_model.criterion(evidence, label)
train_loss += loss.item()
optimizer.zero_grad()
loss.backward()
optimizer.step()
training_losses.append(train_loss / bs_num)

0
rag/services/__init__.py Normal file
View File

130
rag/services/intent.py Normal file
View File

@ -0,0 +1,130 @@
from flask import Flask, request, jsonify
import numpy as np
import joblib
import json
import torch
from rag.db.embedding import embedding
from rag.api.constant import StatusCode, MsgCode
from rag.api.admin import app
from rag.api.admin import logger
from rag.api.config import necessary_files
from rag.model.enn import LinearEnn, train_enn
import sys
import os
sys.path.append(os.path.abspath('.'))
from prompt import PromptEngine
class IntentRecogition:
def __init__(self) -> None:
self.engine = PromptEngine(necessary_files['intent-story'])
self.classifier = LinearEnn(in_dim=768, out_dim=7, focal=0, alpha_kl=0)
self.classifier.load_state_dict(torch.load(necessary_files['intent-classifier']))
def get_intent_recogition(self, query: str) -> dict:
query_embed = embedding.embed_documents([query])
prob, u = self.classifier.predict(query_embed)
result_id = prob.argmax(dim=1)
u = u.item()
result_id = int(result_id.item())
return {
'id': int(result_id),
'name': self.engine.id2intent[result_id],
'uncertainty': float(u)
}
intent_recogition = IntentRecogition()
@app.route('/intent/reload-embedding-mapping', methods=['post'])
def reload_embedding_mapping():
try:
intent_recogition.classifier.load_state_dict(torch.load(necessary_files['intent-classifier']))
except Exception as e:
response = jsonify({
'code': StatusCode.process_error.value,
'data': str(e),
'msg': MsgCode.query_not_empty.value
})
response.status_code = StatusCode.success.value
return response
response = jsonify({
'code': StatusCode.success.value,
'data': 'load model from ' + necessary_files['intent-classifier'],
'msg': StatusCode.success.value
})
response.status_code = StatusCode.success.value
return response
# TODO: 删除该接口
@app.route('/intent/retrain-embedding-mapping', methods=['post'])
def retrain_embedding_mapping():
# engine = PromptEngine(necessary_files['intent-story'])
# engine.merge_stories_from_yml(necessary_files['issue-story'])
# sentences = []
# labels = []
# for story in engine.stories:
# sentences.append(story.message)
# labels.append(engine.intent2id[story.intent])
# try:
# labels = np.array(labels)
# embed = embedding.embed_documents(sentences)
# enn_model = intent_recogition.classifier
# train_enn(enn_model, embed, labels, bs=64, lr=1e-3, epoch=100)
# torch.save(enn_model.state_dict(), necessary_files['intent-classifier'])
# except Exception as e:
# response = jsonify({
# 'code': StatusCode.process_error.value,
# 'data': str(e),
# 'msg': MsgCode.query_not_empty.value
# })
# response.status_code = StatusCode.success.value
# return response
response = jsonify({
'code': StatusCode.success.value,
'data': 'save data to ' + necessary_files['intent-classifier'],
'msg': StatusCode.success.value
})
response.status_code = StatusCode.success.value
return response
@app.route('/intent/get-intent-recogition', methods=['post'])
def get_intent_recogition():
params = request.data.decode('utf-8')
params: dict = json.loads(params)
result_data = {}
query = params.get('query', None)
if query is None:
response = jsonify({
'code': StatusCode.user_error.value,
'data': result_data,
'msg': MsgCode.query_not_empty.value
})
response.status_code = StatusCode.success.value
return response
result = intent_recogition.get_intent_recogition(query)
logger_chunk = json.dumps({
'query': query,
'intent': result
}, ensure_ascii=False)
logger.debug(logger_chunk)
response = jsonify({
'code': StatusCode.success.value,
'data': result,
'msg': StatusCode.success.value
})
response.status_code = StatusCode.success.value
return response

48
rag/services/vecdb.py Normal file
View File

@ -0,0 +1,48 @@
from flask import Flask, request, jsonify
import json
from rag.api.admin import app
from rag.db.embedding import db
from rag.api.constant import StatusCode, MsgCode
from rag.utils.url_mapping import urlmapping
@app.route('/vecdb/similarity_search_with_score', methods=['post'])
def post_similarity_search_with_score():
params = request.data.decode('utf-8')
params: dict = json.loads(params)
result_data = []
query = params.get('query', None)
if query is None:
response = jsonify({
'code': StatusCode.user_error.value,
'data': result_data,
'msg': MsgCode.query_not_empty.value
})
response.status_code = StatusCode.success.value
return response
k = int(params.get('k', 3))
results = db.similarity_search_with_score(query=query, k=k)
for doc, score in results:
page_content = doc.page_content
meta = doc.metadata
source = meta.get('source', '')
if len(source) > 0:
source = urlmapping.url_from_mapping(source)
result_data.append({
'content': page_content.strip(),
'meta': meta,
'source': source,
'score': float(score)
})
response = jsonify({
'code': StatusCode.success.value,
'data': result_data,
'msg': StatusCode.success.value
})
response.status_code = StatusCode.success.value
return response

View File

@ -1,14 +0,0 @@
import requests as r
import json
payload = json.dumps({
'query': '一键生成 requirements.txt ',
'k': 3
})
res = r.post('http://localhost:8081/vecdb/similarity_search_with_score', data=payload)
print(res.status_code)
if res.status_code == 200:
print(res.json())

3
rag/utils/__init__.py Normal file
View File

@ -0,0 +1,3 @@
from .url_mapping import urlmapping
__all__ = [ urlmapping ]

View File

@ -13,6 +13,7 @@ class UrlMappingRegister:
return lambda func: register(start, func)
def url_from_mapping(self, source: str) -> str:
source = source.lstrip('.').lstrip('/')
for pattern in self.startsWith_patterns:
func = self.startsWith_patterns[pattern]
if source.startswith(pattern):
@ -32,6 +33,7 @@ def kirigaya_cn(source: str) -> str:
template = f'https://kirigaya.cn/blog/article?seq={article_id}'
return template
# 样例: docs/digital-document/guide/quick-start.md
# 目标: https://sterben.nitcloud.cn/zh/guide/quick-start.html
@urlmapping.startsWith('docs/digital-document')
@ -42,6 +44,16 @@ def digital_document(source: str) -> str:
return router
# 样例: docs/digital-issue/issue-2/issue.md
# 目标: https://github.com/Digital-EDA/Digital-IDE/issues/2
@urlmapping.startsWith('docs/digital-issue')
def digital_document(source: str) -> str:
parts = source.split('/')
issue_id = parts[2].split('-')[-1]
router = f'https://github.com/Digital-EDA/Digital-IDE/issues/{issue_id}'
return router
if __name__ == '__main__':
print(kirigaya_cn('docs/kirigaya.cn/129.md'))
print(kirigaya_cn('docs/kirigaya.cn/21.md'))

View File

@ -1,5 +0,0 @@
Flask==3.0.3
gevent==24.2.1
langchain==0.2.1
langchain_community==0.2.1
Requests==2.32.2

110
scripts/ernie.py Normal file
View File

@ -0,0 +1,110 @@
import requests as r
import json
api_key = '9J5qFTYr6wPRxkoVoXycnoWf'
secret_key = 'Xa6eJelStx5i7Ft3qQH0NAT6AvOkqhkH'
def get_access_token():
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
url = f'https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id={api_key}&client_secret={secret_key}'
payload = json.dumps("")
res = r.post(
url=url,
data=payload,
headers=headers
)
resJson = res.json()
access_token = resJson.get('access_token')
assert isinstance(access_token, str), 'access_token 获取失败,详细信息' + str(resJson)
return access_token
access_token = get_access_token()
text = open('./template.txt', 'r', encoding='utf-8').read()
t2 = open('./t2.txt', 'r', encoding='utf-8').read()
payload = json.dumps({
# "messages": [
# {
# "role": "user",
# "content": text
# },
# {
# "role": "assistant",
# "content": "{ id: 3 }"
# },
# {
# "role": "user",
# "content": "Message: 大佬们为啥我的digital ide启动之后所有功能都没启动捏我配置了property文件然后插件的vivado路经和modelsim路经都加上了\nIntent: "
# },
# {
# "role": "assistant",
# "content": "{ id: 0 }"
# },
# {
# "role": "user",
# "content": "话说digital-ide打开大的verilog卡死了\nIntent: "
# },
# {
# "role": "assistant",
# "content": "{ id: 1 }"
# },
# {
# 'role': 'user',
# "content": "请问一下第一次点击对文件仿真可以出波形文件再次点击的时候就会提示unknown module type了。是哪个配置没配置好\nIntent: "
# },
# ]
'messages': [
# {
# 'role': 'user',
# 'content': 'Label a users message from a conversation with an intent. Reply ONLY with the name of the intent.\nThe intent should be one of the following:\n- 1\n- 2\n- 3\n- 4\nMessage: surface了解一下\nIntent: { id: 4 }\nMessage: Metals一开直接报错\nIntent: { id: 4 }\nMessage: 大佬们为啥我的digital ide启动之后所有功能都没启动捏我配置了property文件然后插件的vivado路经和modelsim路经都加上了\nIntent: { id: 1 }\nMessage: 请问 property.json 如何配置?\nIntent: { id: 1 }\nMessage: 请问一下第一次点击对文件仿真可以出波形文件再次点击的时候就会提示unknown module type了。是哪个配置没配置好\nIntent: '
# },
# {
# 'role': 'assistant',
# 'content': '{id : 1}'
# },
# {
# 'role': 'user',
# 'content': 'Message: 话说digital-ide打开大的verilog卡死了\nIntent: { id: 2 }\nMessage: 帮我上传一下这份数据\nIntent: { id: 3 }\nMessage: 我的自动补全无法使用是不是有bug\nIntent: { id: 2 }\nMessage: 这群要被chisel夺舍了吗\nIntent: '
# },
# {
# 'role': 'assistant',
# 'content': '{id : 4}'
# },
{
"role": "user",
"content": "如何解决 digital ide 无法载入配置文件的问题?\nIntent: "
}
]
})
headers = {
'Content-Type': 'application/json'
}
url = 'https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/ernie-lite-8k?access_token=' + access_token
res = r.post(url, headers=headers, data=payload)
print(res.json())
# print(res.json()['result'])
# cache = []
# for line in res.iter_lines():
# line_text: str = line.decode('UTF-8')
# if line_text.startswith('data:'):
# iter_json = json.loads(line_text.lstrip('data: '))
# result: str = iter_json['result']
# cache.append(result)
# if result.endswith('。') or result.endswith('.'):
# sentence = ''.join(cache).strip()
# print(sentence)
# cache.clear()
# if len(cache) > 0:
# print(''.join(cache).strip())

123
scripts/make_ts_api.py Normal file
View File

@ -0,0 +1,123 @@
import markdown
import re
from bs4 import BeautifulSoup, Tag
"""/**
* @description 发送私聊消息
* @param user_id 对方 QQ
* @param message 要发送的内容
* @param auto_escape 消息内容是否作为纯文本发送即不解析 CQ 只在 message 字段是字符串时有效
*/
function send_private_msg(user_id: number, message: Lagrange.Message, auto_escape: boolean = false) {
return {
action: 'send_private_msg',
params: { user_id, message, auto_escape }
};
}"""
template = """/**
* @description %s%s */
export function %s(%s) {
return {
action: '%s',
params: { %s }
};
}
"""
onebot_document = open('./scripts/onebot.md', 'r', encoding='utf-8').read()
html = markdown.markdown(onebot_document)
soup = BeautifulSoup(html, 'html.parser')
def snake_to_camel(s, capitalize_first_letter=False):
components = s.split('_')
camel = ''.join(x.capitalize() for x in components)
if not capitalize_first_letter:
camel = camel[0].lower() + camel[1:]
return camel
def next_node(el: Tag):
p = el.next_sibling
while len(p.text.strip()) == 0:
p = p.next_sibling
return p
tss = """/**
* @author 锦恢
* @email 1193466151@qq.com
* @description Lagrange.Core 前端接口
* @comment 接口调用详细参考文档
* - https://github.com/botuniverse/onebot-11/blob/master/communication/ws.md
*/
import * as Lagrange from '../type';
"""
for el in soup.find_all('h2'):
el: Tag
function_name = None
function_desc = None
for child in el.children:
if child.name == 'code':
function_name = child.text
elif child.name is None:
function_desc = child.text
if function_name and function_desc:
ts_func_name = snake_to_camel(function_name)
title2 = next_node(el)
table = next_node(title2)
count = 0
params = []
for line in table.text.strip().split('\n'):
count += 1
if count >= 3:
splits = [l for l in line.split('|') if len(l.strip()) > 0]
print(splits)
if len(splits) == 4:
param = {
'name': splits[0].strip(),
'type': splits[1].strip().split()[0],
'default': splits[2].strip(),
'desc': splits[3].strip()
}
elif len(splits) == 3:
param = {
'name': splits[0].strip(),
'type': splits[1].strip().split()[0],
'default': '-',
'desc': splits[2].strip()
}
if param['type'] == 'message':
param['type'] = 'string | Lagrange.Send.Default[]'
params.append(param)
t1 = function_desc.strip()
t2 = '\n'
for param in params:
t2 += ' * @param {} {}\n'.format(param['name'], param['desc'])
t3 = ts_func_name
t4 = []
for param in params:
if param['default'] == '-':
t4.append('{}: {}'.format(param['name'], param['type']))
for param in params:
if param['default'] != '-':
t4.append('{}: {} = {}'.format(param['name'], param['type'], param['default']))
t4 = ', '.join(t4)
t5 = function_name
t6 = [param['name'] for param in params]
t6 = ', '.join(t6)
ts_code = template % (t1, t2, t3, t4, t5, t6)
tss += ts_code
open('./bot/api/onebot.ts', 'w', encoding='utf-8').write(tss)

View File

@ -0,0 +1,123 @@
import markdown
import re
from bs4 import BeautifulSoup, Tag
"""/**
* @description 发送私聊消息
* @param user_id 对方 QQ
* @param message 要发送的内容
* @param auto_escape 消息内容是否作为纯文本发送即不解析 CQ 只在 message 字段是字符串时有效
*/
function send_private_msg(user_id: number, message: Lagrange.Message, auto_escape: boolean = false) {
return {
action: 'send_private_msg',
params: { user_id, message, auto_escape }
};
}"""
template = """/**
* @description %s%s */
public %s(%s) {
return this.send({
action: '%s',
params: { %s }
});
}
"""
onebot_document = open('./scripts/onebot.md', 'r', encoding='utf-8').read()
html = markdown.markdown(onebot_document)
soup = BeautifulSoup(html, 'html.parser')
def snake_to_camel(s, capitalize_first_letter=False):
components = s.split('_')
camel = ''.join(x.capitalize() for x in components)
if not capitalize_first_letter:
camel = camel[0].lower() + camel[1:]
return camel
def next_node(el: Tag):
p = el.next_sibling
while len(p.text.strip()) == 0:
p = p.next_sibling
return p
tss = """/**
* @author 锦恢
* @email 1193466151@qq.com
* @description Lagrange.Core 前端接口
* @comment 接口调用详细参考文档
* - https://github.com/botuniverse/onebot-11/blob/master/communication/ws.md
*/
import * as Lagrange from '../type';
"""
for el in soup.find_all('h2'):
el: Tag
function_name = None
function_desc = None
for child in el.children:
if child.name == 'code':
function_name = child.text
elif child.name is None:
function_desc = child.text
if function_name and function_desc:
ts_func_name = snake_to_camel(function_name)
title2 = next_node(el)
table = next_node(title2)
count = 0
params = []
for line in table.text.strip().split('\n'):
count += 1
if count >= 3:
splits = [l for l in line.split('|') if len(l.strip()) > 0]
print(splits)
if len(splits) == 4:
param = {
'name': splits[0].strip(),
'type': splits[1].strip().split()[0],
'default': splits[2].strip(),
'desc': splits[3].strip()
}
elif len(splits) == 3:
param = {
'name': splits[0].strip(),
'type': splits[1].strip().split()[0],
'default': '-',
'desc': splits[2].strip()
}
if param['type'] == 'message':
param['type'] = 'string | Lagrange.Send.Default[]'
params.append(param)
t1 = function_desc.strip()
t2 = '\n'
for param in params:
t2 += ' * @param {} {}\n'.format(param['name'], param['desc'])
t3 = ts_func_name
t4 = []
for param in params:
if param['default'] == '-':
t4.append('{}: {}'.format(param['name'], param['type']))
for param in params:
if param['default'] != '-':
t4.append('{}: {} = {}'.format(param['name'], param['type'], param['default']))
t4 = ', '.join(t4)
t5 = function_name
t6 = [param['name'] for param in params]
t6 = ', '.join(t6)
ts_code = template % (t1, t2, t3, t4, t5, t6)
tss += ts_code
open('./scripts/onebot.ts', 'w', encoding='utf-8').write(tss)

634
scripts/onebot.md Normal file
View File

@ -0,0 +1,634 @@
# 公开 API
- [`send_private_msg` 发送私聊消息](#send_private_msg-发送私聊消息)
- [`send_group_msg` 发送群消息](#send_group_msg-发送群消息)
- [`send_msg` 发送消息](#send_msg-发送消息)
- [`delete_msg` 撤回消息](#delete_msg-撤回消息)
- [`get_msg` 获取消息](#get_msg-获取消息)
- [`get_forward_msg` 获取合并转发消息](#get_forward_msg-获取合并转发消息)
- [`send_like` 发送好友赞](#send_like-发送好友赞)
- [`set_group_kick` 群组踢人](#set_group_kick-群组踢人)
- [`set_group_ban` 群组单人禁言](#set_group_ban-群组单人禁言)
- [`set_group_anonymous_ban` 群组匿名用户禁言](#set_group_anonymous_ban-群组匿名用户禁言)
- [`set_group_whole_ban` 群组全员禁言](#set_group_whole_ban-群组全员禁言)
- [`set_group_admin` 群组设置管理员](#set_group_admin-群组设置管理员)
- [`set_group_anonymous` 群组匿名](#set_group_anonymous-群组匿名)
- [`set_group_card` 设置群名片(群备注)](#set_group_card-设置群名片群备注)
- [`set_group_name` 设置群名](#set_group_name-设置群名)
- [`set_group_leave` 退出群组](#set_group_leave-退出群组)
- [`set_group_special_title` 设置群组专属头衔](#set_group_special_title-设置群组专属头衔)
- [`set_friend_add_request` 处理加好友请求](#set_friend_add_request-处理加好友请求)
- [`set_group_add_request` 处理加群请求/邀请](#set_group_add_request-处理加群请求邀请)
- [`get_login_info` 获取登录号信息](#get_login_info-获取登录号信息)
- [`get_stranger_info` 获取陌生人信息](#get_stranger_info-获取陌生人信息)
- [`get_friend_list` 获取好友列表](#get_friend_list-获取好友列表)
- [`get_group_info` 获取群信息](#get_group_info-获取群信息)
- [`get_group_list` 获取群列表](#get_group_list-获取群列表)
- [`get_group_member_info` 获取群成员信息](#get_group_member_info-获取群成员信息)
- [`get_group_member_list` 获取群成员列表](#get_group_member_list-获取群成员列表)
- [`get_group_honor_info` 获取群荣誉信息](#get_group_honor_info-获取群荣誉信息)
- [`get_cookies` 获取 Cookies](#get_cookies-获取-cookies)
- [`get_csrf_token` 获取 CSRF Token](#get_csrf_token-获取-csrf-token)
- [`get_credentials` 获取 QQ 相关接口凭证](#get_credentials-获取-qq-相关接口凭证)
- [`get_record` 获取语音](#get_record-获取语音)
- [`get_image` 获取图片](#get_image-获取图片)
- [`can_send_image` 检查是否可以发送图片](#can_send_image-检查是否可以发送图片)
- [`can_send_record` 检查是否可以发送语音](#can_send_record-检查是否可以发送语音)
- [`get_status` 获取运行状态](#get_status-获取运行状态)
- [`get_version_info` 获取版本信息](#get_version_info-获取版本信息)
- [`set_restart` 重启 OneBot 实现](#set_restart-重启-onebot-实现)
- [`clean_cache` 清理缓存](#clean_cache-清理缓存)
## `send_private_msg` 发送私聊消息
### 参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| ----- | ------- | ----- | --- |
| `user_id` | number | - | 对方 QQ 号 |
| `message` | message | - | 要发送的内容 |
| `auto_escape` | boolean | `false` | 消息内容是否作为纯文本发送(即不解析 CQ 码),只在 `message` 字段是字符串时有效 |
### 响应数据
| 字段名 | 数据类型 | 说明 |
| ----- | ------- | --- |
| `message_id` | number (int32) | 消息 ID |
## `send_group_msg` 发送群消息
### 参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| ----- | ------- | ----- | --- |
| `group_id` | number | - | 群号 |
| `message` | message | - | 要发送的内容 |
| `auto_escape` | boolean | `false` | 消息内容是否作为纯文本发送(即不解析 CQ 码),只在 `message` 字段是字符串时有效 |
### 响应数据
| 字段名 | 数据类型 | 说明 |
| ----- | ------- | --- |
| `message_id` | number (int32) | 消息 ID |
## `send_msg` 发送消息
### 参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| ----- | ------- | ----- | --- |
| `message_type` | string | - | 消息类型,支持 `private``group`,分别对应私聊、群组,如不传入,则根据传入的 `*_id` 参数判断 |
| `user_id` | number | - | 对方 QQ 号(消息类型为 `private` 时需要) |
| `group_id` | number | - | 群号(消息类型为 `group` 时需要) |
| `message` | message | - | 要发送的内容 |
| `auto_escape` | boolean | `false` | 消息内容是否作为纯文本发送(即不解析 CQ 码),只在 `message` 字段是字符串时有效 |
### 响应数据
| 字段名 | 数据类型 | 说明 |
| ----- | ------- | --- |
| `message_id` | number (int32) | 消息 ID |
## `delete_msg` 撤回消息
### 参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| ----- | ------- | ----- | --- |
| `message_id` | number (int32) | - | 消息 ID |
### 响应数据
## `get_msg` 获取消息
### 参数
| 字段名 | 数据类型 | 说明 |
| ------------ | ----- | ------ |
| `message_id` | number (int32) | 消息 ID |
### 响应数据
| 字段名 | 数据类型 | 说明 |
| ------------ | ------- | ---------- |
| `time` | number (int32) | 发送时间 |
| `message_type` | string | 消息类型,同 [消息事件](../event/message.md) |
| `message_id` | number (int32) | 消息 ID |
| `real_id` | number (int32) | 消息真实 ID |
| `sender` | object | 发送人信息,同 [消息事件](../event/message.md) |
| `message` | message | 消息内容 |
## `get_forward_msg` 获取合并转发消息
### 参数
| 字段名 | 数据类型 | 说明 |
| ------------ | ------ | ------ |
| `id` | string | 合并转发 ID |
### 响应数据
| 字段名 | 类型 | 说明 |
| --- | --- | --- |
| `message` | message | 消息内容,使用 [消息的数组格式](../message/array.md) 表示,数组中的消息段全部为 [`node` 消息段](../message/segment.md#合并转发自定义节点) |
## `send_like` 发送好友赞
### 参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| ----- | ------- | ----- | --- |
| `user_id` | number | - | 对方 QQ 号 |
| `times` | number | 1 | 赞的次数,每个好友每天最多 10 次 |
### 响应数据
## `set_group_kick` 群组踢人
### 参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| ----- | ------- | ----- | --- |
| `group_id` | number | - | 群号 |
| `user_id` | number | - | 要踢的 QQ 号 |
| `reject_add_request` | boolean | `false` | 拒绝此人的加群请求 |
### 响应数据
## `set_group_ban` 群组单人禁言
### 参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| ----- | ------- | ----- | --- |
| `group_id` | number | - | 群号 |
| `user_id` | number | - | 要禁言的 QQ 号 |
| `duration` | number | `30 * 60` | 禁言时长单位秒0 表示取消禁言 |
### 响应数据
## `set_group_anonymous_ban` 群组匿名用户禁言
### 参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| ----- | ------- | ----- | --- |
| `group_id` | number | - | 群号 |
| `anonymous` | object | - | 可选,要禁言的匿名用户对象(群消息上报的 `anonymous` 字段) |
| `anonymous_flag` | string | - | 可选,要禁言的匿名用户的 flag需从群消息上报的数据中获得 |
| `duration` | number | `30 * 60` | 禁言时长,单位秒,无法取消匿名用户禁言 |
上面的 `anonymous``anonymous_flag` 两者任选其一传入即可,若都传入,则使用 `anonymous`
### 响应数据
## `set_group_whole_ban` 群组全员禁言
### 参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| ----- | ------- | ----- | --- |
| `group_id` | number | - | 群号 |
| `enable` | boolean | `true` | 是否禁言 |
### 响应数据
## `set_group_admin` 群组设置管理员
### 参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| ----- | ------- | ----- | --- |
| `group_id` | number | - | 群号 |
| `user_id` | number | - | 要设置管理员的 QQ 号 |
| `enable` | boolean | `true` | true 为设置false 为取消 |
### 响应数据
## `set_group_anonymous` 群组匿名
### 参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| ----- | ------- | ----- | --- |
| `group_id` | number | - | 群号 |
| `enable` | boolean | `true` | 是否允许匿名聊天 |
### 响应数据
## `set_group_card` 设置群名片(群备注)
### 参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| ----- | ------- | ----- | --- |
| `group_id` | number | - | 群号 |
| `user_id` | number | - | 要设置的 QQ 号 |
| `card` | string | "" | 群名片内容,不填或空字符串表示删除群名片 |
### 响应数据
## `set_group_name` 设置群名
### 参数
| 字段名 | 数据类型 | 说明 |
| -------- | ------ | ---- |
| `group_id` | number (int64) | 群号 |
| `group_name` | string | 新群名 |
### 响应数据
## `set_group_leave` 退出群组
### 参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| ----- | ------- | ----- | --- |
| `group_id` | number | - | 群号 |
| `is_dismiss` | boolean | `false` | 是否解散,如果登录号是群主,则仅在此项为 true 时能够解散 |
### 响应数据
## `set_group_special_title` 设置群组专属头衔
### 参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| ----- | ------- | ----- | --- |
| `group_id` | number | - | 群号 |
| `user_id` | number | - | 要设置的 QQ 号 |
| `special_title` | string | "" | 专属头衔,不填或空字符串表示删除专属头衔 |
| `duration` | number | `-1` | 专属头衔有效期,单位秒,-1 表示永久,不过此项似乎没有效果,可能是只有某些特殊的时间长度有效,有待测试 |
### 响应数据
## `set_friend_add_request` 处理加好友请求
### 参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| ----- | ------- | ----- | --- |
| `flag` | string | - | 加好友请求的 flag需从上报的数据中获得 |
| `approve` | boolean | `true` | 是否同意请求 |
| `remark` | string | "" | 添加后的好友备注(仅在同意时有效) |
### 响应数据
## `set_group_add_request` 处理加群请求/邀请
### 参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| ----- | ------- | ----- | --- |
| `flag` | string | - | 加群请求的 flag需从上报的数据中获得 |
| `sub_type` | string | - | `add``invite`,请求类型(需要和上报消息中的 `sub_type` 字段相符) |
| `approve` | boolean | `true` | 是否同意请求/邀请 |
| `reason` | string | "" | 拒绝理由(仅在拒绝时有效) |
### 响应数据
## `get_login_info` 获取登录号信息
### 参数
### 响应数据
| 字段名 | 数据类型 | 说明 |
| ----- | ------- | --- |
| `user_id` | number (int64) | QQ 号 |
| `nickname` | string | QQ 昵称 |
## `get_stranger_info` 获取陌生人信息
### 参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| ----- | ------- | ----- | --- |
| `user_id` | number | - | QQ 号 |
| `no_cache` | boolean | `false` | 是否不使用缓存(使用缓存可能更新不及时,但响应更快) |
### 响应数据
| 字段名 | 数据类型 | 说明 |
| ----- | ------- | --- |
| `user_id` | number (int64) | QQ 号 |
| `nickname` | string | 昵称 |
| `sex` | string | 性别,`male``female``unknown` |
| `age` | number (int32) | 年龄 |
## `get_friend_list` 获取好友列表
### 参数
### 响应数据
响应内容为 JSON 数组,每个元素如下:
| 字段名 | 数据类型 | 说明 |
| ----- | ------- | --- |
| `user_id` | number (int64) | QQ 号 |
| `nickname` | string | 昵称 |
| `remark` | string | 备注名 |
## `get_group_info` 获取群信息
### 参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| ----- | ------- | ----- | --- |
| `group_id` | number | - | 群号 |
| `no_cache` | boolean | `false` | 是否不使用缓存(使用缓存可能更新不及时,但响应更快) |
### 响应数据
| 字段名 | 数据类型 | 说明 |
| ----- | ------- | --- |
| `group_id` | number (int64) | 群号 |
| `group_name` | string | 群名称 |
| `member_count` | number (int32) | 成员数 |
| `max_member_count` | number (int32) | 最大成员数(群容量) |
## `get_group_list` 获取群列表
### 参数
### 响应数据
响应内容为 JSON 数组,每个元素和上面的 `get_group_info` 接口相同。
## `get_group_member_info` 获取群成员信息
### 参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| ----- | ------- | ----- | --- |
| `group_id` | number | - | 群号 |
| `user_id` | number | - | QQ 号 |
| `no_cache` | boolean | `false` | 是否不使用缓存(使用缓存可能更新不及时,但响应更快) |
### 响应数据
| 字段名 | 数据类型 | 说明 |
| ----- | ------- | --- |
| `group_id` | number (int64) | 群号 |
| `user_id` | number (int64) | QQ 号 |
| `nickname` | string | 昵称 |
| `card` | string | 群名片/备注 |
| `sex` | string | 性别,`male``female``unknown` |
| `age` | number (int32) | 年龄 |
| `area` | string | 地区 |
| `join_time` | number (int32) | 加群时间戳 |
| `last_sent_time` | number (int32) | 最后发言时间戳 |
| `level` | string | 成员等级 |
| `role` | string | 角色,`owner``admin``member` |
| `unfriendly` | boolean | 是否不良记录成员 |
| `title` | string | 专属头衔 |
| `title_expire_time` | number (int32) | 专属头衔过期时间戳 |
| `card_changeable` | boolean | 是否允许修改群名片 |
## `get_group_member_list` 获取群成员列表
### 参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| ----- | ------- | ----- | --- |
| `group_id` | number (int64) | - | 群号 |
### 响应数据
响应内容为 JSON 数组,每个元素的内容和上面的 `get_group_member_info` 接口相同,但对于同一个群组的同一个成员,获取列表时和获取单独的成员信息时,某些字段可能有所不同,例如 `area``title` 等字段在获取列表时无法获得,具体应以单独的成员信息为准。
## `get_group_honor_info` 获取群荣誉信息
### 参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| ----- | ------- | ----- | --- |
| `group_id` | number (int64) | - | 群号 |
| `type` | string | - | 要获取的群荣誉类型,可传入 `talkative` `performer` `legend` `strong_newbie` `emotion` 以分别获取单个类型的群荣誉数据,或传入 `all` 获取所有数据 |
### 响应数据
| 字段名 | 数据类型 | 说明 |
| ----- | ------- | --- |
| `group_id` | number (int64) | 群号 |
| `current_talkative` | object | 当前龙王,仅 `type``talkative``all` 时有数据 |
| `talkative_list` | array | 历史龙王,仅 `type``talkative``all` 时有数据 |
| `performer_list` | array | 群聊之火,仅 `type``performer``all` 时有数据 |
| `legend_list` | array | 群聊炽焰,仅 `type``legend``all` 时有数据 |
| `strong_newbie_list` | array | 冒尖小春笋,仅 `type``strong_newbie``all` 时有数据 |
| `emotion_list` | array | 快乐之源,仅 `type``emotion``all` 时有数据 |
其中 `current_talkative` 字段的内容如下:
| 字段名 | 数据类型 | 说明 |
| ----- | ------- | --- |
| `user_id` | number (int64) | QQ 号 |
| `nickname` | string | 昵称 |
| `avatar` | string | 头像 URL |
| `day_count` | number (int32) | 持续天数 |
其它各 `*_list` 的每个元素是一个 JSON 对象,内容如下:
| 字段名 | 数据类型 | 说明 |
| ----- | ------- | --- |
| `user_id` | number (int64) | QQ 号 |
| `nickname` | string | 昵称 |
| `avatar` | string | 头像 URL |
| `description` | string | 荣誉描述 |
## `get_cookies` 获取 Cookies
### 参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| ----- | ------- | ----- | --- |
| `domain` | string | "" | 需要获取 cookies 的域名 |
### 响应数据
| 字段名 | 数据类型 | 说明 |
| ----- | ------- | --- |
| `cookies` | string | Cookies |
## `get_csrf_token` 获取 CSRF Token
### 参数
### 响应数据
| 字段名 | 数据类型 | 说明 |
| ----- | ------- | --- |
| `token` | number (int32) | CSRF Token |
## `get_credentials` 获取 QQ 相关接口凭证
即上面两个接口的合并。
### 参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| ----- | ------- | ----- | --- |
| `domain` | string | "" | 需要获取 cookies 的域名 |
### 响应数据
| 字段名 | 数据类型 | 说明 |
| ----- | ------- | --- |
| `cookies` | string | Cookies |
| `csrf_token` | number (int32) | CSRF Token |
## `get_record` 获取语音
> **提示**:要使用此接口,通常需要安装 ffmpeg请参考 OneBot 实现的相关说明。
### 参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| ----- | ------- | ----- | --- |
| `file` | string | - | 收到的语音文件名(消息段的 `file` 参数),如 `0B38145AA44505000B38145AA4450500.silk` |
| `out_format` | string | - | 要转换到的格式,目前支持 `mp3``amr``wma``m4a``spx``ogg``wav``flac` |
### 响应数据
| 字段名 | 数据类型 | 说明 |
| ----- | ------- | --- |
| `file` | string | 转换后的语音文件路径,如 `/home/somebody/cqhttp/data/record/0B38145AA44505000B38145AA4450500.mp3` |
## `get_image` 获取图片
### 参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| ----- | ------- | ----- | --- |
| `file` | string | - | 收到的图片文件名(消息段的 `file` 参数),如 `6B4DE3DFD1BD271E3297859D41C530F5.jpg` |
### 响应数据
| 字段名 | 数据类型 | 说明 |
| ----- | ------- | --- |
| `file` | string | 下载后的图片文件路径,如 `/home/somebody/cqhttp/data/image/6B4DE3DFD1BD271E3297859D41C530F5.jpg` |
## `can_send_image` 检查是否可以发送图片
### 参数
### 响应数据
| 字段名 | 数据类型 | 说明 |
| ----- | ------- | --- |
| `yes` | boolean | 是或否 |
## `can_send_record` 检查是否可以发送语音
### 参数
### 响应数据
| 字段名 | 数据类型 | 说明 |
| ----- | ------- | --- |
| `yes` | boolean | 是或否 |
## `get_status` 获取运行状态
### 参数
### 响应数据
| 字段名 | 数据类型 | 说明 |
| ----- | ------- | --- |
| `online` | boolean | 当前 QQ 在线,`null` 表示无法查询到在线状态 |
| `good` | boolean | 状态符合预期,意味着各模块正常运行、功能正常,且 QQ 在线 |
| …… | - | OneBot 实现自行添加的其它内容 |
通常情况下建议只使用 `online``good` 这两个字段来判断运行状态,因为根据 OneBot 实现的不同,其它字段可能完全不同。
## `get_version_info` 获取版本信息
### 参数
### 响应数据
| 字段名 | 数据类型 | 说明 |
| ----- | ------- | --- |
| `app_name` | string | 应用标识,如 `mirai-native` |
| `app_version` | string | 应用版本,如 `1.2.3` |
| `protocol_version` | string | OneBot 标准版本,如 `v11` |
| …… | - | OneBot 实现自行添加的其它内容 |
## `set_restart` 重启 OneBot 实现
由于重启 OneBot 实现同时需要重启 API 服务,这意味着当前的 API 请求会被中断,因此需要异步地重启,接口返回的 `status``async`
### 参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| ----- | ------- | ----- | --- |
| `delay` | number | `0` | 要延迟的毫秒数,如果默认情况下无法重启,可以尝试设置延迟为 2000 左右 |
### 响应数据
## `clean_cache` 清理缓存
用于清理积攒了太多的缓存文件。
### 参数
### 响应数据
<hr>
| 上一节 | 下一节 |
| --- | --- |
| [API 概述](README.md) | [隐藏 API](hidden.md) |

176
scripts/prompt.py Normal file
View File

@ -0,0 +1,176 @@
from __future__ import annotations
from dataclasses import dataclass
import warnings
import random
import math
import yaml
@dataclass(frozen=True)
class IntentNode:
name: str
description: str | None
children: list[IntentNode]
parent: IntentNode | None
stories: list[Story]
@dataclass(frozen=True)
class Story:
message: str
intent: str
class PromptEngine:
path: str
schema: IntentNode | None
stories: list[Story]
rejects: list[str]
intent2id: dict[str, int]
id2intent: dict[int, str]
name2node: dict[str, IntentNode]
def __init__(self, path: str) -> None:
self.path = path
self.config = yaml.load(open(path, 'r', encoding='utf-8'), yaml.Loader)
self.intent2id = {}
self.id2intent = {}
self.name2node = {}
self.schema = self.handle_schema(self.config['schema'])
self.stories = self.handle_stories(self.config['stories'])
self.rejects = self.handle_rejects(self.config['rejects'])
def handle_schema(self, raw_schema: dict) -> IntentNode:
raw_root = raw_schema.get('root', None)
if raw_root is None:
warnings.warn('schema must have a root node as the beginning, otherwise intent recogition will not work')
return None
current_layers: list[tuple[dict, IntentNode | None]] = [(raw_root, None)]
nodes: list[IntentNode] = []
# 层次遍历
while len(current_layers) > 0:
new_current_layers: list[tuple[dict, IntentNode | None]] = []
for raw_node, intent_node in current_layers:
name = raw_node.get('name', None)
children = raw_node.get('children', None)
description = raw_node.get('description', None)
if name is None:
raise NameError('you must specify a name in story item, current item : {}'.format(raw_node))
if children is None:
children = []
if name not in self.intent2id:
assign_id = len(self.intent2id)
self.intent2id[name] = assign_id
self.id2intent[assign_id] = name
node = IntentNode(name, description, [], intent_node, [])
self.name2node[name] = node
nodes.append(node)
if intent_node:
intent_node.children.append(node)
for raw_node in children:
new_current_layers.append((raw_node, node))
current_layers.clear()
current_layers.extend(new_current_layers)
return nodes[0]
def handle_stories(self, raw_stories: list[dict]) -> list[Story]:
stories: list[Story] = []
for pair in raw_stories:
message = pair.get('message', None)
intent = pair.get('intent', None)
if intent not in self.intent2id:
warnings.warn('{} is not the intent you declare in schema, so this pair will be ignored'.format(intent))
continue
if message and intent:
story = Story(message, intent)
node = self.name2node.get(intent)
node.stories.append(story)
stories.append(story)
return stories
def handle_rejects(self, raw_rejects: list[str]) -> list[str]:
rejects = []
for reject in raw_rejects:
rejects.append(reject)
return rejects
def generate_chunk(self, stories: list[Story]) -> tuple[str]:
prompts = []
for story in stories:
prompts.append('Message: ' + story.message.strip())
intent_id = self.intent2id.get(story.intent)
prompts.append('Intent: { id: %s }' % (intent_id))
prompts.pop()
user_content = '\n'.join(prompts) + '\n' + 'Intent: '
assistant_content = '{id : %s}' % (intent_id)
return user_content, assistant_content
def generate_llm_message(self, question: str, intent: IntentNode = None, chunk_size: int = 5, max_chunk_num: int = 10):
if intent is None:
intent = self.schema
story_cache = []
for node in intent.children:
story_cache.extend(node.stories)
random.shuffle(story_cache)
chunk_num = math.ceil(len(story_cache) / chunk_size)
message = []
for chunk_id in range(chunk_num):
start = chunk_id * chunk_size
end = min(len(story_cache), start + chunk_size)
chunk = story_cache[start: end]
user_content, assistant_content = self.generate_chunk(chunk)
message.append({
'role': 'user',
'content': user_content
})
message.append({
'role': 'assistant',
'content': assistant_content
})
if len(message) / 2 >= max_chunk_num:
break
message.append({
'role': 'user',
'content': question + '\nIntent: '
})
# 创建开头的预设
preset = 'Label a users message from a conversation with an intent. Reply ONLY with the name of the intent.'
intent_preset = ['The intent should be one of the following:']
for node in intent.children:
intent_id = self.intent2id.get(node.name)
intent_preset.append('- {}'.format(intent_id))
intent_preset = '\n'.join(intent_preset)
message[0]['content'] = preset + '\n' + intent_preset + '\n' + message[0]['content']
return message
class KIntent:
path: str
engine: PromptEngine
def __init__(self, path: str) -> None:
self.path = path
self.engine = PromptEngine(path)
def inference(self, question: str, chunk_size: int = 5, max_chunk_num: int = 10) -> list[IntentNode]:
root_node = self.engine.schema
results: list[IntentNode] = []
stack = [root_node]
while len(stack) > 0:
node = stack.pop()
if __name__ == '__main__':
prompt_engine = PromptEngine('./story.yml')
msg = prompt_engine.generate_llm_message('如何解决 digital ide 无法载入配置文件的问题?')
print(msg)

70
scripts/qq-text-format.js Normal file
View File

@ -0,0 +1,70 @@
function markdownToPlainText(markdown) {
// 移除 Markdown 标题符号
let plainText = markdown.replace(/^#+\s*/gm, '');
// 移除 Markdown 链接格式
plainText = plainText.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1');
// 移除 Markdown 加粗和斜体符号
plainText = plainText.replace(/\*\*([^*]+)\*\*/g, '$1');
plainText = plainText.replace(/\*([^*]+)\*/g, '$1');
// 移除 Markdown 代码块符号
plainText = plainText.replace(/`([^`]+)`/g, '$1');
// 移除 Markdown 分割线
plainText = plainText.replace(/^-{3,}/gm, '');
// 移除多余的空行
plainText = plainText.replace(/\n{3,}/g, '\n\n');
return plainText.trim();
}
// 示例用法
const markdown = `以下是整理好的热门文章资讯,已翻译为简体中文:
---
### 1. **我的GPT造型师教会我如何更好地提示**
**文章介绍**
本文探讨了大型语言模型LLM的奇怪行为并通过作者与GPT造型师的互动揭示了如何通过优化提示来更好地利用这些模型文章深入分析了LLM的工作原理和局限性
**文章链接**
\`https://towardsdatascience.com/what-my-gpt-stylist-taught-me-about-prompting-better-inside-the-strange-behavior-of-llms/\`
**作者**
Arielle Caron
**发布时间**
2025年5月9日
---
### 2. **AccentFold论文回顾非洲ASR领域最重要的研究之一**
**文章介绍**
文章回顾了AccentFold论文该研究解决了当前自动语音识别ASR系统在处理非洲口音时的局限性作者详细分析了论文的创新点和实际应用价值
**文章链接**
\`https://towardsdatascience.com/a-review-of-accentfold-one-of-the-most-important-papers-on-african-asr/\`
**作者**
Zaynab Awofeso
**发布时间**
2025年5月9日
---
### 3. **欺骗性数据的危险第二部分基础比例与糟糕的统计**
**文章介绍**
本文是作者关于欺骗性数据系列的第二部分重点讨论了相关性基础比例摘要统计和不确定性如何误导人们文章通过生动的例子和图表帮助读者理解统计陷阱
**文章链接**
\`https://towardsdatascience.com/the-dangers-of-deceptive-data-part-2-base-proportions-and-bad-statistics/\`
**作者**
Murtaza Ali
**发布时间**
2025年5月8日
---
希望这些资讯对您有帮助如果需要进一步了解某篇文章请随时告诉我`;
console.log(markdownToPlainText(markdown));

41
scripts/story.yml Normal file
View File

@ -0,0 +1,41 @@
schema:
root:
name: root
children:
- name: usage
description: 使用查询
children:
- name: bug
description: bug 查询
children:
- name: command
description: 指令
children:
- name: others
description: 其他
children:
stories:
- message: 请问 property.json 如何配置?
intent: usage
- message: 我的自动补全无法使用是不是有bug
intent: bug
- message: 帮我上传一下这份数据
intent: command
- message: surface了解一下
intent: others
- message: 大佬们为啥我的digital ide启动之后所有功能都没启动捏我配置了property文件然后插件的vivado路经和modelsim路经都加上了
intent: usage
- message: 这群要被chisel夺舍了吗
intent: others
- message: Metals一开直接报错
intent: others
- message: 话说digital-ide打开大的verilog卡死了
intent: bug
- message: 请问一下第一次点击对文件仿真可以出波形文件再次点击的时候就会提示unknown module type了。是哪个配置没配置好
intent: usage
rejects:
- metal
- metals
- idea

18
scripts/t2.txt Normal file
View File

@ -0,0 +1,18 @@
Label a users message from a conversation with an intent. Reply ONLY with the name of the intent.
The intent should be one of the following:
- 0
- 1
- 2
- 3
Message: 大佬们为啥我的digital ide启动之后所有功能都没启动捏我配置了property文件然后插件的vivado路经和modelsim路经都加上了
Intent: { id: 0 }
Message: 这群要被chisel夺舍了吗
Intent: { id: 3 }
Message: Metals一开直接报错
Intent: { id: 3 }
Message: 话说digital-ide打开大的verilog卡死了
Intent: { id: 1 }
Message: 请问一下第一次点击对文件仿真可以出波形文件再次点击的时候就会提示unknown module type了。是哪个配置没配置好
Intent: { id: <answer here> }

16
scripts/template.txt Normal file
View File

@ -0,0 +1,16 @@
Label a users message from a conversation with an intent. Reply ONLY with the name of the intent.
The intent should be one of the following:
- 0
- 1
- 2
- 3
Message: 请问 property.json 如何配置?
Intent: { id: 0 }
Message: 我的自动补全无法使用是不是有bug
Intent: { id: 1 }
Message: 帮我上传一下这份数据
Intent: { id: 2 }
Message: 为数不多我觉得很好的动漫了
Intent: { id: 3 }
Message: M1系列可以跑iPhone和iPad游戏
Intent: { id: <answer here> }

6
scripts/test.js Normal file
View File

@ -0,0 +1,6 @@
function hello(a) {
console.log(a);
}
hello.call(this, 'hello');

13
scripts/text.txt Normal file
View File

@ -0,0 +1,13 @@
Label a users message from a
conversation with an intent. Reply ONLY with the name of the intent.
The intent should be one of the following:
{% for intent in intents %}
- {{intent}}
{% endfor %}
{% for example in examples %}
Message: {{example['text']}}
Intent: {{example['intent']}}
{% endfor %}
Message: {{message}}
Intent:

41
test/index.js Normal file
View File

@ -0,0 +1,41 @@
const path = require('path');
const Mocha = require('mocha');
const glob = require('glob');
function run() {
// Create the mocha test
const mocha = new Mocha({
ui: 'tdd',
color: true
});
const testsRoot = path.resolve(__dirname, '..');
return new Promise((c, e) => {
glob('**/**.test.js', { cwd: testsRoot }, (err, files) => {
if (err) {
return e(err);
}
files.forEach(f => mocha.addFile(path.resolve(testsRoot, f)));
try {
// 设置每个测试的超时
mocha.timeout(60000);
mocha.run(failures => {
if (failures > 0) {
e(new Error(`${failures} tests failed.`));
} else {
c();
}
});
} catch (err) {
console.error(err);
e(err);
}
});
});
}
run();

66
test/suite/rag.test.js Normal file
View File

@ -0,0 +1,66 @@
const fs = require('fs');
const yaml = require('yaml');
const assert = require('assert');
const axios = require('axios');
const vecdbBuffer = fs.readFileSync('./config/vecdb.yml', 'utf-8');
const vecdbConfig = yaml.parse(vecdbBuffer);
const vecdbBaseURL = `http://${vecdbConfig['addr']}:${vecdbConfig['port']}`;
console.log(vecdbBaseURL);
const vecdbRequests = axios.create({
baseURL: vecdbBaseURL,
timeout: 5000
});
const apiGetIntentRecogition = (req) => vecdbRequests({
url: '/intent/get-intent-recogition', method: 'POST',
data: req
});
suite('test intent recogition', () => {
// 也可以事先写好测试集,写在测试程序里或者从静态文件中读入
const intent_suites = [
{ input: '如何使用 digital ide 这个插件?', expect: 'usage' },
{ input: '我今天打开 vscode发现 自动补全失效了,我是哪里没有配置好吗?', expect: 'usage,bug' },
{ input: 'path top.v is not a hdlFile 请问报这个错误大概是啥原因啊', expect: 'usage,bug' },
{ input: '我同学在学习强国看到小麦收割了,然后就买相应的股就赚了', expect: 'others' },
{ input: '我平时写代码就喜欢喝茶', expect: 'others' },
{ input: '请问报这个错误大概是啥原因啊', expect: 'usage,bug' },
{ input: '感觉现在啥都在往AI靠', expect: 'others' },
{ input: '别人设置的肯定有点不合适自己的', expect: 'others' },
{ input: '在企业里面最大的问题是碰见傻逼怎么办?', expect: 'others' },
{ input: '几乎完全不喝牛奶2333', expect: 'others' },
{ input: 'command not found: python', expect: 'usage,bug,others' },
{ input: '兄弟们有没有C语言绘图库推荐', expect: 'usage' },
{ input: '我早上开着机去打论文 回来发现我电脑切换到Linux了', expect: 'usage,bug,others' },
{ input: '我在Windows下遇到的只要问题就是对于C程序包管理和编译管理器偶尔会不认识彼此但除此之外都很安稳win11除外', expect: 'usage,others' },
{ input: '我的反撤回还能用', expect: 'others' },
{ input: '因为这是养蛊的虚拟机,放了些国产垃圾软件,得用国产流氓之王才能镇得住他们', expect: 'others' },
{ input: '你咋装了个360', expect: 'others' },
{ input: '', expect: 'expression,others' },
{ input: '有点怪,这里没有和竖线对齐', expect: 'others' },
{ input: '调试时候下载bit流和ltx固化需要在xdc约束里面添加spi或bpi相关约束后生成bit流。建议一开始xdc约束当中就添加相关约束后续生成mcs文件需要先添加flash型号根据相关型号生成对应的mcs文件后再固化到flash当中。', expect: 'advice,others' },
{ input: 'at 触发不够人性化,很多用户不知道 Tip 的存在就不会去使用它', expect: 'expression,others' },
{ input: '非问句会触发吗', expect: 'expression,others' },
{ input: 'zlib', expect: 'expression,others' },
{ input: 'https://github.com/Digital-EDA/Digital-IDE/discussions', expect: 'others' },
];
for (const s of intent_suites) {
const input = s.input;
const expects = s.expect.split(',');
test(`Message: ${input}) => Intent: ${expects.join(',')}`, async () => {
const axiosRes = await apiGetIntentRecogition({ query: input });
const res = axiosRes.data;
const payload = res.data;
const intentName = payload.name;
assert(expects.includes(intentName), `infer intent "${intentName}" not in expect "${expects}"`);
});
}
});

672
yarn.lock
View File

@ -1,672 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@types/node@*", "@types/node@^20.12.12":
version "20.12.12"
resolved "https://registry.npmmirror.com/@types/node/-/node-20.12.12.tgz#7cbecdf902085cec634fdb362172dfe12b8f2050"
integrity sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==
dependencies:
undici-types "~5.26.4"
"@types/ws@^8.5.10":
version "8.5.10"
resolved "https://registry.npmmirror.com/@types/ws/-/ws-8.5.10.tgz#4acfb517970853fa6574a3a6886791d04a396787"
integrity sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==
dependencies:
"@types/node" "*"
"@zeit/schemas@2.36.0":
version "2.36.0"
resolved "https://registry.npmmirror.com/@zeit/schemas/-/schemas-2.36.0.tgz#7a1b53f4091e18d0b404873ea3e3c83589c765f2"
integrity sha512-7kjMwcChYEzMKjeex9ZFXkt1AyNov9R5HZtjBKVsmVpw7pa7ZtlCGvCBC2vnnXctaYN+aRI61HjIqeetZW5ROg==
accepts@~1.3.5:
version "1.3.8"
resolved "https://registry.npmmirror.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e"
integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==
dependencies:
mime-types "~2.1.34"
negotiator "0.6.3"
ajv@8.12.0:
version "8.12.0"
resolved "https://registry.npmmirror.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1"
integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==
dependencies:
fast-deep-equal "^3.1.1"
json-schema-traverse "^1.0.0"
require-from-string "^2.0.2"
uri-js "^4.2.2"
ansi-align@^3.0.1:
version "3.0.1"
resolved "https://registry.npmmirror.com/ansi-align/-/ansi-align-3.0.1.tgz#0cdf12e111ace773a86e9a1fad1225c43cb19a59"
integrity sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==
dependencies:
string-width "^4.1.0"
ansi-regex@^5.0.1:
version "5.0.1"
resolved "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
ansi-regex@^6.0.1:
version "6.0.1"
resolved "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a"
integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==
ansi-styles@^4.1.0:
version "4.3.0"
resolved "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
dependencies:
color-convert "^2.0.1"
ansi-styles@^6.1.0:
version "6.2.1"
resolved "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5"
integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==
arch@^2.2.0:
version "2.2.0"
resolved "https://registry.npmmirror.com/arch/-/arch-2.2.0.tgz#1bc47818f305764f23ab3306b0bfc086c5a29d11"
integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==
arg@5.0.2:
version "5.0.2"
resolved "https://registry.npmmirror.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c"
integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==
asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
axios@^1.7.2:
version "1.7.2"
resolved "https://registry.npmmirror.com/axios/-/axios-1.7.2.tgz#b625db8a7051fbea61c35a3cbb3a1daa7b9c7621"
integrity sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==
dependencies:
follow-redirects "^1.15.6"
form-data "^4.0.0"
proxy-from-env "^1.1.0"
balanced-match@^1.0.0:
version "1.0.2"
resolved "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
boxen@7.0.0:
version "7.0.0"
resolved "https://registry.npmmirror.com/boxen/-/boxen-7.0.0.tgz#9e5f8c26e716793fc96edcf7cf754cdf5e3fbf32"
integrity sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==
dependencies:
ansi-align "^3.0.1"
camelcase "^7.0.0"
chalk "^5.0.1"
cli-boxes "^3.0.0"
string-width "^5.1.2"
type-fest "^2.13.0"
widest-line "^4.0.1"
wrap-ansi "^8.0.1"
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
dependencies:
balanced-match "^1.0.0"
concat-map "0.0.1"
bytes@3.0.0:
version "3.0.0"
resolved "https://registry.npmmirror.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==
camelcase@^7.0.0:
version "7.0.1"
resolved "https://registry.npmmirror.com/camelcase/-/camelcase-7.0.1.tgz#f02e50af9fd7782bc8b88a3558c32fd3a388f048"
integrity sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==
chalk-template@0.4.0:
version "0.4.0"
resolved "https://registry.npmmirror.com/chalk-template/-/chalk-template-0.4.0.tgz#692c034d0ed62436b9062c1707fadcd0f753204b"
integrity sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==
dependencies:
chalk "^4.1.2"
chalk@5.0.1:
version "5.0.1"
resolved "https://registry.npmmirror.com/chalk/-/chalk-5.0.1.tgz#ca57d71e82bb534a296df63bbacc4a1c22b2a4b6"
integrity sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==
chalk@^4.1.2:
version "4.1.2"
resolved "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
dependencies:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
chalk@^5.0.1:
version "5.3.0"
resolved "https://registry.npmmirror.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385"
integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==
cli-boxes@^3.0.0:
version "3.0.0"
resolved "https://registry.npmmirror.com/cli-boxes/-/cli-boxes-3.0.0.tgz#71a10c716feeba005e4504f36329ef0b17cf3145"
integrity sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==
clipboardy@3.0.0:
version "3.0.0"
resolved "https://registry.npmmirror.com/clipboardy/-/clipboardy-3.0.0.tgz#f3876247404d334c9ed01b6f269c11d09a5e3092"
integrity sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==
dependencies:
arch "^2.2.0"
execa "^5.1.1"
is-wsl "^2.2.0"
color-convert@^2.0.1:
version "2.0.1"
resolved "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
dependencies:
color-name "~1.1.4"
color-name@~1.1.4:
version "1.1.4"
resolved "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
combined-stream@^1.0.8:
version "1.0.8"
resolved "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
dependencies:
delayed-stream "~1.0.0"
compressible@~2.0.16:
version "2.0.18"
resolved "https://registry.npmmirror.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba"
integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==
dependencies:
mime-db ">= 1.43.0 < 2"
compression@1.7.4:
version "1.7.4"
resolved "https://registry.npmmirror.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f"
integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==
dependencies:
accepts "~1.3.5"
bytes "3.0.0"
compressible "~2.0.16"
debug "2.6.9"
on-headers "~1.0.2"
safe-buffer "5.1.2"
vary "~1.1.2"
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
content-disposition@0.5.2:
version "0.5.2"
resolved "https://registry.npmmirror.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4"
integrity sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==
cross-spawn@^7.0.3:
version "7.0.3"
resolved "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
dependencies:
path-key "^3.1.0"
shebang-command "^2.0.0"
which "^2.0.1"
debug@2.6.9:
version "2.6.9"
resolved "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
dependencies:
ms "2.0.0"
deep-extend@^0.6.0:
version "0.6.0"
resolved "https://registry.npmmirror.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
delayed-stream@~1.0.0:
version "1.0.0"
resolved "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
eastasianwidth@^0.2.0:
version "0.2.0"
resolved "https://registry.npmmirror.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
emoji-regex@^8.0.0:
version "8.0.0"
resolved "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
emoji-regex@^9.2.2:
version "9.2.2"
resolved "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72"
integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
execa@^5.1.1:
version "5.1.1"
resolved "https://registry.npmmirror.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd"
integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==
dependencies:
cross-spawn "^7.0.3"
get-stream "^6.0.0"
human-signals "^2.1.0"
is-stream "^2.0.0"
merge-stream "^2.0.0"
npm-run-path "^4.0.1"
onetime "^5.1.2"
signal-exit "^3.0.3"
strip-final-newline "^2.0.0"
fast-deep-equal@^3.1.1:
version "3.1.3"
resolved "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
fast-url-parser@1.1.3:
version "1.1.3"
resolved "https://registry.npmmirror.com/fast-url-parser/-/fast-url-parser-1.1.3.tgz#f4af3ea9f34d8a271cf58ad2b3759f431f0b318d"
integrity sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==
dependencies:
punycode "^1.3.2"
follow-redirects@^1.15.6:
version "1.15.6"
resolved "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b"
integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==
form-data@^4.0.0:
version "4.0.0"
resolved "https://registry.npmmirror.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
mime-types "^2.1.12"
get-stream@^6.0.0:
version "6.0.1"
resolved "https://registry.npmmirror.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==
has-flag@^4.0.0:
version "4.0.0"
resolved "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
human-signals@^2.1.0:
version "2.1.0"
resolved "https://registry.npmmirror.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
ini@~1.3.0:
version "1.3.8"
resolved "https://registry.npmmirror.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
is-docker@^2.0.0:
version "2.2.1"
resolved "https://registry.npmmirror.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa"
integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==
is-fullwidth-code-point@^3.0.0:
version "3.0.0"
resolved "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
is-port-reachable@4.0.0:
version "4.0.0"
resolved "https://registry.npmmirror.com/is-port-reachable/-/is-port-reachable-4.0.0.tgz#dac044091ef15319c8ab2f34604d8794181f8c2d"
integrity sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==
is-stream@^2.0.0:
version "2.0.1"
resolved "https://registry.npmmirror.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077"
integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==
is-wsl@^2.2.0:
version "2.2.0"
resolved "https://registry.npmmirror.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271"
integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==
dependencies:
is-docker "^2.0.0"
isexe@^2.0.0:
version "2.0.0"
resolved "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
json-schema-traverse@^1.0.0:
version "1.0.0"
resolved "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2"
integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
merge-stream@^2.0.0:
version "2.0.0"
resolved "https://registry.npmmirror.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
mime-db@1.52.0, "mime-db@>= 1.43.0 < 2":
version "1.52.0"
resolved "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
mime-db@~1.33.0:
version "1.33.0"
resolved "https://registry.npmmirror.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db"
integrity sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==
mime-types@2.1.18:
version "2.1.18"
resolved "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8"
integrity sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==
dependencies:
mime-db "~1.33.0"
mime-types@^2.1.12, mime-types@~2.1.34:
version "2.1.35"
resolved "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
dependencies:
mime-db "1.52.0"
mimic-fn@^2.1.0:
version "2.1.0"
resolved "https://registry.npmmirror.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
minimatch@3.1.2:
version "3.1.2"
resolved "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
dependencies:
brace-expansion "^1.1.7"
minimist@^1.2.0:
version "1.2.8"
resolved "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
ms@2.0.0:
version "2.0.0"
resolved "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
negotiator@0.6.3:
version "0.6.3"
resolved "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
npm-run-path@^4.0.1:
version "4.0.1"
resolved "https://registry.npmmirror.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"
integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==
dependencies:
path-key "^3.0.0"
on-headers@~1.0.2:
version "1.0.2"
resolved "https://registry.npmmirror.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f"
integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==
onetime@^5.1.2:
version "5.1.2"
resolved "https://registry.npmmirror.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e"
integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==
dependencies:
mimic-fn "^2.1.0"
path-is-inside@1.0.2:
version "1.0.2"
resolved "https://registry.npmmirror.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
integrity sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==
path-key@^3.0.0, path-key@^3.1.0:
version "3.1.1"
resolved "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
path-to-regexp@2.2.1:
version "2.2.1"
resolved "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-2.2.1.tgz#90b617025a16381a879bc82a38d4e8bdeb2bcf45"
integrity sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==
proxy-from-env@^1.1.0:
version "1.1.0"
resolved "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
punycode@^1.3.2:
version "1.4.1"
resolved "https://registry.npmmirror.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==
punycode@^2.1.0:
version "2.3.1"
resolved "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
range-parser@1.2.0:
version "1.2.0"
resolved "https://registry.npmmirror.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e"
integrity sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==
rc@^1.0.1, rc@^1.1.6:
version "1.2.8"
resolved "https://registry.npmmirror.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==
dependencies:
deep-extend "^0.6.0"
ini "~1.3.0"
minimist "^1.2.0"
strip-json-comments "~2.0.1"
registry-auth-token@3.3.2:
version "3.3.2"
resolved "https://registry.npmmirror.com/registry-auth-token/-/registry-auth-token-3.3.2.tgz#851fd49038eecb586911115af845260eec983f20"
integrity sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==
dependencies:
rc "^1.1.6"
safe-buffer "^5.0.1"
registry-url@3.1.0:
version "3.1.0"
resolved "https://registry.npmmirror.com/registry-url/-/registry-url-3.1.0.tgz#3d4ef870f73dde1d77f0cf9a381432444e174942"
integrity sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==
dependencies:
rc "^1.0.1"
require-from-string@^2.0.2:
version "2.0.2"
resolved "https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
safe-buffer@5.1.2:
version "5.1.2"
resolved "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
safe-buffer@^5.0.1:
version "5.2.1"
resolved "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
serve-handler@6.1.5:
version "6.1.5"
resolved "https://registry.npmmirror.com/serve-handler/-/serve-handler-6.1.5.tgz#a4a0964f5c55c7e37a02a633232b6f0d6f068375"
integrity sha512-ijPFle6Hwe8zfmBxJdE+5fta53fdIY0lHISJvuikXB3VYFafRjMRpOffSPvCYsbKyBA7pvy9oYr/BT1O3EArlg==
dependencies:
bytes "3.0.0"
content-disposition "0.5.2"
fast-url-parser "1.1.3"
mime-types "2.1.18"
minimatch "3.1.2"
path-is-inside "1.0.2"
path-to-regexp "2.2.1"
range-parser "1.2.0"
serve@^14.2.3:
version "14.2.3"
resolved "https://registry.npmmirror.com/serve/-/serve-14.2.3.tgz#047ba2b349354255bc09e0332cd41a92787836c9"
integrity sha512-VqUFMC7K3LDGeGnJM9h56D3XGKb6KGgOw0cVNtA26yYXHCcpxf3xwCTUaQoWlVS7i8Jdh3GjQkOB23qsXyjoyQ==
dependencies:
"@zeit/schemas" "2.36.0"
ajv "8.12.0"
arg "5.0.2"
boxen "7.0.0"
chalk "5.0.1"
chalk-template "0.4.0"
clipboardy "3.0.0"
compression "1.7.4"
is-port-reachable "4.0.0"
serve-handler "6.1.5"
update-check "1.5.4"
shebang-command@^2.0.0:
version "2.0.0"
resolved "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==
dependencies:
shebang-regex "^3.0.0"
shebang-regex@^3.0.0:
version "3.0.0"
resolved "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
signal-exit@^3.0.3:
version "3.0.7"
resolved "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
string-width@^4.1.0:
version "4.2.3"
resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
string-width@^5.0.1, string-width@^5.1.2:
version "5.1.2"
resolved "https://registry.npmmirror.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==
dependencies:
eastasianwidth "^0.2.0"
emoji-regex "^9.2.2"
strip-ansi "^7.0.1"
strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"
strip-ansi@^7.0.1:
version "7.1.0"
resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==
dependencies:
ansi-regex "^6.0.1"
strip-final-newline@^2.0.0:
version "2.0.0"
resolved "https://registry.npmmirror.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==
strip-json-comments@~2.0.1:
version "2.0.1"
resolved "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==
supports-color@^7.1.0:
version "7.2.0"
resolved "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
dependencies:
has-flag "^4.0.0"
type-fest@^2.13.0:
version "2.19.0"
resolved "https://registry.npmmirror.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b"
integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==
typescript@^5.4.5:
version "5.4.5"
resolved "https://registry.npmmirror.com/typescript/-/typescript-5.4.5.tgz#42ccef2c571fdbd0f6718b1d1f5e6e5ef006f611"
integrity sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==
undici-types@~5.26.4:
version "5.26.5"
resolved "https://registry.npmmirror.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
update-check@1.5.4:
version "1.5.4"
resolved "https://registry.npmmirror.com/update-check/-/update-check-1.5.4.tgz#5b508e259558f1ad7dbc8b4b0457d4c9d28c8743"
integrity sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==
dependencies:
registry-auth-token "3.3.2"
registry-url "3.1.0"
uri-js@^4.2.2:
version "4.4.1"
resolved "https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
dependencies:
punycode "^2.1.0"
vary@~1.1.2:
version "1.1.2"
resolved "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
which@^2.0.1:
version "2.0.2"
resolved "https://registry.npmmirror.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
dependencies:
isexe "^2.0.0"
widest-line@^4.0.1:
version "4.0.1"
resolved "https://registry.npmmirror.com/widest-line/-/widest-line-4.0.1.tgz#a0fc673aaba1ea6f0a0d35b3c2795c9a9cc2ebf2"
integrity sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==
dependencies:
string-width "^5.0.1"
wrap-ansi@^8.0.1:
version "8.1.0"
resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==
dependencies:
ansi-styles "^6.1.0"
string-width "^5.0.1"
strip-ansi "^7.0.1"
ws@^8.17.0:
version "8.17.0"
resolved "https://registry.npmmirror.com/ws/-/ws-8.17.0.tgz#d145d18eca2ed25aaf791a183903f7be5e295fea"
integrity sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==
yaml@^2.4.2:
version "2.4.2"
resolved "https://registry.npmmirror.com/yaml/-/yaml-2.4.2.tgz#7a2b30f2243a5fc299e1f14ca58d475ed4bc5362"
integrity sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA==