diff --git a/astrbot/core/agent/run_context.py b/astrbot/core/agent/run_context.py index 3c500b2d6..791653dc3 100644 --- a/astrbot/core/agent/run_context.py +++ b/astrbot/core/agent/run_context.py @@ -13,7 +13,7 @@ TContext = TypeVar("TContext", default=Any) class ContextWrapper(Generic[TContext]): """A context for running an agent, which can be used to pass additional data or state.""" - context: TContext + context: TContext | None = None messages: list[Message] = Field(default_factory=list) """This field stores the llm message context for the agent run, agent runners will maintain this field automatically.""" tool_call_timeout: int = 120 # Default tool call timeout in seconds diff --git a/astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py b/astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py index 5c21cf22b..203212286 100644 --- a/astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py +++ b/astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py @@ -13,6 +13,7 @@ from astrbot.core.agent.message import ( dump_messages_with_checkpoints, ) from astrbot.core.agent.response import AgentStats +from astrbot.core.astr_agent_run_util import AgentRunner, run_agent, run_live_agent from astrbot.core.astr_main_agent import ( MainAgentBuildConfig, MainAgentBuildResult, @@ -27,6 +28,15 @@ from astrbot.core.message.message_event_result import ( from astrbot.core.persona_error_reply import ( extract_persona_custom_error_message_from_event, ) +from astrbot.core.pipeline.context import PipelineContext, call_event_hook +from astrbot.core.pipeline.process_stage.follow_up import ( + FollowUpCapture, + finalize_follow_up_capture, + prepare_follow_up_capture, + register_active_runner, + try_capture_follow_up, + unregister_active_runner, +) from astrbot.core.pipeline.stage import Stage from astrbot.core.platform.astr_message_event import AstrMessageEvent from astrbot.core.provider.entities import ( @@ -37,17 +47,6 @@ from astrbot.core.star.star_handler import EventType from astrbot.core.utils.metrics import Metric from astrbot.core.utils.session_lock import session_lock_manager -from .....astr_agent_run_util import AgentRunner, run_agent, run_live_agent -from ....context import PipelineContext, call_event_hook -from ...follow_up import ( - FollowUpCapture, - finalize_follow_up_capture, - prepare_follow_up_capture, - register_active_runner, - try_capture_follow_up, - unregister_active_runner, -) - class InternalAgentSubStage(Stage): async def initialize(self, ctx: PipelineContext) -> None: @@ -75,6 +74,7 @@ class InternalAgentSubStage(Stage): "buffer_intermediate_messages", False, ) + self.provider_wake_prefix: str = settings.get("wake_prefix", "") self.show_reasoning = settings.get("display_reasoning_text", False) self.sanitize_context_by_modalities: bool = settings.get( "sanitize_context_by_modalities", @@ -147,9 +147,8 @@ class InternalAgentSubStage(Stage): max_quoted_fallback_images=settings.get("max_quoted_fallback_images", 20), ) - async def process( - self, event: AstrMessageEvent, provider_wake_prefix: str - ) -> AsyncGenerator[None, None]: + async def process(self, event: AstrMessageEvent) -> AsyncGenerator[None, None]: + provider_wake_prefix = self.provider_wake_prefix follow_up_capture: FollowUpCapture | None = None follow_up_consumed_marked = False follow_up_activated = False diff --git a/astrbot/core/provider/sources/anthropic_source.py b/astrbot/core/provider/sources/anthropic_source.py index 17bc544dc..ad0f0b685 100644 --- a/astrbot/core/provider/sources/anthropic_source.py +++ b/astrbot/core/provider/sources/anthropic_source.py @@ -1,10 +1,9 @@ import base64 import json from collections.abc import AsyncGenerator -from typing import Literal, TypedDict +from typing import Any, Literal import aiofiles -import anthropic import httpx from anthropic import AsyncAnthropic from anthropic.types import ( @@ -43,17 +42,6 @@ from astrbot.core.utils.network_utils import ( "Anthropic Claude API 提供商适配器", ) class ProviderAnthropic(Provider): - class _ToolUseBufferEntry(TypedDict, total=False): - id: str - name: str - input: dict[str, object] - input_json: str - - class _FinalToolCall(TypedDict): - id: str - name: str - input: dict[str, object] - @staticmethod def _ensure_usable_response( llm_response: LLMResponse, @@ -241,7 +229,7 @@ class ProviderAnthropic(Provider): ) if can_append_to_previous_tool_results: - last_content.append(tool_result_block) + last_content.append(tool_result_block) # type: ignore[union-attr] else: new_messages.append( { @@ -421,10 +409,10 @@ class ProviderAnthropic(Provider): } # 用于累积工具调用信息 - tool_use_buffer: dict[int, ProviderAnthropic._ToolUseBufferEntry] = {} + tool_use_buffer: dict[int, dict[str, Any]] = {} # 用于累积最终结果 final_text = "" - final_tool_calls: list[ProviderAnthropic._FinalToolCall] = [] + final_tool_calls: list[dict[str, Any]] = [] id = None usage = TokenUsage() extra_body = self.provider_config.get("custom_extra_body", {}) @@ -439,7 +427,6 @@ class ProviderAnthropic(Provider): **payloads, extra_body=extra_body, ) as stream: - assert isinstance(stream, anthropic.AsyncMessageStream) async for event in stream: if isinstance(event, RawMessageStartEvent): id = event.message.id diff --git a/astrbot/core/provider/sources/gemini_source.py b/astrbot/core/provider/sources/gemini_source.py index ed56e42b0..e13a0fc18 100644 --- a/astrbot/core/provider/sources/gemini_source.py +++ b/astrbot/core/provider/sources/gemini_source.py @@ -5,11 +5,12 @@ import logging import random import uuid from collections.abc import AsyncGenerator -from pathlib import Path +from pathlib import PurePath from typing import ClassVar, Literal from urllib.parse import urlparse import aiofiles +import anyio from google import genai from google.genai import types from google.genai.errors import APIError @@ -250,7 +251,7 @@ class ProviderGoogleGenAI(Provider): logprobs=payloads.get("logprobs"), seed=payloads.get("seed"), response_modalities=modalities, - tools=tool_list, + tools=tool_list, # type: ignore[arg-type] tool_config=tool_config, safety_settings=self.safety_settings or None, thinking_config=thinking_config, @@ -539,7 +540,7 @@ class ProviderGoogleGenAI(Provider): ) result = await self.client.models.generate_content( model=model, - contents=conversation, + contents=conversation, # type: ignore[arg-type] config=config, ) logger.debug(f"genai result: {result}") @@ -612,7 +613,7 @@ class ProviderGoogleGenAI(Provider): ) result = await self.client.models.generate_content_stream( model=model, - contents=conversation, + contents=conversation, # type: ignore[arg-type] config=config, ) break @@ -855,25 +856,25 @@ class ProviderGoogleGenAI(Provider): async def resolve_audio_part(audio_path: str) -> dict | None: if audio_path.startswith("http"): - suffix = Path(urlparse(audio_path).path).suffix or ".wav" - temp_dir = Path(get_astrbot_temp_path()) - temp_dir.mkdir(parents=True, exist_ok=True) + suffix = PurePath(urlparse(audio_path).path).suffix or ".wav" + temp_path = anyio.Path(get_astrbot_temp_path()) + await temp_path.mkdir(parents=True, exist_ok=True) resolved_path = str( - temp_dir / f"provider_audio_{uuid.uuid4().hex}{suffix}", + temp_path / f"provider_audio_{uuid.uuid4().hex}{suffix}", ) await download_file(audio_path, resolved_path) - elif audio_path.startswith("file:///"): + elif audio_path.startswith("file:///:"): resolved_path = audio_path.replace("file:///", "") else: resolved_path = audio_path - suffix = Path(resolved_path).suffix.lower() + suffix = PurePath(resolved_path).suffix.lower() if suffix != ".mp3": resolved_path = await ensure_wav(resolved_path) suffix = ".wav" try: - audio_bytes = Path(resolved_path).read_bytes() + audio_bytes = await anyio.Path(resolved_path).read_bytes() except OSError as exc: logger.warning( f"Failed to read audio file {resolved_path}, skipping. Error: {exc}", diff --git a/astrbot/core/provider/sources/openai_source.py b/astrbot/core/provider/sources/openai_source.py index 83e696b91..19be5e9e1 100644 --- a/astrbot/core/provider/sources/openai_source.py +++ b/astrbot/core/provider/sources/openai_source.py @@ -8,10 +8,11 @@ import re import uuid from collections.abc import AsyncGenerator from io import BytesIO -from pathlib import Path -from typing import Any, Literal +from pathlib import Path, PurePath +from typing import Any, Literal, cast from urllib.parse import unquote, urlparse +import anyio import httpx from openai import AsyncAzureOpenAI, AsyncOpenAI from openai._exceptions import NotFoundError @@ -200,7 +201,7 @@ class ProviderOpenAIOfficial(Provider): image_format = str(image.format or "").upper() except (OSError, UnidentifiedImageError): if mode == "strict": - raise ValueError(f"Invalid image file: {image_path}") + raise ValueError(f"Invalid image file: {image_path}") from None return None mime_type = { @@ -315,12 +316,12 @@ class ProviderOpenAIOfficial(Provider): async def _audio_ref_to_local_path(self, audio_ref: str) -> tuple[str, list[Path]]: cleanup_paths: list[Path] = [] if audio_ref.startswith("http"): - suffix = Path(urlparse(audio_ref).path).suffix or ".wav" - temp_dir = Path(get_astrbot_temp_path()) - temp_dir.mkdir(parents=True, exist_ok=True) - target_path = temp_dir / f"provider_audio_{uuid.uuid4().hex}{suffix}" + suffix = PurePath(urlparse(audio_ref).path).suffix or ".wav" + temp_path = anyio.Path(get_astrbot_temp_path()) + await temp_path.mkdir(parents=True, exist_ok=True) + target_path = temp_path / f"provider_audio_{uuid.uuid4().hex}{suffix}" await download_file(audio_ref, str(target_path)) - cleanup_paths.append(target_path) + cleanup_paths.append(Path(target_path)) return str(target_path), cleanup_paths if audio_ref.startswith("file://"): return self._file_uri_to_path(audio_ref), cleanup_paths @@ -330,7 +331,7 @@ class ProviderOpenAIOfficial(Provider): cleanup_paths: list[Path] = [] try: audio_path, cleanup_paths = await self._audio_ref_to_local_path(audio_ref) - suffix = Path(audio_path).suffix.lower() + suffix = PurePath(audio_path).suffix.lower() if suffix == ".mp3": audio_format = "mp3" else: @@ -339,7 +340,7 @@ class ProviderOpenAIOfficial(Provider): cleanup_paths.append(Path(converted_audio_path)) audio_path = converted_audio_path audio_format = "wav" - audio_bytes = Path(audio_path).read_bytes() + audio_bytes = await anyio.Path(audio_path).read_bytes() except Exception as exc: logger.warning("音频 %s 预处理失败,将忽略。错误: %s", audio_ref, exc) return None @@ -520,7 +521,7 @@ class ProviderOpenAIOfficial(Provider): models_str.append(model.id) return models_str except NotFoundError as e: - raise Exception(f"获取模型列表失败:{e}") + raise Exception(f"获取模型列表失败:{e}") from e @staticmethod def _sanitize_assistant_messages(payloads: dict) -> None: @@ -540,12 +541,16 @@ class ProviderOpenAIOfficial(Provider): cleaned: list[Any] = [] for idx, msg in enumerate(messages): - if not isinstance(msg, dict) or msg.get("role") != "assistant": + if not isinstance(msg, dict): + cleaned.append(msg) + continue + msg = cast(dict[str, Any], msg) + if msg.get("role") != "assistant": cleaned.append(msg) continue - content = msg.get("content") - tool_calls = msg.get("tool_calls") + content: Any = msg.get("content") + tool_calls: Any = msg.get("tool_calls") if _is_empty(content) and not tool_calls: logger.warning(f"过滤第 {idx} 条空 assistant 消息 (无工具调用)") @@ -892,16 +897,17 @@ class ProviderOpenAIOfficial(Provider): if tool_call.type == "function": # workaround for #1454 - if isinstance(tool_call.function.arguments, str): + func = tool_call.function # type: ignore[union-attr] + if isinstance(func.arguments, str): try: - args = json.loads(tool_call.function.arguments) + args = json.loads(func.arguments) except json.JSONDecodeError as e: logger.error(f"解析参数失败: {e}") args = {} else: - args = tool_call.function.arguments + args = func.arguments args_ls.append(args) - func_name_ls.append(tool_call.function.name) + func_name_ls.append(func.name) tool_call_ids.append(tool_call.id) # gemini-2.5 / gemini-3 series extra_content handling diff --git a/astrbot/core/star/session_plugin_manager.py b/astrbot/core/star/session_plugin_manager.py index adad7dfb2..ef10b6f89 100644 --- a/astrbot/core/star/session_plugin_manager.py +++ b/astrbot/core/star/session_plugin_manager.py @@ -1,6 +1,6 @@ """会话插件管理器 - 负责管理每个会话的插件启停状态""" -from typing import Any, TypedDict +from typing import Any, TypedDict, cast from astrbot.core import logger, sp from astrbot.core.platform.astr_message_event import AstrMessageEvent @@ -11,14 +11,15 @@ class SessionPluginSettings(TypedDict, total=False): disabled_plugins: list[str] -def _normalize_session_plugin_config(value: object) -> dict[str, SessionPluginSettings]: +def _normalize_session_plugin_config(value: object) -> dict[str, dict[str, list[str]]]: if not isinstance(value, dict): return {} - config: dict[str, SessionPluginSettings] = {} + config: dict[str, dict[str, list[str]]] = {} for session_id, raw_settings in value.items(): if not isinstance(session_id, str) or not isinstance(raw_settings, dict): continue - settings: SessionPluginSettings = {} + raw_settings = cast(dict[str, Any], raw_settings) + settings: dict[str, list[str]] = {} enabled_plugins = raw_settings.get("enabled_plugins") if isinstance(enabled_plugins, list) and all( isinstance(plugin_name, str) for plugin_name in enabled_plugins diff --git a/astrbot/dashboard/routes/chat.py b/astrbot/dashboard/routes/chat.py index 2659c0db9..8b5a65e5e 100644 --- a/astrbot/dashboard/routes/chat.py +++ b/astrbot/dashboard/routes/chat.py @@ -191,9 +191,10 @@ class BotMessageAccumulator: tool_call_id = str(tool_result.get("id") or "") if not tool_call_id: return - tool_call: dict[str, Any] = self.pending_tool_calls.pop(tool_call_id, None) or { - "id": tool_call_id - } + existing = self.pending_tool_calls.pop(tool_call_id, None) + tool_call: dict[str, Any] = ( + existing if existing is not None else {"id": tool_call_id} + ) tool_call["result"] = tool_result.get("result") tool_call["finished_ts"] = tool_result.get("ts") self.parts.append({"type": "tool_call", "tool_calls": [tool_call]}) diff --git a/dashboard/src/assets/mdi-subset/materialdesignicons-subset.css b/dashboard/src/assets/mdi-subset/materialdesignicons-subset.css index 0c2434206..9448f6130 100644 --- a/dashboard/src/assets/mdi-subset/materialdesignicons-subset.css +++ b/dashboard/src/assets/mdi-subset/materialdesignicons-subset.css @@ -1,10 +1,11 @@ -/* Auto-generated MDI subset – 267 icons */ +/* Auto-generated MDI subset – 268 icons */ /* Do not edit manually. Run: pnpm run subset-icons */ @font-face { font-family: "Material Design Icons"; - src: url("./materialdesignicons-webfont-subset.woff2") format("woff2"), - url("./materialdesignicons-webfont-subset.woff") format("woff"); + src: + url("./materialdesignicons-webfont-subset.woff2") format("woff2"), + url("./materialdesignicons-webfont-subset.woff") format("woff"); font-weight: normal; font-style: normal; } @@ -748,6 +749,10 @@ content: "\F0B3C"; } +.mdi-numeric-4::before { + content: "\F0B3D"; +} + .mdi-open-in-new::before { content: "\F03CC"; } @@ -1096,19 +1101,23 @@ visibility: hidden; } -.mdi-18px.mdi-set, .mdi-18px.mdi:before { +.mdi-18px.mdi-set, +.mdi-18px.mdi:before { font-size: 18px; } -.mdi-24px.mdi-set, .mdi-24px.mdi:before { +.mdi-24px.mdi-set, +.mdi-24px.mdi:before { font-size: 24px; } -.mdi-36px.mdi-set, .mdi-36px.mdi:before { +.mdi-36px.mdi-set, +.mdi-36px.mdi:before { font-size: 36px; } -.mdi-48px.mdi-set, .mdi-48px.mdi:before { +.mdi-48px.mdi-set, +.mdi-48px.mdi:before { font-size: 48px; } @@ -1127,176 +1136,177 @@ .mdi-light.mdi-inactive:before { color: rgba(255, 255, 255, 0.3); } - +/* .mdi-rotate-45 { - /* - // Not included in production - &.mdi-flip-h:before { - -webkit-transform: scaleX(-1) rotate(45deg); - transform: scaleX(-1) rotate(45deg); - filter: FlipH; - -ms-filter: "FlipH"; - } - &.mdi-flip-v:before { - -webkit-transform: scaleY(-1) rotate(45deg); - -ms-transform: rotate(45deg); - transform: scaleY(-1) rotate(45deg); - filter: FlipV; - -ms-filter: "FlipV"; - } - */ -} + // Not included in production + &.mdi-flip-h:before { + -webkit-transform: scaleX(-1) rotate(45deg); + transform: scaleX(-1) rotate(45deg); + filter: FlipH; + -ms-filter: "FlipH"; + } + &.mdi-flip-v:before { + -webkit-transform: scaleY(-1) rotate(45deg); + -ms-transform: rotate(45deg); + transform: scaleY(-1) rotate(45deg); + filter: FlipV; + -ms-filter: "FlipV"; + } + +} +*/ .mdi-rotate-45:before { -webkit-transform: rotate(45deg); -ms-transform: rotate(45deg); transform: rotate(45deg); } - +/* .mdi-rotate-90 { - /* - // Not included in production - &.mdi-flip-h:before { - -webkit-transform: scaleX(-1) rotate(90deg); - transform: scaleX(-1) rotate(90deg); - filter: FlipH; - -ms-filter: "FlipH"; - } - &.mdi-flip-v:before { - -webkit-transform: scaleY(-1) rotate(90deg); - -ms-transform: rotate(90deg); - transform: scaleY(-1) rotate(90deg); - filter: FlipV; - -ms-filter: "FlipV"; - } - */ -} + // Not included in production + &.mdi-flip-h:before { + -webkit-transform: scaleX(-1) rotate(90deg); + transform: scaleX(-1) rotate(90deg); + filter: FlipH; + -ms-filter: "FlipH"; + } + &.mdi-flip-v:before { + -webkit-transform: scaleY(-1) rotate(90deg); + -ms-transform: rotate(90deg); + transform: scaleY(-1) rotate(90deg); + filter: FlipV; + -ms-filter: "FlipV"; + } + +} +*/ .mdi-rotate-90:before { -webkit-transform: rotate(90deg); -ms-transform: rotate(90deg); transform: rotate(90deg); } - +/* .mdi-rotate-135 { - /* - // Not included in production - &.mdi-flip-h:before { - -webkit-transform: scaleX(-1) rotate(135deg); - transform: scaleX(-1) rotate(135deg); - filter: FlipH; - -ms-filter: "FlipH"; - } - &.mdi-flip-v:before { - -webkit-transform: scaleY(-1) rotate(135deg); - -ms-transform: rotate(135deg); - transform: scaleY(-1) rotate(135deg); - filter: FlipV; - -ms-filter: "FlipV"; - } - */ -} + // Not included in production + &.mdi-flip-h:before { + -webkit-transform: scaleX(-1) rotate(135deg); + transform: scaleX(-1) rotate(135deg); + filter: FlipH; + -ms-filter: "FlipH"; + } + &.mdi-flip-v:before { + -webkit-transform: scaleY(-1) rotate(135deg); + -ms-transform: rotate(135deg); + transform: scaleY(-1) rotate(135deg); + filter: FlipV; + -ms-filter: "FlipV"; + } + +} +*/ .mdi-rotate-135:before { -webkit-transform: rotate(135deg); -ms-transform: rotate(135deg); transform: rotate(135deg); } +/* .mdi-rotate-180 { - /* - // Not included in production - &.mdi-flip-h:before { - -webkit-transform: scaleX(-1) rotate(180deg); - transform: scaleX(-1) rotate(180deg); - filter: FlipH; - -ms-filter: "FlipH"; - } - &.mdi-flip-v:before { - -webkit-transform: scaleY(-1) rotate(180deg); - -ms-transform: rotate(180deg); - transform: scaleY(-1) rotate(180deg); - filter: FlipV; - -ms-filter: "FlipV"; - } - */ -} + // Not included in production + &.mdi-flip-h:before { + -webkit-transform: scaleX(-1) rotate(180deg); + transform: scaleX(-1) rotate(180deg); + filter: FlipH; + -ms-filter: "FlipH"; + } + &.mdi-flip-v:before { + -webkit-transform: scaleY(-1) rotate(180deg); + -ms-transform: rotate(180deg); + transform: scaleY(-1) rotate(180deg); + filter: FlipV; + -ms-filter: "FlipV"; + } + +} +*/ .mdi-rotate-180:before { -webkit-transform: rotate(180deg); -ms-transform: rotate(180deg); transform: rotate(180deg); } - +/* .mdi-rotate-225 { - /* - // Not included in production - &.mdi-flip-h:before { - -webkit-transform: scaleX(-1) rotate(225deg); - transform: scaleX(-1) rotate(225deg); - filter: FlipH; - -ms-filter: "FlipH"; - } - &.mdi-flip-v:before { - -webkit-transform: scaleY(-1) rotate(225deg); - -ms-transform: rotate(225deg); - transform: scaleY(-1) rotate(225deg); - filter: FlipV; - -ms-filter: "FlipV"; - } - */ -} + // Not included in production + &.mdi-flip-h:before { + -webkit-transform: scaleX(-1) rotate(225deg); + transform: scaleX(-1) rotate(225deg); + filter: FlipH; + -ms-filter: "FlipH"; + } + &.mdi-flip-v:before { + -webkit-transform: scaleY(-1) rotate(225deg); + -ms-transform: rotate(225deg); + transform: scaleY(-1) rotate(225deg); + filter: FlipV; + -ms-filter: "FlipV"; + } + +} +*/ .mdi-rotate-225:before { -webkit-transform: rotate(225deg); -ms-transform: rotate(225deg); transform: rotate(225deg); } - +/* .mdi-rotate-270 { - /* - // Not included in production - &.mdi-flip-h:before { - -webkit-transform: scaleX(-1) rotate(270deg); - transform: scaleX(-1) rotate(270deg); - filter: FlipH; - -ms-filter: "FlipH"; - } - &.mdi-flip-v:before { - -webkit-transform: scaleY(-1) rotate(270deg); - -ms-transform: rotate(270deg); - transform: scaleY(-1) rotate(270deg); - filter: FlipV; - -ms-filter: "FlipV"; - } - */ -} + // Not included in production + &.mdi-flip-h:before { + -webkit-transform: scaleX(-1) rotate(270deg); + transform: scaleX(-1) rotate(270deg); + filter: FlipH; + -ms-filter: "FlipH"; + } + &.mdi-flip-v:before { + -webkit-transform: scaleY(-1) rotate(270deg); + -ms-transform: rotate(270deg); + transform: scaleY(-1) rotate(270deg); + filter: FlipV; + -ms-filter: "FlipV"; + } + +} +*/ .mdi-rotate-270:before { -webkit-transform: rotate(270deg); -ms-transform: rotate(270deg); transform: rotate(270deg); } - +/* .mdi-rotate-315 { - /* - // Not included in production - &.mdi-flip-h:before { - -webkit-transform: scaleX(-1) rotate(315deg); - transform: scaleX(-1) rotate(315deg); - filter: FlipH; - -ms-filter: "FlipH"; - } - &.mdi-flip-v:before { - -webkit-transform: scaleY(-1) rotate(315deg); - -ms-transform: rotate(315deg); - transform: scaleY(-1) rotate(315deg); - filter: FlipV; - -ms-filter: "FlipV"; - } - */ -} + // Not included in production + &.mdi-flip-h:before { + -webkit-transform: scaleX(-1) rotate(315deg); + transform: scaleX(-1) rotate(315deg); + filter: FlipH; + -ms-filter: "FlipH"; + } + &.mdi-flip-v:before { + -webkit-transform: scaleY(-1) rotate(315deg); + -ms-transform: rotate(315deg); + transform: scaleY(-1) rotate(315deg); + filter: FlipV; + -ms-filter: "FlipV"; + } + +} +*/ .mdi-rotate-315:before { -webkit-transform: rotate(315deg); -ms-transform: rotate(315deg); diff --git a/dashboard/src/assets/mdi-subset/materialdesignicons-webfont-subset.woff b/dashboard/src/assets/mdi-subset/materialdesignicons-webfont-subset.woff index 0cc10cbc6..03059b126 100644 Binary files a/dashboard/src/assets/mdi-subset/materialdesignicons-webfont-subset.woff and b/dashboard/src/assets/mdi-subset/materialdesignicons-webfont-subset.woff differ diff --git a/dashboard/src/assets/mdi-subset/materialdesignicons-webfont-subset.woff2 b/dashboard/src/assets/mdi-subset/materialdesignicons-webfont-subset.woff2 index d10ef4119..a47b3649c 100644 Binary files a/dashboard/src/assets/mdi-subset/materialdesignicons-webfont-subset.woff2 and b/dashboard/src/assets/mdi-subset/materialdesignicons-webfont-subset.woff2 differ diff --git a/dashboard/src/components/chat/MessageList.vue b/dashboard/src/components/chat/MessageList.vue index 39d0d6b24..420de5fb5 100644 --- a/dashboard/src/components/chat/MessageList.vue +++ b/dashboard/src/components/chat/MessageList.vue @@ -381,7 +381,6 @@ import axios from "@/utils/request"; import { useToast } from "@/utils/toast"; import ReasoningBlock from "./message_list_comps/ReasoningBlock.vue"; import MessagePartsRenderer from "./message_list_comps/MessagePartsRenderer.vue"; -import RefNode from "./message_list_comps/RefNode.vue"; import ActionRef from "./message_list_comps/ActionRef.vue"; enableKatex(); @@ -1060,7 +1059,7 @@ export default defineComponent({ // Start timer for updating elapsed time startElapsedTimeTimer(): void { - let fastUpdateCount = 0; + let _fastUpdateCount = 0; const fastUpdateInterval = 12; const slowUpdateInterval = 1000; @@ -1092,7 +1091,7 @@ export default defineComponent({ ); if (hasSubSecondToolCall) { - fastUpdateCount++; + _fastUpdateCount++; this.elapsedTimeTimer = setTimeout(updateTime, fastUpdateInterval); } else { this.elapsedTimeTimer = setTimeout(updateTime, slowUpdateInterval); diff --git a/pyproject.toml b/pyproject.toml index 110165af1..90611d91f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -141,7 +141,16 @@ pythonVersion = "3.12" reportMissingTypeStubs = false reportMissingImports = false include = ["astrbot"] -exclude = ["dashboard", "node_modules", "dist", "data", "tests", "tests/**", "**/tests/**"] +exclude = [ + "dashboard", + "node_modules", + "dist", + "data", + "tests", + "tests/**", + "**/tests/**", + "**/__pycache__", +] [tool.mypy] plugins = ["pydantic.mypy"] diff --git a/tests/test_openai_source.py b/tests/test_openai_source.py index 428ef1566..c2ef8975b 100644 --- a/tests/test_openai_source.py +++ b/tests/test_openai_source.py @@ -284,7 +284,7 @@ async def test_openai_payload_handles_none_think_content(): { "role": "assistant", "content": [ - {"type": "think", "think": None}, # type: ignore + {"type": "think", "think": None}, {"type": "text", "text": "final answer"}, ], }