pull/1429/head
SengokuCola 2025-12-12 23:50:59 +08:00
commit 08ce18e0f4
31 changed files with 314 additions and 294 deletions

View File

@ -2,8 +2,7 @@ name: Docker Build and Push (Dev)
on:
schedule:
- cron: '0 0 * * *'
# push:
- cron: '0 0 * * *' # every day at midnight UTC
# branches:
# - dev
workflow_dispatch: # 允许手动触发工作流
@ -28,11 +27,11 @@ jobs:
fetch-depth: 0
# Clone required dependencies
- name: Clone maim_message
run: git clone https://github.com/MaiM-with-u/maim_message maim_message
# - name: Clone maim_message
# run: git clone https://github.com/MaiM-with-u/maim_message maim_message
- name: Clone lpmm
run: git clone https://github.com/MaiM-with-u/MaiMBot-LPMM.git MaiMBot-LPMM
run: git clone https://github.com/Mai-with-u/MaiMBot-LPMM.git MaiMBot-LPMM
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
@ -82,11 +81,11 @@ jobs:
fetch-depth: 0
# Clone required dependencies
- name: Clone maim_message
run: git clone https://github.com/MaiM-with-u/maim_message maim_message
# - name: Clone maim_message
# run: git clone https://github.com/MaiM-with-u/maim_message maim_message
- name: Clone lpmm
run: git clone https://github.com/MaiM-with-u/MaiMBot-LPMM.git MaiMBot-LPMM
run: git clone https://github.com/Mai-with-u/MaiMBot-LPMM.git MaiMBot-LPMM
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
@ -147,8 +146,8 @@ jobs:
with:
images: ${{ secrets.DOCKERHUB_USERNAME }}/maibot
tags: |
type=ref,event=branch
type=sha,prefix=${{ github.ref_name }}-,enable=${{ github.ref_type == 'branch' }}
type=raw,value=dev
type=schedule,pattern=dev-{{date 'YYMMDD'}}
- name: Create and Push Manifest
run: |

View File

@ -31,11 +31,11 @@ jobs:
fetch-depth: 0
# Clone required dependencies
- name: Clone maim_message
run: git clone https://github.com/MaiM-with-u/maim_message maim_message
# - name: Clone maim_message
# run: git clone https://github.com/MaiM-with-u/maim_message maim_message
- name: Clone lpmm
run: git clone https://github.com/MaiM-with-u/MaiMBot-LPMM.git MaiMBot-LPMM
run: git clone https://github.com/Mai-with-u/MaiMBot-LPMM.git MaiMBot-LPMM
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
@ -84,11 +84,11 @@ jobs:
fetch-depth: 0
# Clone required dependencies
- name: Clone maim_message
run: git clone https://github.com/MaiM-with-u/maim_message maim_message
# - name: Clone maim_message
# run: git clone https://github.com/MaiM-with-u/maim_message maim_message
- name: Clone lpmm
run: git clone https://github.com/MaiM-with-u/MaiMBot-LPMM.git MaiMBot-LPMM
run: git clone https://github.com/Mai-with-u/MaiMBot-LPMM.git MaiMBot-LPMM
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

View File

@ -271,7 +271,7 @@ def _to_emoji_objects(data: Any) -> Tuple[List["MaiEmoji"], int]:
emoji.description = emoji_data.description
# Deserialize emotion string from DB to list
emoji.emotion = emoji_data.emotion.split(",") if emoji_data.emotion else []
emoji.emotion = emoji_data.emotion.replace("",",").split(",") if emoji_data.emotion else []
emoji.usage_count = emoji_data.usage_count
db_last_used_time = emoji_data.last_used_time
@ -732,7 +732,7 @@ class EmojiManager:
emoji_record = Emoji.get_or_none(Emoji.emoji_hash == emoji_hash)
if emoji_record and emoji_record.emotion:
logger.info(f"[缓存命中] 从数据库获取表情包情感标签: {emoji_record.emotion[:50]}...")
return emoji_record.emotion.split(",")
return emoji_record.emotion.replace("",",").split(",")
except Exception as e:
logger.error(f"从数据库查询表情包情感标签时出错: {e}")
@ -993,7 +993,7 @@ class EmojiManager:
)
# 处理情感列表
emotions = [e.strip() for e in emotions_text.split(",") if e.strip()]
emotions = [e.strip() for e in emotions_text.replace("",",").split(",") if e.strip()]
# 根据情感标签数量随机选择 - 超过5个选3个超过2个选2个
if len(emotions) > 5:

