chore: no changes needed for calendar message card
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import base64
|
||||
from dataclasses import dataclass
|
||||
from datetime import date
|
||||
import hashlib
|
||||
from typing import Any, Protocol
|
||||
|
||||
import dashscope
|
||||
@@ -12,6 +14,7 @@ from fastapi import HTTPException
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
from core.auth.models import CurrentUser
|
||||
from core.agentscope.schemas.agui_input import extract_latest_user_payload
|
||||
from core.config.settings import config
|
||||
from core.logging import get_logger
|
||||
|
||||
@@ -54,6 +57,15 @@ class AgentRepositoryLike(Protocol):
|
||||
|
||||
async def get_latest_session_id_for_user(self, *, user_id: str) -> str | None: ...
|
||||
|
||||
async def persist_user_message(
|
||||
self,
|
||||
*,
|
||||
session_id: str,
|
||||
run_id: str,
|
||||
content_text: str,
|
||||
metadata: dict[str, object] | None,
|
||||
) -> None: ...
|
||||
|
||||
|
||||
class QueueClientLike(Protocol):
|
||||
async def enqueue(
|
||||
@@ -70,6 +82,17 @@ class EventStreamLike(Protocol):
|
||||
) -> list[dict[str, object]]: ...
|
||||
|
||||
|
||||
class AttachmentStorageLike(Protocol):
|
||||
async def upload_bytes(
|
||||
self,
|
||||
*,
|
||||
bucket: str,
|
||||
path: str,
|
||||
content: bytes,
|
||||
content_type: str,
|
||||
) -> str: ...
|
||||
|
||||
|
||||
def ensure_session_owner(*, owner_id: str, current_user: CurrentUser) -> None:
|
||||
if owner_id != str(current_user.id):
|
||||
raise HTTPException(status_code=403, detail="Forbidden")
|
||||
@@ -79,6 +102,7 @@ class AgentService:
|
||||
_repository: AgentRepositoryLike
|
||||
_queue: QueueClientLike
|
||||
_stream: EventStreamLike
|
||||
_attachment_storage: AttachmentStorageLike | None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -86,10 +110,12 @@ class AgentService:
|
||||
repository: AgentRepositoryLike,
|
||||
queue: QueueClientLike,
|
||||
stream: EventStreamLike,
|
||||
attachment_storage: AttachmentStorageLike | None = None,
|
||||
) -> None:
|
||||
self._repository = repository
|
||||
self._queue = queue
|
||||
self._stream = stream
|
||||
self._attachment_storage = attachment_storage
|
||||
|
||||
async def enqueue_run(
|
||||
self,
|
||||
@@ -119,6 +145,18 @@ class AgentService:
|
||||
else:
|
||||
ensure_session_owner(owner_id=owner, current_user=current_user)
|
||||
|
||||
user_message_text, user_message_metadata = await self._prepare_user_message(
|
||||
run_input=run_input,
|
||||
current_user=current_user,
|
||||
)
|
||||
await self._repository.persist_user_message(
|
||||
session_id=thread_id,
|
||||
run_id=run_id,
|
||||
content_text=user_message_text,
|
||||
metadata=user_message_metadata,
|
||||
)
|
||||
await self._repository.commit()
|
||||
|
||||
task_id = await self._queue.enqueue(
|
||||
command={
|
||||
"command": "run",
|
||||
@@ -135,6 +173,54 @@ class AgentService:
|
||||
created=created,
|
||||
)
|
||||
|
||||
async def _prepare_user_message(
|
||||
self,
|
||||
*,
|
||||
run_input: RunAgentInput,
|
||||
current_user: CurrentUser,
|
||||
) -> tuple[str, dict[str, object] | None]:
|
||||
text, content_blocks = extract_latest_user_payload(run_input)
|
||||
attachments: list[dict[str, object]] = []
|
||||
if self._attachment_storage is not None:
|
||||
for index, block in enumerate(content_blocks):
|
||||
if not isinstance(block, dict):
|
||||
continue
|
||||
if block.get("type") != "image_url":
|
||||
continue
|
||||
image_value = block.get("image_url")
|
||||
if not isinstance(image_value, dict):
|
||||
continue
|
||||
url = image_value.get("url")
|
||||
if not isinstance(url, str) or not url.startswith("data:"):
|
||||
continue
|
||||
decoded = _decode_data_url(url)
|
||||
if decoded is None:
|
||||
continue
|
||||
mime_type, payload = decoded
|
||||
suffix = _mime_to_suffix(mime_type)
|
||||
checksum = hashlib.sha1(payload).hexdigest()[:16]
|
||||
path = (
|
||||
f"agent-inputs/{current_user.id}/{run_input.thread_id}/"
|
||||
f"{run_input.run_id}/attachment-{index}-{checksum}.{suffix}"
|
||||
)
|
||||
stored_path = await self._attachment_storage.upload_bytes(
|
||||
bucket=config.storage.bucket,
|
||||
path=path,
|
||||
content=payload,
|
||||
content_type=mime_type,
|
||||
)
|
||||
attachments.append(
|
||||
{
|
||||
"bucket": config.storage.bucket,
|
||||
"path": stored_path,
|
||||
"mimeType": mime_type,
|
||||
}
|
||||
)
|
||||
metadata: dict[str, object] = {}
|
||||
if attachments:
|
||||
metadata["attachments"] = attachments
|
||||
return text, metadata or None
|
||||
|
||||
async def enqueue_resume(
|
||||
self,
|
||||
*,
|
||||
@@ -340,3 +426,30 @@ class AsrService:
|
||||
|
||||
|
||||
asr_service = AsrService()
|
||||
|
||||
|
||||
def _decode_data_url(data_url: str) -> tuple[str, bytes] | None:
|
||||
if not data_url.startswith("data:"):
|
||||
return None
|
||||
header, sep, payload = data_url.partition(",")
|
||||
if not sep:
|
||||
return None
|
||||
mime_type = "image/png"
|
||||
if ";" in header:
|
||||
maybe_mime = header[5:].split(";", 1)[0].strip()
|
||||
if maybe_mime:
|
||||
mime_type = maybe_mime
|
||||
try:
|
||||
decoded = base64.b64decode(payload, validate=True)
|
||||
except ValueError:
|
||||
return None
|
||||
return mime_type, decoded
|
||||
|
||||
|
||||
def _mime_to_suffix(mime_type: str) -> str:
|
||||
mapping = {
|
||||
"image/png": "png",
|
||||
"image/jpeg": "jpg",
|
||||
"image/webp": "webp",
|
||||
}
|
||||
return mapping.get(mime_type.lower(), "bin")
|
||||
|
||||
Reference in New Issue
Block a user