diff --git a/astrbot/core/platform/sources/gewechat/client.py b/astrbot/core/platform/sources/gewechat/client.py index 53ee1878..499bc76b 100644 --- a/astrbot/core/platform/sources/gewechat/client.py +++ b/astrbot/core/platform/sources/gewechat/client.py @@ -499,6 +499,23 @@ class SimpleGewechatClient: json_blob = await resp.json() logger.debug(f"发送图片结果: {json_blob}") + async def post_video( + self, to_wxid, video_url: str, thumb_url: str, video_duration: int + ): + payload = { + "appId": self.appid, + "toWxid": to_wxid, + "videoUrl": video_url, + "thumbUrl": thumb_url, + "videoDuration": video_duration, + } + async with aiohttp.ClientSession() as session: + async with session.post( + f"{self.base_url}/message/postVideo", headers=self.headers, json=payload + ) as resp: + json_blob = await resp.json() + logger.debug(f"发送视频结果: {json_blob}") + async def post_voice(self, to_wxid, voice_url: str, voice_duration: int): """发送语音信息 diff --git a/astrbot/core/platform/sources/gewechat/gewechat_event.py b/astrbot/core/platform/sources/gewechat/gewechat_event.py index 3aca64ba..5b74a63e 100644 --- a/astrbot/core/platform/sources/gewechat/gewechat_event.py +++ b/astrbot/core/platform/sources/gewechat/gewechat_event.py @@ -2,12 +2,13 @@ import wave import uuid import traceback import os + from astrbot.core.utils.io import save_temp_img, download_file from astrbot.core.utils.tencent_record_helper import wav_to_tencent_silk from astrbot.api import logger from astrbot.api.event import AstrMessageEvent, MessageChain from astrbot.api.platform import AstrBotMessage, PlatformMetadata, Group, MessageMember -from astrbot.api.message_components import Plain, Image, Record, At, File +from astrbot.api.message_components import Plain, Image, Record, At, File, Video from .client import SimpleGewechatClient @@ -82,6 +83,56 @@ class GewechatPlatformEvent(AstrMessageEvent): img_url = f"{client.file_server_url}/{file_id}" logger.debug(f"gewe callback img url: {img_url}") await client.post_image(to_wxid, img_url) + elif isinstance(comp, Video): + try: + from pyffmpeg import FFmpeg + except (ImportError, ModuleNotFoundError): + logger.error( + "需要安装 pyffmpeg 库才能发送视频: pip install pyffmpeg" + ) + raise ModuleNotFoundError( + "需要安装 pyffmpeg 库才能发送视频: pip install pyffmpeg" + ) + + video_url = comp.file + # 根据 url 下载视频 + video_filename = f"{uuid.uuid4()}.mp4" + video_path = f"data/temp/{video_filename}" + await download_file(video_url, video_path) + + # 获取视频第一帧 + thumb_path = f"data/temp/{uuid.uuid4()}.jpg" + try: + ff = FFmpeg() + command = f'-i "{video_path}" -ss 0 -vframes 1 "{thumb_path}"' + ff.options(command) + thumb_file_id = os.path.basename(thumb_path) + thumb_url = f"{client.file_server_url}/{thumb_file_id}" + except Exception as e: + logger.error(f"获取视频第一帧失败: {e}") + # 获取视频时长 + try: + from pyffmpeg import FFprobe + + # 创建 FFprobe 实例 + ffprobe = FFprobe(video_url) + # 获取时长字符串 + duration_str = ffprobe.duration + # 处理时长字符串 + video_duration = float(duration_str.replace(":", "")) + except Exception as e: + logger.error(f"获取时长失败: {e}") + video_duration = 10 + + file_id = os.path.basename(video_path) + video_url = f"{client.file_server_url}/{file_id}" + await client.post_video(to_wxid, video_url, thumb_url, video_duration) + + # 删除临时视频和缩略图文件 + if os.path.exists(video_path): + os.remove(video_path) + if os.path.exists(thumb_path): + os.remove(thumb_path) elif isinstance(comp, Record): # 默认已经存在 data/temp 中 record_url = comp.file diff --git a/astrbot/dashboard/routes/stat.py b/astrbot/dashboard/routes/stat.py index b20e9dca..04b2d21e 100644 --- a/astrbot/dashboard/routes/stat.py +++ b/astrbot/dashboard/routes/stat.py @@ -1,6 +1,7 @@ import traceback import psutil import time +import threading from .route import Route, Response, RouteContext from astrbot.core import logger from quart import request @@ -64,6 +65,25 @@ class StatRoute(Route): stat_dict = stat.__dict__ + # 获取CPU使用率 - 修复CPU始终为0的问题 + process = psutil.Process() + # 获取系统CPU使用率而不是进程CPU使用率 + cpu_percent = psutil.cpu_percent(interval=0.5) + + # 获取线程数 + thread_count = threading.active_count() + + # 获取插件信息 + plugins = self.core_lifecycle.star_context.get_all_stars() + plugin_info = [] + for plugin in plugins: + info = { + "name": getattr(plugin, "name", plugin.__class__.__name__), + "version": getattr(plugin, "version", "1.0.0"), + "is_enabled": True + } + plugin_info.append(info) + stat_dict.update( { "platform": self.db_helper.get_grouped_base_stats( @@ -73,9 +93,8 @@ class StatRoute(Route): "platform_count": len( self.core_lifecycle.platform_manager.get_insts() ), - "plugin_count": len( - self.core_lifecycle.star_context.get_all_stars() - ), + "plugin_count": len(plugins), + "plugins": plugin_info, "message_time_series": message_time_based_stats, "running": self.format_sec( int(time.time()) - self.core_lifecycle.start_time @@ -84,6 +103,9 @@ class StatRoute(Route): "process": psutil.Process().memory_info().rss >> 20, "system": psutil.virtual_memory().total >> 20, }, + "cpu_percent": round(cpu_percent, 1), + "thread_count": thread_count, + "start_time": self.core_lifecycle.start_time, } ) diff --git a/dashboard/src/components/shared/AstrBotConfig.vue b/dashboard/src/components/shared/AstrBotConfig.vue index 2796f95d..2e24af80 100644 --- a/dashboard/src/components/shared/AstrBotConfig.vue +++ b/dashboard/src/components/shared/AstrBotConfig.vue @@ -1,156 +1,374 @@ \ No newline at end of file + + + \ No newline at end of file diff --git a/dashboard/src/views/ChatPage.vue b/dashboard/src/views/ChatPage.vue index 8f412cec..346dde07 100644 --- a/dashboard/src/views/ChatPage.vue +++ b/dashboard/src/views/ChatPage.vue @@ -8,164 +8,181 @@ marked.setOptions({ + \ No newline at end of file diff --git a/dashboard/src/views/dashboards/default/DefaultDashboard.vue b/dashboard/src/views/dashboards/default/DefaultDashboard.vue index e904bf3a..859d6226 100644 --- a/dashboard/src/views/dashboards/default/DefaultDashboard.vue +++ b/dashboard/src/views/dashboards/default/DefaultDashboard.vue @@ -1,37 +1,84 @@ + + diff --git a/dashboard/src/views/dashboards/default/components/MemoryUsage.vue b/dashboard/src/views/dashboards/default/components/MemoryUsage.vue new file mode 100644 index 00000000..79d0ecb2 --- /dev/null +++ b/dashboard/src/views/dashboards/default/components/MemoryUsage.vue @@ -0,0 +1,140 @@ + + + + + diff --git a/dashboard/src/views/dashboards/default/components/MessageStat.vue b/dashboard/src/views/dashboards/default/components/MessageStat.vue index 98b56819..6856c302 100644 --- a/dashboard/src/views/dashboards/default/components/MessageStat.vue +++ b/dashboard/src/views/dashboards/default/components/MessageStat.vue @@ -1,65 +1,136 @@ - - \ No newline at end of file + methods: { + formatNumber(num) { + return new Intl.NumberFormat('zh-CN').format(num); + }, + + async fetchMessageSeries() { + this.loading = true; + + try { + const response = await axios.get(`/api/stat/get?offset_sec=${this.selectedTimeRange.value}`); + const data = response.data.data; + + if (data && data.message_time_series) { + this.messageTimeSeries = data.message_time_series; + this.processTimeSeriesData(); + } + } catch (error) { + console.error('获取消息趋势数据失败:', error); + } finally { + this.loading = false; + } + }, + + processTimeSeriesData() { + // 转换数据为图表格式 + this.chartSeries[0].data = this.messageTimeSeries.map((item) => { + return [new Date(item[0]*1000).getTime(), item[1]]; + }); + + // 计算总消息数 + let total = 0; + this.messageTimeSeries.forEach(item => { + total += item[1]; + }); + this.totalMessages = this.formatNumber(total); + + // 计算日平均 + if (this.messageTimeSeries.length > 0) { + const daysSpan = this.selectedTimeRange.value / 86400; // 将秒转换为天数 + this.dailyAverage = this.formatNumber(Math.round(total / daysSpan)); + } + + // 计算增长率 + this.calculateGrowthRate(); + }, + + calculateGrowthRate() { + if (this.messageTimeSeries.length < 4) { + this.growthRate = 0; + return; + } + + // 计算前半部分和后半部分的消息总数 + const halfIndex = Math.floor(this.messageTimeSeries.length / 2); + + const firstHalf = this.messageTimeSeries + .slice(0, halfIndex) + .reduce((sum, item) => sum + item[1], 0); + + const secondHalf = this.messageTimeSeries + .slice(halfIndex) + .reduce((sum, item) => sum + item[1], 0); + + // 计算增长率 + if (firstHalf > 0) { + this.growthRate = Math.round(((secondHalf - firstHalf) / firstHalf) * 100); + } else { + this.growthRate = secondHalf > 0 ? 100 : 0; + } + } + } +}; + + + \ No newline at end of file diff --git a/dashboard/src/views/dashboards/default/components/OnlinePlatform.vue b/dashboard/src/views/dashboards/default/components/OnlinePlatform.vue index 26fd6e8a..3eee6aaa 100644 --- a/dashboard/src/views/dashboards/default/components/OnlinePlatform.vue +++ b/dashboard/src/views/dashboards/default/components/OnlinePlatform.vue @@ -1,37 +1,84 @@ - - - \ No newline at end of file + + + \ No newline at end of file diff --git a/dashboard/src/views/dashboards/default/components/OnlineTime.vue b/dashboard/src/views/dashboards/default/components/OnlineTime.vue index 4ba51a7d..3c869aa3 100644 --- a/dashboard/src/views/dashboards/default/components/OnlineTime.vue +++ b/dashboard/src/views/dashboards/default/components/OnlineTime.vue @@ -1,61 +1,190 @@ - - - - \ No newline at end of file + \ No newline at end of file diff --git a/dashboard/src/views/dashboards/default/components/PlatformStat.vue b/dashboard/src/views/dashboards/default/components/PlatformStat.vue index 243fd5b1..9235ff72 100644 --- a/dashboard/src/views/dashboards/default/components/PlatformStat.vue +++ b/dashboard/src/views/dashboards/default/components/PlatformStat.vue @@ -1,116 +1,253 @@ - - - \ No newline at end of file + + + \ No newline at end of file diff --git a/dashboard/src/views/dashboards/default/components/RunningTime.vue b/dashboard/src/views/dashboards/default/components/RunningTime.vue new file mode 100644 index 00000000..139eaa6a --- /dev/null +++ b/dashboard/src/views/dashboards/default/components/RunningTime.vue @@ -0,0 +1,89 @@ + + + + + diff --git a/dashboard/src/views/dashboards/default/components/TotalMessage.vue b/dashboard/src/views/dashboards/default/components/TotalMessage.vue index 8af94a75..6644a7b9 100644 --- a/dashboard/src/views/dashboards/default/components/TotalMessage.vue +++ b/dashboard/src/views/dashboards/default/components/TotalMessage.vue @@ -1,40 +1,97 @@ - - \ No newline at end of file + + + \ No newline at end of file