View File

@ -40,6 +40,90 @@ def is_webui_virtual_group(group_id: str) -> bool:
return group_id and group_id.startswith(VIRTUAL_GROUP_ID_PREFIX)
def parse_message_segments(segment) -> list:
"""解析消息段,转换为 WebUI 可用的格式
参考 NapCat 适配器的消息解析逻辑
Args:
segment: Seg 消息段对象
Returns:
list: 消息段列表每个元素为 {"type": "...", "data": ...}
"""
from maim_message import Seg
result = []
if segment is None:
return result
if segment.type == "seglist":
# 处理消息段列表
if segment.data:
for seg in segment.data:
result.extend(parse_message_segments(seg))
elif segment.type == "text":
# 文本消息
if segment.data:
result.append({"type": "text", "data": segment.data})
elif segment.type == "image":
# 图片消息base64
if segment.data:
result.append({"type": "image", "data": f"data:image/png;base64,{segment.data}"})
elif segment.type == "emoji":
# 表情包消息base64
if segment.data:
result.append({"type": "emoji", "data": f"data:image/gif;base64,{segment.data}"})
elif segment.type == "imageurl":
# 图片链接消息
if segment.data:
result.append({"type": "image", "data": segment.data})
elif segment.type == "face":
# 原生表情
result.append({"type": "face", "data": segment.data})
elif segment.type == "voice":
# 语音消息base64
if segment.data:
result.append({"type": "voice", "data": f"data:audio/wav;base64,{segment.data}"})
elif segment.type == "voiceurl":
# 语音链接
if segment.data:
result.append({"type": "voice", "data": segment.data})
elif segment.type == "video":
# 视频消息base64
if segment.data:
result.append({"type": "video", "data": f"data:video/mp4;base64,{segment.data}"})
elif segment.type == "videourl":
# 视频链接
if segment.data:
result.append({"type": "video", "data": segment.data})
elif segment.type == "music":
# 音乐消息
result.append({"type": "music", "data": segment.data})
elif segment.type == "file":
# 文件消息
result.append({"type": "file", "data": segment.data})
elif segment.type == "reply":
# 回复消息
result.append({"type": "reply", "data": segment.data})
elif segment.type == "forward":
# 转发消息
forward_items = []
if segment.data:
for item in segment.data:
forward_items.append({
"content": parse_message_segments(item.get("message_segment", {})) if isinstance(item, dict) else []
})
result.append({"type": "forward", "data": forward_items})
else:
# 未知类型,尝试作为文本处理
if segment.data:
result.append({"type": "unknown", "original_type": segment.type, "data": str(segment.data)})
return result
async def _send_message(message: MessageSending, show_log=True) -> bool:
"""合并后的消息发送函数包含WS发送和日志记录"""
message_preview = truncate_message(message.processed_plain_text, max_length=200)
@ -56,11 +140,25 @@ async def _send_message(message: MessageSending, show_log=True) -> bool:
import time
from src.config.config import global_config
# 解析消息段,获取富文本内容
message_segments = parse_message_segments(message.message_segment)
# 判断消息类型
# 如果只有一个文本段,使用简单的 text 类型
# 否则使用 rich 类型,包含完整的消息段
if len(message_segments) == 1 and message_segments[0].get("type") == "text":
message_type = "text"
segments = None
else:
message_type = "rich"
segments = message_segments
await chat_manager.broadcast(
{
"type": "bot_message",
"content": message.processed_plain_text,
"message_type": "text",
"message_type": message_type,
"segments": segments, # 富文本消息段
"timestamp": time.time(),
"group_id": group_id, # 包含群 ID 以便前端区分不同的聊天标签
"sender": {

View File

@ -143,10 +143,14 @@ def _convert_tool_options(tool_options: list[ToolOption]) -> list[FunctionDeclar
:param tool_option_param: 工具参数对象
:return: 转换后的工具参数字典
"""
# JSON Schema要求使用"boolean"而不是"bool"
# JSON Schema 类型名称修正:
# - 布尔类型使用 "boolean" 而不是 "bool"
# - 浮点数使用 "number" 而不是 "float"
param_type_value = tool_option_param.param_type.value
if param_type_value == "bool":
param_type_value = "boolean"
elif param_type_value == "float":
param_type_value = "number"
return_dict: dict[str, Any] = {
"type": param_type_value,

View File

@ -118,10 +118,14 @@ def _convert_tool_options(tool_options: list[ToolOption]) -> list[dict[str, Any]
:param tool_option_param: 工具参数对象
:return: 转换后的工具参数字典
"""
# JSON Schema要求使用"boolean"而不是"bool"
# JSON Schema 类型名称修正:
# - 布尔类型使用 "boolean" 而不是 "bool"
# - 浮点数使用 "number" 而不是 "float"
param_type_value = tool_option_param.param_type.value
if param_type_value == "bool":
param_type_value = "boolean"
elif param_type_value == "float":
param_type_value = "number"
return_dict: dict[str, Any] = {
"type": param_type_value,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
import{r as c,R as P,b as Oe}from"./router-CWhjJi2n.js";function Rn(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];return c.useMemo(()=>r=>{t.forEach(o=>o(r))},t)}const et=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u";function me(e){const t=Object.prototype.toString.call(e);return t==="[object Window]"||t==="[object global]"}function pt(e){return"nodeType"in e}function B(e){var t,n;return e?me(e)?e:pt(e)&&(t=(n=e.ownerDocument)==null?void 0:n.defaultView)!=null?t:window:window}function bt(e){const{Document:t}=B(e);return e instanceof t}function Be(e){return me(e)?!1:e instanceof B(e).HTMLElement}function Ut(e){return e instanceof B(e).SVGElement}function ye(e){return e?me(e)?e.document:pt(e)?bt(e)?e:Be(e)||Ut(e)?e.ownerDocument:document:document:document}const Q=et?c.useLayoutEffect:c.useEffect;function wt(e){const t=c.useRef(e);return Q(()=>{t.current=e}),c.useCallback(function(){for(var n=arguments.length,r=new Array(n),o=0;o<n;o++)r[o]=arguments[o];return t.current==null?void 0:t.current(...r)},[])}function Sn(){const e=c.useRef(null),t=c.useCallback((r,o)=>{e.current=setInterval(r,o)},[]),n=c.useCallback(()=>{e.current!==null&&(clearInterval(e.current),e.current=null)},[]);return[t,n]}function ke(e,t){t===void 0&&(t=[e]);const n=c.useRef(e);return Q(()=>{n.current!==e&&(n.current=e)},t),n}function Fe(e,t){const n=c.useRef();return c.useMemo(()=>{const r=e(n.current);return n.current=r,r},[...t])}function Ge(e){const t=wt(e),n=c.useRef(null),r=c.useCallback(o=>{o!==n.current&&t?.(o,n.current),n.current=o},[]);return[n,r]}function dt(e){const t=c.useRef();return c.useEffect(()=>{t.current=e},[e]),t.current}let at={};function $e(e,t){return c.useMemo(()=>{if(t)return t;const n=at[e]==null?0:at[e]+1;return at[e]=n,e+"-"+n},[e,t])}function Wt(e){return function(t){for(var n=arguments.length,r=new Array(n>1?n-1:0),o=1;o<n;o++)r[o-1]=arguments[o];return r.reduce((i,s)=>{const a=Object.entries(s);for(const[l,u]of a){const f=i[l];f!=null&&(i[l]=f+e*u)}return i},{...t})}}const we=Wt(1),ze=Wt(-1);function En(e){return"clientX"in e&&"clientY"in e}function mt(e){if(!e)return!1;const{KeyboardEvent:t}=B(e.target);return t&&e instanceof t}function Mn(e){if(!e)return!1;const{TouchEvent:t}=B(e.target);return t&&e instanceof t}function ft(e){if(Mn(e)){if(e.touches&&e.touches.length){const{clientX:t,clientY:n}=e.touches[0];return{x:t,y:n}}else if(e.changedTouches&&e.changedTouches.length){const{clientX:t,clientY:n}=e.changedTouches[0];return{x:t,y:n}}}return En(e)?{x:e.clientX,y:e.clientY}:null}const Je=Object.freeze({Translate:{toString(e){if(!e)return;const{x:t,y:n}=e;return"translate3d("+(t?Math.round(t):0)+"px, "+(n?Math.round(n):0)+"px, 0)"}},Scale:{toString(e){if(!e)return;const{scaleX:t,scaleY:n}=e;return"scaleX("+t+") scaleY("+n+")"}},Transform:{toString(e){if(e)return[Je.Translate.toString(e),Je.Scale.toString(e)].join(" ")}},Transition:{toString(e){let{property:t,duration:n,easing:r}=e;return t+" "+n+"ms "+r}}}),Ot="a,frame,iframe,input:not([type=hidden]):not(:disabled),select:not(:disabled),textarea:not(:disabled),button:not(:disabled),*[tabindex]";function In(e){return e.matches(Ot)?e:e.querySelector(Ot)}const An={display:"none"};function On(e){let{id:t,value:n}=e;return P.createElement("div",{id:t,style:An},n)}function Tn(e){let{id:t,announcement:n,ariaLiveType:r="assertive"}=e;const o={position:"fixed",top:0,left:0,width:1,height:1,margin:-1,border:0,padding:0,overflow:"hidden",clip:"rect(0 0 0 0)",clipPath:"inset(100%)",whiteSpace:"nowrap"};return P.createElement("div",{id:t,style:o,role:"status","aria-live":r,"aria-atomic":!0},n)}function Nn(){const[e,t]=c.useState("");return{announce:c.useCallback(r=>{r!=null&&t(r)},[]),announcement:e}}const Ht=c.createContext(null);function Ln(e){const t=c.useContext(Ht);c.useEffect(()=>{if(!t)throw new Error("useDndMonitor must be used within a children of <DndContext>");return t(e)},[e,t])}function kn(){const[e]=c.useState(()=>new Set),t=c.useCallback(r=>(e.add(r),()=>e.delete(r)),[e]);return[c.useCallback(r=>{let{type:o,event:i}=r;e.forEach(s=>{var a;return(a=s[o])==null?void 0:a.call(s,i)})},[e]),t]}const zn={draggable:`
import{r as c,R as P,b as Oe}from"./router-Bz250laD.js";function Rn(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];return c.useMemo(()=>r=>{t.forEach(o=>o(r))},t)}const et=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u";function me(e){const t=Object.prototype.toString.call(e);return t==="[object Window]"||t==="[object global]"}function pt(e){return"nodeType"in e}function B(e){var t,n;return e?me(e)?e:pt(e)&&(t=(n=e.ownerDocument)==null?void 0:n.defaultView)!=null?t:window:window}function bt(e){const{Document:t}=B(e);return e instanceof t}function Be(e){return me(e)?!1:e instanceof B(e).HTMLElement}function Ut(e){return e instanceof B(e).SVGElement}function ye(e){return e?me(e)?e.document:pt(e)?bt(e)?e:Be(e)||Ut(e)?e.ownerDocument:document:document:document}const Q=et?c.useLayoutEffect:c.useEffect;function wt(e){const t=c.useRef(e);return Q(()=>{t.current=e}),c.useCallback(function(){for(var n=arguments.length,r=new Array(n),o=0;o<n;o++)r[o]=arguments[o];return t.current==null?void 0:t.current(...r)},[])}function Sn(){const e=c.useRef(null),t=c.useCallback((r,o)=>{e.current=setInterval(r,o)},[]),n=c.useCallback(()=>{e.current!==null&&(clearInterval(e.current),e.current=null)},[]);return[t,n]}function ke(e,t){t===void 0&&(t=[e]);const n=c.useRef(e);return Q(()=>{n.current!==e&&(n.current=e)},t),n}function Fe(e,t){const n=c.useRef();return c.useMemo(()=>{const r=e(n.current);return n.current=r,r},[...t])}function Ge(e){const t=wt(e),n=c.useRef(null),r=c.useCallback(o=>{o!==n.current&&t?.(o,n.current),n.current=o},[]);return[n,r]}function dt(e){const t=c.useRef();return c.useEffect(()=>{t.current=e},[e]),t.current}let at={};function $e(e,t){return c.useMemo(()=>{if(t)return t;const n=at[e]==null?0:at[e]+1;return at[e]=n,e+"-"+n},[e,t])}function Wt(e){return function(t){for(var n=arguments.length,r=new Array(n>1?n-1:0),o=1;o<n;o++)r[o-1]=arguments[o];return r.reduce((i,s)=>{const a=Object.entries(s);for(const[l,u]of a){const f=i[l];f!=null&&(i[l]=f+e*u)}return i},{...t})}}const we=Wt(1),ze=Wt(-1);function En(e){return"clientX"in e&&"clientY"in e}function mt(e){if(!e)return!1;const{KeyboardEvent:t}=B(e.target);return t&&e instanceof t}function Mn(e){if(!e)return!1;const{TouchEvent:t}=B(e.target);return t&&e instanceof t}function ft(e){if(Mn(e)){if(e.touches&&e.touches.length){const{clientX:t,clientY:n}=e.touches[0];return{x:t,y:n}}else if(e.changedTouches&&e.changedTouches.length){const{clientX:t,clientY:n}=e.changedTouches[0];return{x:t,y:n}}}return En(e)?{x:e.clientX,y:e.clientY}:null}const Je=Object.freeze({Translate:{toString(e){if(!e)return;const{x:t,y:n}=e;return"translate3d("+(t?Math.round(t):0)+"px, "+(n?Math.round(n):0)+"px, 0)"}},Scale:{toString(e){if(!e)return;const{scaleX:t,scaleY:n}=e;return"scaleX("+t+") scaleY("+n+")"}},Transform:{toString(e){if(e)return[Je.Translate.toString(e),Je.Scale.toString(e)].join(" ")}},Transition:{toString(e){let{property:t,duration:n,easing:r}=e;return t+" "+n+"ms "+r}}}),Ot="a,frame,iframe,input:not([type=hidden]):not(:disabled),select:not(:disabled),textarea:not(:disabled),button:not(:disabled),*[tabindex]";function In(e){return e.matches(Ot)?e:e.querySelector(Ot)}const An={display:"none"};function On(e){let{id:t,value:n}=e;return P.createElement("div",{id:t,style:An},n)}function Tn(e){let{id:t,announcement:n,ariaLiveType:r="assertive"}=e;const o={position:"fixed",top:0,left:0,width:1,height:1,margin:-1,border:0,padding:0,overflow:"hidden",clip:"rect(0 0 0 0)",clipPath:"inset(100%)",whiteSpace:"nowrap"};return P.createElement("div",{id:t,style:o,role:"status","aria-live":r,"aria-atomic":!0},n)}function Nn(){const[e,t]=c.useState("");return{announce:c.useCallback(r=>{r!=null&&t(r)},[]),announcement:e}}const Ht=c.createContext(null);function Ln(e){const t=c.useContext(Ht);c.useEffect(()=>{if(!t)throw new Error("useDndMonitor must be used within a children of <DndContext>");return t(e)},[e,t])}function kn(){const[e]=c.useState(()=>new Set),t=c.useCallback(r=>(e.add(r),()=>e.delete(r)),[e]);return[c.useCallback(r=>{let{type:o,event:i}=r;e.forEach(s=>{var a;return(a=s[o])==null?void 0:a.call(s,i)})},[e]),t]}const zn={draggable:`
To pick up a draggable item, press the space bar.
While dragging, use the arrow keys to move the item.
Press space again to drop the item in its new position, or press escape to cancel.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

30
webui/dist/index.html vendored
View File

@ -7,21 +7,21 @@
<link rel="icon" type="image/x-icon" href="/maimai.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>MaiBot Dashboard</title>
<script type="module" crossorigin src="/assets/index-DHIER1DT.js"></script>
<link rel="modulepreload" crossorigin href="/assets/react-vendor-Dtc2IqVY.js">
<link rel="modulepreload" crossorigin href="/assets/router-CWhjJi2n.js">
<link rel="modulepreload" crossorigin href="/assets/utils-CCeOswSm.js">
<link rel="modulepreload" crossorigin href="/assets/radix-core-C3XKqQJw.js">
<link rel="modulepreload" crossorigin href="/assets/radix-extra-DnIxMvW0.js">
<link rel="modulepreload" crossorigin href="/assets/charts-Dhri-zxi.js">
<link rel="modulepreload" crossorigin href="/assets/icons-BusT0Ku_.js">
<link rel="modulepreload" crossorigin href="/assets/codemirror-BHeANvwm.js">
<link rel="modulepreload" crossorigin href="/assets/misc-DyBU7ISD.js">
<link rel="modulepreload" crossorigin href="/assets/dnd-Dyi3CnuX.js">
<link rel="modulepreload" crossorigin href="/assets/uppy-DUr9_tfX.js">
<link rel="modulepreload" crossorigin href="/assets/markdown-A1ShuLvG.js">
<link rel="modulepreload" crossorigin href="/assets/reactflow-B3n3_Vkw.js">
<link rel="stylesheet" crossorigin href="/assets/index-DrsALSzA.css">
<script type="module" crossorigin src="/assets/index-CgxOYrz-.js"></script>
<link rel="modulepreload" crossorigin href="/assets/react-vendor-BmxF9s7Q.js">
<link rel="modulepreload" crossorigin href="/assets/router-Bz250laD.js">
<link rel="modulepreload" crossorigin href="/assets/utils-BXc2jIuz.js">
<link rel="modulepreload" crossorigin href="/assets/radix-core-9dEfQl-6.js">
<link rel="modulepreload" crossorigin href="/assets/radix-extra-DDK-u9dm.js">
<link rel="modulepreload" crossorigin href="/assets/charts-DbiuC1q1.js">
<link rel="modulepreload" crossorigin href="/assets/icons-CwAZotQh.js">
<link rel="modulepreload" crossorigin href="/assets/codemirror-BEE0n9kQ.js">
<link rel="modulepreload" crossorigin href="/assets/misc-CKjrIrIJ.js">
<link rel="modulepreload" crossorigin href="/assets/dnd-CHfCzWUK.js">
<link rel="modulepreload" crossorigin href="/assets/reactflow-DLoXAt4c.js">
<link rel="modulepreload" crossorigin href="/assets/uppy-BMZiFQyG.js">
<link rel="modulepreload" crossorigin href="/assets/markdown-kUhwkcQP.js">
<link rel="stylesheet" crossorigin href="/assets/index-CWjV9Ftw.css">
</head>
<body>
<div id="root" class="notranslate"></div>