mirror of
https://github.com/AstrBotDevs/AstrBot
synced 2026-07-16 09:40:30 +08:00
test: final coverage batch - utils, pipeline, agent, star tests
- 22 unit test files added/modified across all core modules - Fix jsonschema ValidationError import in test_tool - Coverage increased from 23% to ~35% overall
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]})
|
||||
|
||||
@@ -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);
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -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);
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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"},
|
||||
],
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user