feat: 添加日历批量操作与客户端时区感知功能,优化前端 UI 交互体验

This commit is contained in:
zl-q
2026-03-17 00:13:41 +08:00
parent d3783522e6
commit c26cdbbc27
27 changed files with 1532 additions and 412 deletions
+6
View File
@@ -1,3 +1,7 @@
from schemas.agent.forwarded_props import (
ClientTimeContext,
parse_forwarded_props_client_time,
)
from schemas.agent.runtime_models import (
ResultType,
RouterAgentOutput,
@@ -22,6 +26,7 @@ from schemas.agent.ui_hints import (
__all__ = [
"AgentType",
"ClientTimeContext",
"ResultType",
"RouterAgentOutput",
"RouterUiDecision",
@@ -39,4 +44,5 @@ __all__ = [
"WorkerAgentOutputRich",
"WorkerAgentOutput",
"resolve_worker_output_model",
"parse_forwarded_props_client_time",
]
@@ -0,0 +1,78 @@
from __future__ import annotations
from datetime import datetime
import re
from typing import Any
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
from pydantic import (
BaseModel,
ConfigDict,
Field,
StrictInt,
ValidationError,
field_validator,
)
_RFC3339_WITH_TZ_PATTERN = re.compile(
r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$"
)
class ClientTimeContext(BaseModel):
model_config = ConfigDict(extra="forbid")
device_timezone: str = Field(
...,
description="IANA timezone from client device, e.g. America/Los_Angeles.",
)
client_now_iso: str = Field(
...,
description="RFC3339 datetime with timezone offset from client device.",
)
client_epoch_ms: StrictInt = Field(
...,
ge=0,
description="Unix epoch milliseconds from client device.",
)
@field_validator("device_timezone")
@classmethod
def validate_device_timezone(cls, value: str) -> str:
try:
ZoneInfo(value)
except ZoneInfoNotFoundError as exc:
raise ValueError("invalid client_time.device_timezone") from exc
return value
@field_validator("client_now_iso")
@classmethod
def validate_client_now_iso(cls, value: str) -> str:
if not _RFC3339_WITH_TZ_PATTERN.fullmatch(value):
raise ValueError("invalid client_time.client_now_iso")
normalized = value.replace("Z", "+00:00")
try:
parsed = datetime.fromisoformat(normalized)
except ValueError as exc:
raise ValueError("invalid client_time.client_now_iso") from exc
if parsed.tzinfo is None:
raise ValueError("invalid client_time.client_now_iso")
return value
class ForwardedPropsPayload(BaseModel):
model_config = ConfigDict(extra="forbid")
client_time: ClientTimeContext | None = None
def parse_forwarded_props_client_time(
forwarded_props: Any,
) -> ClientTimeContext | None:
if not isinstance(forwarded_props, dict):
return None
try:
payload = ForwardedPropsPayload.model_validate(forwarded_props)
except ValidationError as exc:
raise ValueError("invalid RunAgentInput.forwardedProps") from exc
return payload.client_time