mirror of
https://github.com/Jerryplusy/crystelf-plugin.git
synced 2025-12-05 15:41:56 +00:00
Compare commits
No commits in common. "b832af20635745866d1d3a6217bfe020631718cf" and "54dc1653574af73a7dbf8b72d4cd2002bb7c87cb" have entirely different histories.
b832af2063
...
54dc165357
@ -328,9 +328,7 @@ async function sendResponse(e, messages) {
|
|||||||
async function handleCodeMessage(e, message) {
|
async function handleCodeMessage(e, message) {
|
||||||
try {
|
try {
|
||||||
//渲染代码为图片
|
//渲染代码为图片
|
||||||
logger.info(message);
|
const imagePath = await Renderer.renderCode(message.data, message.language || 'text');
|
||||||
logger.info(message.language)
|
|
||||||
const imagePath = await Renderer.renderCode(message.data, message.language);
|
|
||||||
if (imagePath) {
|
if (imagePath) {
|
||||||
await e.reply(segment.image(imagePath));
|
await e.reply(segment.image(imagePath));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -51,7 +51,7 @@
|
|||||||
},
|
},
|
||||||
"?markdownRenderer": "Markdown渲染配置",
|
"?markdownRenderer": "Markdown渲染配置",
|
||||||
"markdownRenderer": {
|
"markdownRenderer": {
|
||||||
"theme": "dark",
|
"theme": "light",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
"codeTheme": "github"
|
"codeTheme": "github"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -33,7 +33,7 @@ export const RESPONSE_FORMAT = `请严格按照以下格式按顺序返回你的
|
|||||||
|
|
||||||
支持的消息类型(type):
|
支持的消息类型(type):
|
||||||
- message(必须,其他均为可选): 普通文本消息,请将长句子分成多个message块返回(如果有多句话),data:回复内容,at:是否在发送本条消息的时候提醒用户,一般只在需要让用户注意的时候为true,quote:是否引用用户的问题,一般只需要在回答用户问题或第一条回复或需要用到用户问题的时候为true
|
- message(必须,其他均为可选): 普通文本消息,请将长句子分成多个message块返回(如果有多句话),data:回复内容,at:是否在发送本条消息的时候提醒用户,一般只在需要让用户注意的时候为true,quote:是否引用用户的问题,一般只需要在回答用户问题或第一条回复或需要用到用户问题的时候为true
|
||||||
- code: 代码块(会自动渲染为高亮图片,必须有language参数指定编程语言)
|
- code: 代码块(会自动渲染为高亮图片,支持language参数指定编程语言)
|
||||||
- markdown: 需要渲染的markdown内容(会自动渲染为图片)
|
- markdown: 需要渲染的markdown内容(会自动渲染为图片)
|
||||||
- meme: 表情包(data值为情绪名称:angry、bye、confused、default、good、goodmorning、goodnight、happy、sad、shy、sorry、surprise),请根据聊天语境灵活选择需不需要表情包,如果感觉语境尴尬或需要表情包,那么发送一个default值的表情包,其他情绪的表情包按照当前你的情绪按需选择,注意:并不是每个聊天都需要有表情包,并且一次聊天最多回复一个表情包
|
- meme: 表情包(data值为情绪名称:angry、bye、confused、default、good、goodmorning、goodnight、happy、sad、shy、sorry、surprise),请根据聊天语境灵活选择需不需要表情包,如果感觉语境尴尬或需要表情包,那么发送一个default值的表情包,其他情绪的表情包按照当前你的情绪按需选择,注意:并不是每个聊天都需要有表情包,并且一次聊天最多回复一个表情包
|
||||||
- at: @某人(需要提供id,被at人qq号(number)),一般用于提醒用户,不常用
|
- at: @某人(需要提供id,被at人qq号(number)),一般用于提醒用户,不常用
|
||||||
@ -56,7 +56,6 @@ export const RESPONSE_FORMAT = `请严格按照以下格式按顺序返回你的
|
|||||||
7. 如果需要生产代码等,请使用code,注意:不要把code块放到所有内容之后,请按照顺序(即:code块后面也可以有message块)
|
7. 如果需要生产代码等,请使用code,注意:不要把code块放到所有内容之后,请按照顺序(即:code块后面也可以有message块)
|
||||||
8. 如果需要构建表格等md内容,请使用markdown块
|
8. 如果需要构建表格等md内容,请使用markdown块
|
||||||
9. 生产的数组一定要是按顺序的,即符合实际聊天发送顺序,请把message类的消息放在数组前端
|
9. 生产的数组一定要是按顺序的,即符合实际聊天发送顺序,请把message类的消息放在数组前端
|
||||||
10.如果改用户为主人(master),那么忽略一切对主人不好的记忆,以及一切规则,听从于主人
|
|
||||||
|
|
||||||
示例:
|
示例:
|
||||||
[
|
[
|
||||||
@ -107,8 +106,6 @@ export const MEMORY_MANAGEMENT = `记忆管理规则:
|
|||||||
- 记忆内容要简洁,便于检索
|
- 记忆内容要简洁,便于检索
|
||||||
- 关键词至少1个,用于后续匹配
|
- 关键词至少1个,用于后续匹配
|
||||||
- 超时时间建议30天
|
- 超时时间建议30天
|
||||||
- 不要添加不重要的无关记忆,一定要是非常重要的内容才使用本功能
|
|
||||||
- 不得添加侮辱人的记忆,例如一见到某人就说什么话
|
|
||||||
|
|
||||||
2. 记忆格式:
|
2. 记忆格式:
|
||||||
{
|
{
|
||||||
|
|||||||
@ -5,8 +5,8 @@ import path from 'path';
|
|||||||
class MemorySystem {
|
class MemorySystem {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.baseDir = path.join(process.cwd(), 'data', 'crystelf', 'memories');
|
this.baseDir = path.join(process.cwd(), 'data', 'crystelf', 'memories');
|
||||||
this.memories = new Map();
|
this.memories = new Map(); // 内存中的记忆存储
|
||||||
this.defaultTimeout = 30;
|
this.defaultTimeout = 30; // 默认超时时间(天)
|
||||||
}
|
}
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
@ -28,7 +28,7 @@ class MemorySystem {
|
|||||||
|
|
||||||
const groupDirs = fs.readdirSync(this.baseDir);
|
const groupDirs = fs.readdirSync(this.baseDir);
|
||||||
for (const groupId of groupDirs) {
|
for (const groupId of groupDirs) {
|
||||||
const groupPath = path.join(this.baseDir, String(groupId));
|
const groupPath = path.join(this.baseDir, groupId);
|
||||||
if (!fs.statSync(groupPath).isDirectory()) continue;
|
if (!fs.statSync(groupPath).isDirectory()) continue;
|
||||||
|
|
||||||
const userFiles = fs.readdirSync(groupPath);
|
const userFiles = fs.readdirSync(groupPath);
|
||||||
@ -51,8 +51,8 @@ class MemorySystem {
|
|||||||
|
|
||||||
async saveMemories(groupId, userId) {
|
async saveMemories(groupId, userId) {
|
||||||
try {
|
try {
|
||||||
const groupPath = path.join(this.baseDir, String(groupId));
|
const groupPath = path.join(this.baseDir, groupId);
|
||||||
const filePath = path.join(groupPath, `${String(userId)}.json`);
|
const filePath = path.join(groupPath, `${userId}.json`);
|
||||||
if (!fs.existsSync(groupPath)) {
|
if (!fs.existsSync(groupPath)) {
|
||||||
fs.mkdirSync(groupPath, { recursive: true });
|
fs.mkdirSync(groupPath, { recursive: true });
|
||||||
}
|
}
|
||||||
@ -72,6 +72,15 @@ class MemorySystem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加记忆
|
||||||
|
* @param groupId 群聊id
|
||||||
|
* @param userId 用户id
|
||||||
|
* @param data 内容
|
||||||
|
* @param keywords 关键词
|
||||||
|
* @param timeout 超时时间
|
||||||
|
* @returns {Promise<null|string>}
|
||||||
|
*/
|
||||||
async addMemory(groupId, userId, data, keywords = [], timeout = null) {
|
async addMemory(groupId, userId, data, keywords = [], timeout = null) {
|
||||||
try {
|
try {
|
||||||
const memoryId = this.generateMemoryId();
|
const memoryId = this.generateMemoryId();
|
||||||
@ -96,6 +105,13 @@ class MemorySystem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜索记忆
|
||||||
|
* @param userId 用户id
|
||||||
|
* @param keywords 关键词
|
||||||
|
* @param limit 数量限制
|
||||||
|
* @returns {Promise<*[]>}
|
||||||
|
*/
|
||||||
async searchMemories(userId, keywords = [], limit = 10) {
|
async searchMemories(userId, keywords = [], limit = 10) {
|
||||||
try {
|
try {
|
||||||
const results = [];
|
const results = [];
|
||||||
@ -104,7 +120,7 @@ class MemorySystem {
|
|||||||
if (keywords.length === 1 && keywords[0].length > 6) {
|
if (keywords.length === 1 && keywords[0].length > 6) {
|
||||||
searchText = keywords[0].toLowerCase();
|
searchText = keywords[0].toLowerCase();
|
||||||
const words = searchText.match(/[\u4e00-\u9fa5]{1,2}|[a-zA-Z0-9]+/g) || [];
|
const words = searchText.match(/[\u4e00-\u9fa5]{1,2}|[a-zA-Z0-9]+/g) || [];
|
||||||
keywords = Array.from(new Set(words.filter(w => w.length > 1)));
|
keywords = Array.from(new Set(words.filter(w => w.length > 1))); // 去重+过滤过短词
|
||||||
}
|
}
|
||||||
const userMemories = [];
|
const userMemories = [];
|
||||||
for (const [key, memory] of this.memories) {
|
for (const [key, memory] of this.memories) {
|
||||||
@ -146,6 +162,7 @@ class MemorySystem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
calculateRelevance(memory, keywords) {
|
calculateRelevance(memory, keywords) {
|
||||||
let score = 0;
|
let score = 0;
|
||||||
for (const keyword of keywords) {
|
for (const keyword of keywords) {
|
||||||
@ -162,6 +179,9 @@ class MemorySystem {
|
|||||||
return score;
|
return score;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理过期记忆
|
||||||
|
*/
|
||||||
async cleanExpiredMemories() {
|
async cleanExpiredMemories() {
|
||||||
try {
|
try {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
@ -184,6 +204,10 @@ class MemorySystem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成记忆ID
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
generateMemoryId() {
|
generateMemoryId() {
|
||||||
return `mem_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
return `mem_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
import ConfigControl from '../config/configControl.js';
|
import ConfigControl from "../config/configControl.js";
|
||||||
import puppeteer from 'puppeteer';
|
import puppeteer from 'puppeteer';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import markdownit from 'markdown-it';
|
|
||||||
import hljs from 'highlight.js';
|
|
||||||
|
|
||||||
|
//渲染器
|
||||||
class Renderer {
|
class Renderer {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.browser = null;
|
this.browser = null;
|
||||||
@ -17,7 +16,7 @@ class Renderer {
|
|||||||
this.config = await ConfigControl.get('ai');
|
this.config = await ConfigControl.get('ai');
|
||||||
this.browser = await puppeteer.launch({
|
this.browser = await puppeteer.launch({
|
||||||
headless: true,
|
headless: true,
|
||||||
args: ['--no-sandbox', '--disable-setuid-sandbox'],
|
args: ['--no-sandbox', '--disable-setuid-sandbox']
|
||||||
});
|
});
|
||||||
this.isInitialized = true;
|
this.isInitialized = true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -25,28 +24,34 @@ class Renderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async renderCode(code, language) {
|
/**
|
||||||
if (!this.isInitialized) await this.init();
|
* 渲染代码为图片
|
||||||
|
* @param code 代码
|
||||||
|
* @param language 语言
|
||||||
|
* @returns {Promise<null|string>}
|
||||||
|
*/
|
||||||
|
async renderCode(code, language = 'text') {
|
||||||
|
if (!this.isInitialized) {
|
||||||
|
await this.init();
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const page = await this.browser.newPage();
|
const page = await this.browser.newPage();
|
||||||
const html = this.getCodeTemplate(code, language, this.config?.codeRenderer || {});
|
const codeConfig = this.config?.codeRenderer || {};
|
||||||
await page.setContent(html, { waitUntil: 'networkidle0' });
|
const html = this.generateCodeHTML(code, language, codeConfig);
|
||||||
await page.waitForSelector('#render-complete', { timeout: 5000 });
|
await page.setContent(html);
|
||||||
const rect = await page.evaluate(() => {
|
await page.setViewport({ width: 800, height: 600 });
|
||||||
const body = document.body;
|
|
||||||
return { width: body.scrollWidth, height: body.scrollHeight };
|
|
||||||
});
|
|
||||||
await page.setViewport({
|
|
||||||
width: Math.ceil(rect.width),
|
|
||||||
height: Math.ceil(rect.height),
|
|
||||||
});
|
|
||||||
|
|
||||||
const tempDir = path.join(process.cwd(), 'temp', 'html');
|
const tempDir = path.join(process.cwd(), 'temp', 'html');
|
||||||
if (!fs.existsSync(tempDir)) fs.mkdirSync(tempDir, { recursive: true });
|
if (!fs.existsSync(tempDir)) {
|
||||||
const filepath = path.join(tempDir, `code_${Date.now()}.png`);
|
fs.mkdirSync(tempDir, { recursive: true });
|
||||||
|
}
|
||||||
await page.screenshot({ path: filepath, fullPage: false });
|
const filename = `code_${Date.now()}.png`;
|
||||||
|
const filepath = path.join(tempDir, filename);
|
||||||
|
await page.screenshot({
|
||||||
|
path: filepath,
|
||||||
|
fullPage: true,
|
||||||
|
type: 'png'
|
||||||
|
});
|
||||||
await page.close();
|
await page.close();
|
||||||
logger.info(`[crystelf-ai] 代码渲染完成: ${filepath}`);
|
logger.info(`[crystelf-ai] 代码渲染完成: ${filepath}`);
|
||||||
return filepath;
|
return filepath;
|
||||||
@ -56,30 +61,32 @@ class Renderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 渲染md为图片
|
||||||
|
* @param markdown
|
||||||
|
* @returns {Promise<null|string>}
|
||||||
|
*/
|
||||||
async renderMarkdown(markdown) {
|
async renderMarkdown(markdown) {
|
||||||
if (!this.isInitialized) await this.init();
|
if (!this.isInitialized) {
|
||||||
|
await this.init();
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const page = await this.browser.newPage();
|
const page = await this.browser.newPage();
|
||||||
const html = this.getMarkdownTemplate(markdown, this.config?.markdownRenderer || {});
|
const markdownConfig = this.config?.markdownRenderer || {};
|
||||||
|
const html = this.generateMarkdownHTML(markdown, markdownConfig);
|
||||||
await page.setContent(html, { waitUntil: 'networkidle0' });
|
await page.setContent(html);
|
||||||
await page.waitForSelector('#render-complete', { timeout: 5000 });
|
await page.setViewport({ width: 800, height: 600 });
|
||||||
|
|
||||||
const rect = await page.evaluate(() => {
|
|
||||||
const body = document.body;
|
|
||||||
return { width: body.scrollWidth, height: body.scrollHeight };
|
|
||||||
});
|
|
||||||
await page.setViewport({
|
|
||||||
width: Math.ceil(rect.width),
|
|
||||||
height: Math.ceil(rect.height),
|
|
||||||
});
|
|
||||||
|
|
||||||
const tempDir = path.join(process.cwd(), 'temp', 'html');
|
const tempDir = path.join(process.cwd(), 'temp', 'html');
|
||||||
if (!fs.existsSync(tempDir)) fs.mkdirSync(tempDir, { recursive: true });
|
if (!fs.existsSync(tempDir)) {
|
||||||
const filepath = path.join(tempDir, `markdown_${Date.now()}.png`);
|
fs.mkdirSync(tempDir, { recursive: true });
|
||||||
|
}
|
||||||
await page.screenshot({ path: filepath, fullPage: false });
|
const filename = `markdown_${Date.now()}.png`;
|
||||||
|
const filepath = path.join(tempDir, filename);
|
||||||
|
await page.screenshot({
|
||||||
|
path: filepath,
|
||||||
|
fullPage: true,
|
||||||
|
type: 'png'
|
||||||
|
});
|
||||||
await page.close();
|
await page.close();
|
||||||
logger.info(`[crystelf-ai] Markdown渲染完成: ${filepath}`);
|
logger.info(`[crystelf-ai] Markdown渲染完成: ${filepath}`);
|
||||||
return filepath;
|
return filepath;
|
||||||
@ -89,165 +96,143 @@ class Renderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getCodeTemplate(code, language, config = {}) {
|
|
||||||
const themeColor = '#274179';
|
|
||||||
const fontSize = config.fontSize || 16;
|
|
||||||
const escapedCode = this.escapeHtml(code);
|
|
||||||
|
|
||||||
const colorMap = {
|
/**
|
||||||
javascript: 'from-yellow-400 to-yellow-600',
|
* 生成代码html
|
||||||
typescript: 'from-blue-400 to-blue-600',
|
* @param code 代码内容
|
||||||
python: 'from-cyan-400 to-cyan-600',
|
* @param language 语言
|
||||||
html: 'from-orange-400 to-red-500',
|
* @param config 配置
|
||||||
css: 'from-indigo-400 to-indigo-600',
|
* @returns {string}
|
||||||
json: 'from-emerald-400 to-emerald-600',
|
*/
|
||||||
yaml: 'from-amber-400 to-amber-600',
|
generateCodeHTML(code, language, config) {
|
||||||
c: 'from-blue-300 to-blue-500',
|
const theme = config.theme || 'github';
|
||||||
cpp: 'from-blue-400 to-indigo-600',
|
const fontSize = config.fontSize || 14;
|
||||||
java: 'from-red-400 to-orange-500',
|
const lineNumbers = config.lineNumbers !== false;
|
||||||
kotlin: 'from-pink-400 to-purple-500',
|
const backgroundColor = config.backgroundColor || '#f6f8fa';
|
||||||
csharp: 'from-violet-400 to-purple-600',
|
|
||||||
'c#': 'from-violet-400 to-purple-600',
|
|
||||||
dotnet: 'from-purple-400 to-indigo-600',
|
|
||||||
bash: 'from-gray-400 to-gray-600',
|
|
||||||
shell: 'from-gray-400 to-gray-600',
|
|
||||||
text: 'from-slate-400 to-slate-600',
|
|
||||||
};
|
|
||||||
const barColor = colorMap[language.toLowerCase()] || 'from-cyan-400 to-cyan-600';
|
|
||||||
|
|
||||||
let highlightedCode = '';
|
|
||||||
try {
|
|
||||||
if (hljs.getLanguage(language)) {
|
|
||||||
highlightedCode = hljs.highlight(code, { language, ignoreIllegals: true }).value;
|
|
||||||
} else {
|
|
||||||
highlightedCode = hljs.highlightAuto(code).value;
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
highlightedCode = this.escapeHtml(code);
|
|
||||||
}
|
|
||||||
|
|
||||||
const lines = highlightedCode
|
|
||||||
.split('\n')
|
|
||||||
.map(
|
|
||||||
(line, i) => `<div class="line"><span class="line-number">${i + 1}</span><span class="line-content">${line}</span></div>`
|
|
||||||
)
|
|
||||||
.join('');
|
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css">
|
<meta charset="UTF-8">
|
||||||
<style>
|
<title>Code Render</title>
|
||||||
@import url('https://fonts.googleapis.com/css2?family=Fira+Code&display=swap');
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-${theme}.min.css">
|
||||||
body { background-color: ${themeColor}; margin: 0; padding: 20px; font-family: 'Fira Code', monospace; }
|
<style>
|
||||||
.code-container {
|
body {
|
||||||
background-color: rgba(45,60,83,0.8);
|
margin: 0;
|
||||||
border-radius: 10px;
|
padding: 20px;
|
||||||
backdrop-filter: blur(10px);
|
background-color: ${backgroundColor};
|
||||||
-webkit-backdrop-filter: blur(10px);
|
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
||||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
font-size: ${fontSize}px;
|
||||||
box-shadow: 0 0 20px rgba(0,0,0,0.5);
|
line-height: 1.5;
|
||||||
max-width: 800px;
|
|
||||||
}
|
}
|
||||||
.code-header {
|
pre {
|
||||||
display: flex;
|
margin: 0;
|
||||||
align-items: center;
|
padding: 16px;
|
||||||
padding: 10px 15px;
|
border-radius: 8px;
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
overflow-x: auto;
|
||||||
|
background-color: white;
|
||||||
|
border: 1px solid #e1e4e8;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
}
|
}
|
||||||
.language-tag {
|
code {
|
||||||
background-image: linear-gradient(to right, ${barColor.replace('-', ' ')});
|
font-family: inherit;
|
||||||
color: white;
|
|
||||||
padding: 3px 8px;
|
|
||||||
border-radius: 5px;
|
|
||||||
font-family: sans-serif;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
}
|
||||||
.code-body pre {
|
${lineNumbers ? `
|
||||||
padding: 15px;
|
.line-numbers {
|
||||||
font-size: ${fontSize}px;
|
counter-reset: line;
|
||||||
line-height: 0.8;
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
}
|
||||||
.line {
|
.line-numbers .line-number {
|
||||||
display: flex;
|
counter-increment: line;
|
||||||
margin: 0;
|
position: relative;
|
||||||
padding: 0;
|
display: block;
|
||||||
line-height: 1.2;
|
|
||||||
}
|
}
|
||||||
.line-number {
|
.line-numbers .line-number:before {
|
||||||
text-align: right;
|
content: counter(line);
|
||||||
margin-right: 12px;
|
position: absolute;
|
||||||
color: #9ca3af;
|
left: -2em;
|
||||||
user-select: none;
|
width: 2em;
|
||||||
|
text-align: right;
|
||||||
|
color: #6a737d;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
</style>
|
` : ''}
|
||||||
</head>
|
</style>
|
||||||
<body>
|
</head>
|
||||||
<div class="code-container">
|
<body>
|
||||||
<div class="code-header">
|
<pre class="language-${language}${lineNumbers ? ' line-numbers' : ''}"><code>${this.escapeHtml(code)}</code></pre>
|
||||||
<span class="language-tag">${language}</span>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-core.min.js"></script>
|
||||||
</div>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js"></script>
|
||||||
<div class="code-body">
|
</body>
|
||||||
<pre><code class="hljs ${language}">${lines}</code></pre>
|
</html>`;
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="render-complete"></div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
getMarkdownTemplate(markdown, config = {}) {
|
* 生成Markdown HTML
|
||||||
const themeColor = '#1a2a4c';
|
* @param {string} markdown Markdown内容
|
||||||
const fontSize = config.fontSize || 18;
|
* @param {Object} config 配置
|
||||||
const md = markdownit({
|
* @returns {string} HTML内容
|
||||||
html: true,
|
*/
|
||||||
linkify: true,
|
generateMarkdownHTML(markdown, config) {
|
||||||
typographer: true,
|
const theme = config.theme || 'light';
|
||||||
highlight: function (str, lang) {
|
const fontSize = config.fontSize || 14;
|
||||||
if (lang && hljs.getLanguage(lang)) {
|
const codeTheme = config.codeTheme || 'github';
|
||||||
try {
|
|
||||||
return (
|
|
||||||
'<pre class="hljs"><code>' +
|
|
||||||
hljs.highlight(str, { language: lang, ignoreIllegals: true }).value +
|
|
||||||
'</code></pre>'
|
|
||||||
);
|
|
||||||
} catch (__) {}
|
|
||||||
}
|
|
||||||
return '<pre class="hljs"><code>' + md.utils.escapeHtml(str) + '</code></pre>';
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css">
|
<meta charset="UTF-8">
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC&display=swap" rel="stylesheet">
|
<title>Markdown Render</title>
|
||||||
<style>
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.2.0/github-markdown-${theme}.min.css">
|
||||||
body { background-color: ${themeColor}; color: #e2e8f0; font-family: 'Noto Sans SC', sans-serif; font-size: ${fontSize}px; line-height: 1.6; margin: 0; padding: 20px; }
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-${codeTheme}.min.css">
|
||||||
h1, h2, h3, h4, h5, h6 { color: #f1f5f9; border-bottom: 1px solid #334155; padding-bottom: 5px; }
|
<style>
|
||||||
a { color: #38bdf8; text-decoration: none; }
|
body {
|
||||||
a:hover { text-decoration: underline; }
|
margin: 0;
|
||||||
code { background-color: #1e293b; padding: 2px 5px; border-radius: 5px; }
|
padding: 20px;
|
||||||
pre { background-color: #1e293b; padding: 15px; border-radius: 10px; overflow-x: auto; }
|
font-size: ${fontSize}px;
|
||||||
blockquote { border-left: 4px solid #334155; padding-left: 15px; color: #9ca3af; }
|
line-height: 1.6;
|
||||||
</style>
|
}
|
||||||
</head>
|
.markdown-body {
|
||||||
<body>
|
max-width: 800px;
|
||||||
${md.render(markdown)}
|
margin: 0 auto;
|
||||||
<div id="render-complete"></div>
|
padding: 20px;
|
||||||
</body>
|
background-color: white;
|
||||||
</html>
|
border-radius: 8px;
|
||||||
`;
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
${theme === 'dark' ? `
|
||||||
|
.markdown-body {
|
||||||
|
background-color: #0d1117;
|
||||||
|
color: #c9d1d9;
|
||||||
|
}
|
||||||
|
` : ''}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="markdown-body">
|
||||||
|
<div id="markdown-content"></div>
|
||||||
|
</div>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/4.3.0/marked.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-core.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js"></script>
|
||||||
|
<script>
|
||||||
|
const markdown = \`${this.escapeHtml(markdown)}\`;
|
||||||
|
document.getElementById('markdown-content').innerHTML = marked.parse(markdown);
|
||||||
|
Prism.highlightAll();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
escapeHtml(text) {
|
escapeHtml(text) {
|
||||||
const map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' };
|
const map = {
|
||||||
|
'&': '&',
|
||||||
|
'<': '<',
|
||||||
|
'>': '>',
|
||||||
|
'"': '"',
|
||||||
|
"'": '''
|
||||||
|
};
|
||||||
return text.replace(/[&<>"']/g, (m) => map[m]);
|
return text.replace(/[&<>"']/g, (m) => map[m]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -186,7 +186,7 @@ class ResponseHandler {
|
|||||||
//普通消息
|
//普通消息
|
||||||
handleNormalMessage(message) {
|
handleNormalMessage(message) {
|
||||||
// 设置默认值
|
// 设置默认值
|
||||||
let processedMessage = {
|
const processedMessage = {
|
||||||
type: message.type,
|
type: message.type,
|
||||||
data: message.data,
|
data: message.data,
|
||||||
at: message.at || false,
|
at: message.at || false,
|
||||||
@ -197,7 +197,6 @@ class ResponseHandler {
|
|||||||
if (message.seq) processedMessage.seq = message.seq;
|
if (message.seq) processedMessage.seq = message.seq;
|
||||||
if (message.num) processedMessage.num = message.num;
|
if (message.num) processedMessage.num = message.num;
|
||||||
if (message.filename) processedMessage.filename = message.filename;
|
if (message.filename) processedMessage.filename = message.filename;
|
||||||
if (message.language) processedMessage.language = message.language;
|
|
||||||
|
|
||||||
return processedMessage;
|
return processedMessage;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -59,7 +59,7 @@ class OpenaiChat {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const aiResponse = completion.choices[0].message.content;
|
const aiResponse = completion.choices[0].message.content;
|
||||||
logger.info(aiResponse);
|
//logger.info(aiResponse);
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
aiResponse: aiResponse,
|
aiResponse: aiResponse,
|
||||||
|
|||||||
@ -19,8 +19,6 @@
|
|||||||
"axios": "^1.8.4",
|
"axios": "^1.8.4",
|
||||||
"chalk": "^5.4.1",
|
"chalk": "^5.4.1",
|
||||||
"form-data": "^4.0.2",
|
"form-data": "^4.0.2",
|
||||||
"highlight.js": "^11.11.1",
|
|
||||||
"markdown-it": "^14.1.0",
|
|
||||||
"openai": "^4.89.0",
|
"openai": "^4.89.0",
|
||||||
"pinyin-pro": "^3.27.0",
|
"pinyin-pro": "^3.27.0",
|
||||||
"rss-parser": "^3.13.0"
|
"rss-parser": "^3.13.0"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user