fix: bug fixes

This commit is contained in:
zhayujie
2026-02-02 22:22:10 +08:00
parent 5d02acbf37
commit 50e60e6d05
15 changed files with 865 additions and 575 deletions

View File

@@ -251,6 +251,14 @@ class FeiShuChanel(ChatChannel):
msg_type = "image"
content_key = "image_key"
elif reply.type == ReplyType.FILE:
# 如果有附加的文本内容,先发送文本
if hasattr(reply, 'text_content') and reply.text_content:
logger.info(f"[FeiShu] Sending text before file: {reply.text_content[:50]}...")
text_reply = Reply(ReplyType.TEXT, reply.text_content)
self._send(text_reply, context)
import time
time.sleep(0.3) # 短暂延迟,确保文本先到达
# 判断是否为视频文件
file_path = reply.content
if file_path.startswith("file://"):
@@ -259,20 +267,18 @@ class FeiShuChanel(ChatChannel):
is_video = file_path.lower().endswith(('.mp4', '.avi', '.mov', '.wmv', '.flv'))
if is_video:
# 视频使用 media 类型,需要上传并获取 file_key 和 duration
video_info = self._upload_video_url(reply.content, access_token)
if not video_info or not video_info.get('file_key'):
# 视频上传(包含duration信息)
upload_data = self._upload_video_url(reply.content, access_token)
if not upload_data or not upload_data.get('file_key'):
logger.warning("[FeiShu] upload video failed")
return
# media 类型需要特殊的 content 格式
# 视频使用 media 类型(根据官方文档)
# 错误码 230055 说明:上传 mp4 时必须使用 msg_type="media"
msg_type = "media"
# 注意media 类型的 content 不使用 content_key而是完整的 JSON 对象
reply_content = {
"file_key": video_info['file_key'],
"duration": video_info.get('duration', 0) # 视频时长(毫秒)
}
content_key = None # media 类型不使用单一的 key
reply_content = upload_data # 完整的上传响应数据包含file_key和duration
logger.info(f"[FeiShu] Sending video: file_key={upload_data.get('file_key')}, duration={upload_data.get('duration')}ms")
content_key = None # 直接序列化整个对象
else:
# 其他文件使用 file 类型
file_key = self._upload_file_url(reply.content, access_token)
@@ -286,12 +292,16 @@ class FeiShuChanel(ChatChannel):
# Check if we can reply to an existing message (need msg_id)
can_reply = is_group and msg and hasattr(msg, 'msg_id') and msg.msg_id
# Build content JSON
content_json = json.dumps(reply_content) if content_key is None else json.dumps({content_key: reply_content})
logger.debug(f"[FeiShu] Sending message: msg_type={msg_type}, content={content_json[:200]}")
if can_reply:
# 群聊中回复已有消息
url = f"https://open.feishu.cn/open-apis/im/v1/messages/{msg.msg_id}/reply"
data = {
"msg_type": msg_type,
"content": json.dumps(reply_content) if content_key is None else json.dumps({content_key: reply_content})
"content": content_json
}
res = requests.post(url=url, headers=headers, json=data, timeout=(5, 10))
else:
@@ -301,7 +311,7 @@ class FeiShuChanel(ChatChannel):
data = {
"receive_id": context.get("receiver"),
"msg_type": msg_type,
"content": json.dumps(reply_content) if content_key is None else json.dumps({content_key: reply_content})
"content": content_json
}
res = requests.post(url=url, headers=headers, params=params, json=data, timeout=(5, 10))
res = res.json()
@@ -471,9 +481,18 @@ class FeiShuChanel(ChatChannel):
file_type = file_type_map.get(file_ext, 'mp4')
upload_url = "https://open.feishu.cn/open-apis/im/v1/files"
data = {'file_type': file_type, 'file_name': file_name}
data = {
'file_type': file_type,
'file_name': file_name
}
# Add duration only if available (required for video/audio)
if duration:
data['duration'] = duration # Must be int, not string
headers = {'Authorization': f'Bearer {access_token}'}
logger.info(f"[FeiShu] Uploading video: file_name={file_name}, duration={duration}ms")
with open(local_path, "rb") as file:
upload_response = requests.post(
upload_url,
@@ -486,11 +505,11 @@ class FeiShuChanel(ChatChannel):
response_data = upload_response.json()
if response_data.get("code") == 0:
file_key = response_data.get("data").get("file_key")
return {
'file_key': file_key,
'duration': duration
}
# Add duration to the response data (API doesn't return it)
upload_data = response_data.get("data")
upload_data['duration'] = duration # Add our calculated duration
logger.info(f"[FeiShu] Upload complete: file_key={upload_data.get('file_key')}, duration={duration}ms")
return upload_data
else:
logger.error(f"[FeiShu] upload video failed: {response_data}")
return None

View File

@@ -762,7 +762,7 @@
<i class="fas fa-bars"></i>
</button>
<img id="header-logo" src="assets/logo.jpg" alt="AI Assistant Logo">
<div id="chat-title">AI 助手</div>
<div id="chat-title" class="agent-title">AI 助手</div>
<a id="github-link" href="https://github.com/zhayujie/chatgpt-on-wechat" target="_blank" rel="noopener noreferrer">
<img id="github-icon" src="assets/github.png" alt="GitHub">
</a>
@@ -771,21 +771,21 @@
<div id="messages">
<!-- 初始欢迎界面 -->
<div id="welcome-screen">
<h1 id="welcome-title">AI 助手</h1>
<p id="welcome-subtitle">我可以回答问题、提供信息或者帮助您完成各种任务</p>
<h1 id="welcome-title" class="agent-title">AI 助手</h1>
<p id="welcome-subtitle" class="agent-subtitle">我可以回答问题、提供信息或者帮助您完成各种任务</p>
<div class="examples-container">
<div class="example-card">
<div class="example-title">解释复杂概念</div>
<div class="example-text">用简单的语言解释量子计算</div>
<div class="example-title">📁 系统管理</div>
<div class="example-text">帮我查看工作空间里有哪些文件</div>
</div>
<div class="example-card">
<div class="example-title">创意写作</div>
<div class="example-text">写一个关于未来城市的短篇故事</div>
<div class="example-title">⏰ 智能任务</div>
<div class="example-text">提醒我5分钟后查看服务器情况</div>
</div>
<div class="example-card">
<div class="example-title">编程帮助</div>
<div class="example-text">如何用Python写一个简单的网络爬虫</div>
<div class="example-title">💻 编程助手</div>
<div class="example-text">帮我编写一个Python爬虫脚本</div>
</div>
<!-- <div class="example-card">
<div class="example-title">生活建议</div>
@@ -830,6 +830,45 @@
let sessionId = generateSessionId();
console.log('Session ID:', sessionId);
// 获取配置并更新标题
let appConfig = {
use_agent: false,
title: "AI 助手",
subtitle: "我可以回答问题、提供信息或者帮助您完成各种任务"
};
// 从服务器获取配置
axios.get('/config')
.then(response => {
if (response.data.status === "success") {
appConfig = response.data;
updateTitle(appConfig.title, appConfig.subtitle);
}
})
.catch(error => {
console.error('Error loading config:', error);
});
// 更新标题的函数
function updateTitle(title, subtitle) {
// 更新顶部标题
const chatTitle = document.getElementById('chat-title');
if (chatTitle) {
chatTitle.textContent = title;
}
// 更新欢迎屏幕标题
const welcomeTitle = document.getElementById('welcome-title');
if (welcomeTitle) {
welcomeTitle.textContent = title;
}
const welcomeSubtitle = document.getElementById('welcome-subtitle');
if (welcomeSubtitle) {
welcomeSubtitle.textContent = subtitle;
}
}
// 添加一个变量来跟踪输入法状态
let isComposing = false;
@@ -1215,21 +1254,21 @@
const newWelcomeScreen = document.createElement('div');
newWelcomeScreen.id = 'welcome-screen';
newWelcomeScreen.innerHTML = `
<h1 id="welcome-title">AI 助手</h1>
<p id="welcome-subtitle">我可以回答问题、提供信息或者帮助您完成各种任务</p>
<h1 id="welcome-title" class="agent-title">${appConfig.title}</h1>
<p id="welcome-subtitle" class="agent-subtitle">${appConfig.subtitle}</p>
<div class="examples-container">
<div class="example-card">
<div class="example-title">解释复杂概念</div>
<div class="example-text">用简单的语言解释量子计算</div>
<div class="example-title">📁 系统管理</div>
<div class="example-text">帮我查看工作空间里有哪些文件</div>
</div>
<div class="example-card">
<div class="example-title">创意写作</div>
<div class="example-text">写一个关于未来城市的短篇故事</div>
<div class="example-title">⏰ 智能任务</div>
<div class="example-text">提醒我5分钟后查看服务器情况</div>
</div>
<div class="example-card">
<div class="example-title">编程帮助</div>
<div class="example-text">如何用Python写一个简单的网络爬虫</div>
<div class="example-title">💻 编程助手</div>
<div class="example-text">帮我编写一个Python爬虫脚本</div>
</div>
</div>
`;

View File

@@ -218,6 +218,7 @@ class WebChannel(ChatChannel):
'/message', 'MessageHandler',
'/poll', 'PollHandler', # 添加轮询处理器
'/chat', 'ChatHandler',
'/config', 'ConfigHandler', # 添加配置处理器
'/assets/(.*)', 'AssetsHandler', # 匹配 /assets/任何路径
)
app = web.application(urls, globals(), autoreload=False)
@@ -262,6 +263,30 @@ class ChatHandler:
return f.read()
class ConfigHandler:
def GET(self):
"""返回前端需要的配置信息"""
try:
use_agent = conf().get("agent", False)
if use_agent:
title = "CowAgent"
subtitle = "我可以帮你解答问题、管理计算机、创造和执行技能,并通过长期记忆不断成长"
else:
title = "AI 助手"
subtitle = "我可以回答问题、提供信息或者帮助您完成各种任务"
return json.dumps({
"status": "success",
"use_agent": use_agent,
"title": title,
"subtitle": subtitle
})
except Exception as e:
logger.error(f"Error getting config: {e}")
return json.dumps({"status": "error", "message": str(e)})
class AssetsHandler:
def GET(self, file_path): # 修改默认参数
try: