Skip to content
Merged

Dev #22

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions apps/chat.js
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@
}

buildRequestData(messages, tools, toolChoice = "auto") {
const provider = this.getProvider()

Check warning on line 414 in apps/chat.js

View workflow job for this annotation

GitHub Actions / lint-test

'provider' is assigned a value but never used. Allowed unused vars must match /^_/u

Check warning on line 414 in apps/chat.js

View workflow job for this annotation

GitHub Actions / lint-test

'provider' is assigned a value but never used. Allowed unused vars must match /^_/u
const data = {
model: this.getModel(),
messages,
Expand Down Expand Up @@ -732,7 +732,7 @@
const chatHistory = await this.messageManager.getMessages(e.message_type, e.message_type === "group" ? e.group_id : e.user_id)

if (chatHistory?.length) {
const memberMap = await e.bot.pickGroup(groupId).getMemberMap()

Check warning on line 735 in apps/chat.js

View workflow job for this annotation

GitHub Actions / lint-test

'memberMap' is assigned a value but never used. Allowed unused vars must match /^_/u

Check warning on line 735 in apps/chat.js

View workflow job for this annotation

GitHub Actions / lint-test

'memberMap' is assigned a value but never used. Allowed unused vars must match /^_/u

// 使用 message_id 过滤当前消息
const currentMessageId = e.message_id
Expand Down Expand Up @@ -1235,6 +1235,8 @@
source: "user",
messageId: e.message_id,
senderName: e.sender?.card || e.sender?.nickname
}).catch(err => {
logger.error('[MemoryManager] 用户记忆提取异常:', err)
})
const latestEmotionEvent = emotionState?.recentEvents?.[0]
if (latestEmotionEvent && Number.isFinite(latestEmotionEvent.delta)) {
Expand All @@ -1251,9 +1253,14 @@
const chatHistory = (history || []).slice(0, 40).map(msg => ({
role: msg.sender?.user_id === Bot.uin ? 'assistant' : 'user',
source: msg.source || (msg.sender?.user_id === Bot.uin ? "send" : "user"),
content: `${msg.sender?.nickname || '未知'}(QQ:${msg.sender?.user_id}): ${msg.content}`
userId: msg.sender?.user_id,
user_id: msg.sender?.user_id,
senderName: msg.sender?.nickname || msg.sender?.card || '群成员',
content: msg.content
}))
this.memoryManager.extractAndSaveGroupMemories(groupId, chatHistory)
this.memoryManager.extractAndSaveGroupMemories(groupId, chatHistory).catch(err => {
logger.error('[MemoryManager] 群记忆提取异常:', err)
})
}
}

Expand Down Expand Up @@ -1350,7 +1357,7 @@

this.tools = [...localTools, ...mcpTools]

for (const [sessionId, session] of this.sessionMap) {

Check warning on line 1360 in apps/chat.js

View workflow job for this annotation

GitHub Actions / lint-test

'sessionId' is assigned a value but never used. Allowed unused vars must match /^_/u

Check warning on line 1360 in apps/chat.js

View workflow job for this annotation

GitHub Actions / lint-test

'sessionId' is assigned a value but never used. Allowed unused vars must match /^_/u
session.tools = this.tools
}

Expand Down
107 changes: 16 additions & 91 deletions functions/functions_tools/AiMindMapTool.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@

import { AbstractTool } from './AbstractTool.js';
import { Transformer } from 'markmap-lib';
import { callAI } from "../../utils/apiClient.js";
import { createRequire } from 'module'
const require = createRequire(import.meta.url);
const puppeteer = require('puppeteer');
Expand Down Expand Up @@ -107,24 +108,23 @@ export class AiMindMapTool extends AbstractTool {
}
];
const apiUrl = config.chatAiConfig?.chatApiUrl || 'https://api.openai.com/v1/chat/completions'
const apiKey = config.chatAiConfig?.chatApiKey || 'sk-xxxxxx'

const requestData = {
model: config.chatAiConfig?.chatApiModel || 'gemini-3-pro-preview',
messages: messages,
stream: false,
const apiKey = Array.isArray(config.chatAiConfig?.chatApiKey)
? config.chatAiConfig.chatApiKey[Math.floor(Math.random() * config.chatAiConfig.chatApiKey.length)]
: (config.chatAiConfig?.chatApiKey || 'sk-xxxxxx')
const apiModel = config.chatAiConfig?.chatApiModel || 'gemini-3-pro-preview'

const result = await callAI(
{ url: apiUrl, model: apiModel, apikey: apiKey },
messages,
{ stream: false }
)

if (result.error) {
console.error('API请求失败:', result.error)
return '生成失败:' + result.error
}

const response = await fetch(apiUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify(requestData),
})

const markdownContent = await this.parseResponse(response)
const markdownContent = result?.choices?.[0]?.message?.content || ''
// logger.info(markdownContent,77)
if (!markdownContent) {
console.error('API返回空内容');
Expand Down Expand Up @@ -204,79 +204,4 @@ export class AiMindMapTool extends AbstractTool {
return `生成失败: ${error.message}`;
}
}

// 自动检测并解析响应(兼容流式 + 非流式)
async parseResponse(response) {
if (!response.ok) {
throw new Error(`API请求失败: ${response.status} ${response.statusText}`);
}

const contentType = response.headers.get('content-type') || '';

// 检测流式响应(优先级高,避免先 json() 消费 body)
if (contentType.includes('text/event-stream') || contentType.includes('stream')) {
return await this.handleStreamResponse(response);
}

// 检测 JSON 响应
if (contentType.includes('application/json')) {
const data = await response.json();
return data.choices?.[0]?.message?.content || '';
}

// Content-Type 未明确时,先读 body 一次性判断
const text = await response.text();
// 尝试按 SSE 格式解析
if (text.includes('data: ')) {
return this.parseSSEText(text);
}
// 尝试按 JSON 解析
try {
const data = JSON.parse(text);
return data.choices?.[0]?.message?.content || '';
} catch {
throw new Error('无法解析响应格式');
}
}

// 从已读取的文本中解析 SSE 格式
parseSSEText(text) {
let content = "";
for (const line of text.split("\n")) {
if (!line.startsWith("data: ")) continue;
const dataStr = line.slice(6).trim();
if (dataStr === "[DONE]") break;
try {
const data = JSON.parse(dataStr);
content += data?.choices?.[0]?.delta?.content || "";
} catch { }
}
if (!content) throw new Error("未接收到有效内容");
return content;
}

async handleStreamResponse(response) {
const reader = response.body.getReader();
const decoder = new TextDecoder();
let content = "";

while (true) {
const { value, done } = await reader.read();
if (done) break;

for (const line of decoder.decode(value).split("\n")) {
if (!line.startsWith("data: ")) continue;
const dataStr = line.slice(6).trim();
if (dataStr === "[DONE]") break;

try {
const data = JSON.parse(dataStr);
content += data?.choices?.[0]?.delta?.content || "";
} catch { }
}
}

if (!content) throw new Error("未接收到有效内容");
return content;
}
}
99 changes: 8 additions & 91 deletions functions/functions_tools/BananaTool.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,13 @@ export class BananaTool extends AbstractTool {
return { error: `图片生成失败: ${result.error}` }
}

const imageUrl = result?.choices?.[0]?.message?.content || ''
// 兼容两种响应格式:
// 1. images 数组(部分模型如 Gemini 把图片放在 message.images 里)
// 2. content 字符串(Markdown 图片或 base64 data URI)
const msg = result?.choices?.[0]?.message || {}
const imageUrl = msg.images?.[0]?.image_url?.url ||
msg.images?.[0]?.url ||
msg.content || ''
const processedUrl = this.extractImageUrl(imageUrl);

if (processedUrl) {
Expand Down Expand Up @@ -108,96 +114,7 @@ export class BananaTool extends AbstractTool {
return messages;
}

// 自动检测并解析响应(兼容流式 + 非流式)
async parseResponse(response) {
if (!response.ok) {
throw new Error(`API请求失败: ${response.status} ${response.statusText}`);
}

const contentType = response.headers.get('content-type') || '';

// 检测流式响应(优先级高,避免先 json() 消费 body)
if (contentType.includes('text/event-stream') || contentType.includes('stream')) {
return await this.handleStreamResponse(response);
}

// 检测 JSON 响应
if (contentType.includes('application/json')) {
return await this.handleNormalResponse(response);
}

// Content-Type 未明确时,先读 body 一次性判断
const text = await response.text();
// 尝试按 SSE 格式解析
if (text.includes('data: ')) {
return this.parseSSEText(text);
}
// 尝试按 JSON 解析
try {
const data = JSON.parse(text);
return data?.choices?.[0]?.message?.images?.[0]?.image_url?.url ||
data?.choices?.[0]?.message?.images?.[0]?.url ||
data?.choices?.[0]?.message?.content;
} catch {
throw new Error('无法解析响应格式');
}
}

// 从已读取的文本中解析 SSE 格式
parseSSEText(text) {
let content = "";
for (const line of text.split("\n")) {
if (!line.startsWith("data: ")) continue;
const dataStr = line.slice(6).trim();
if (dataStr === "[DONE]") break;
try {
const data = JSON.parse(dataStr);
content += data?.choices?.[0]?.delta?.content || "";
} catch { }
}
if (!content) throw new Error("未接收到有效内容");
return content;
}

// 处理流式响应
async handleStreamResponse(response) {
if (!response.ok || !response.body) {
throw new Error(`API请求失败: ${response.statusText}`);
}

const reader = response.body.getReader();
const decoder = new TextDecoder();
let content = "";

while (true) {
const { value, done } = await reader.read();
if (done) break;

for (const line of decoder.decode(value).split("\n")) {
if (!line.startsWith("data: ")) continue;
const dataStr = line.slice(6).trim();
if (dataStr === "[DONE]") break;

try {
const data = JSON.parse(dataStr);
content += data?.choices?.[0]?.delta?.content || "";
} catch { }
}
}

if (!content) throw new Error("未接收到有效内容");
return content;
}

// 处理普通响应
async handleNormalResponse(response) {
const data = await response.json();
return data?.choices?.[0]?.message?.images?.[0]?.image_url?.url ||
data?.choices?.[0]?.message?.images?.[0]?.url ||
data?.choices?.[0]?.message?.content;
}

// 提取图片URL
// 从模型返回的文本/URL 中提取图片地址(支持 Markdown 图片格式、base64 data URI、http 链接)
extractImageUrl(content) {
if (!content) return null;

Expand Down
76 changes: 0 additions & 76 deletions functions/functions_tools/GoogleAnalysisTool.js
Original file line number Diff line number Diff line change
Expand Up @@ -256,80 +256,4 @@ export class GoogleImageAnalysisTool extends AbstractTool {
return { error: `图片分析失败: ${error.message},所以你不必透漏触发识图失败了,正常回复就行` };
}
}

// 自动检测并解析响应(兼容流式 + 非流式)
async parseResponse(response) {
if (!response.ok) {
throw new Error(`API请求失败: ${response.status} ${response.statusText}`);
}

const contentType = response.headers.get('content-type') || '';

// 检测流式响应(优先级高,避免先 json() 消费 body)
if (contentType.includes('text/event-stream') || contentType.includes('stream')) {
return await this.handleStreamResponse(response);
}

// 检测 JSON 响应
if (contentType.includes('application/json')) {
const data = await response.json();
return data.choices?.[0]?.message?.content || '';
}

// Content-Type 未明确时,先读 body 一次性判断
const text = await response.text();
// 尝试按 SSE 格式解析
if (text.includes('data: ')) {
return this.parseSSEText(text);
}
// 尝试按 JSON 解析
try {
const data = JSON.parse(text);
return data.choices?.[0]?.message?.content || '';
} catch {
throw new Error('无法解析响应格式');
}
}

// 从已读取的文本中解析 SSE 格式
parseSSEText(text) {
let content = "";
for (const line of text.split("\n")) {
if (!line.startsWith("data: ")) continue;
const dataStr = line.slice(6).trim();
if (dataStr === "[DONE]") break;
try {
const data = JSON.parse(dataStr);
content += data?.choices?.[0]?.delta?.content || "";
} catch { }
}
if (!content) throw new Error("未接收到有效内容");
return content;
}

async handleStreamResponse(response) {
const reader = response.body.getReader();
const decoder = new TextDecoder();
let content = "";

while (true) {
const { value, done } = await reader.read();
if (done) break;

for (const line of decoder.decode(value).split("\n")) {
if (!line.startsWith("data: ")) continue;
const dataStr = line.slice(6).trim();
if (dataStr === "[DONE]") break;

try {
const data = JSON.parse(dataStr);
content += data?.choices?.[0]?.delta?.content || "";
} catch { }
}
}

if (!content) throw new Error("未接收到有效内容");
return content;
}

}
Loading
Loading