Files
agent-framework/python/samples/demos/chatkit-integration/attachment_store.py
T
Eric Zhu 0c862e97a6 Python: feat: Add ChatKit integration with a sample application (#1273)
* feat: Add ChatKit integration with a new frontend application

- Created a new frontend application using React and Vite for the ChatKit integration.
- Added essential files including package.json, vite.config.ts, and Tailwind CSS configuration.
- Implemented core components: App, Home, ChatKitPanel, ThemeToggle, and hooks for color scheme management.
- Established SQLite-based store implementation for ChatKit data persistence in store.py.
- Integrated theme toggling functionality for light and dark modes.
- Set up ESLint and TypeScript configurations for better development experience.

* git ignore

* fix mypy

* add mising file

* minimal frontend for chatkit sample

* update ignore files

* version

* set python version lowerbound on chatkit

* update project settings for chatkit

* update setup

* update setup

* update setup

* update setup

* weather widget

* add select city widget sample

* remove widget helper

* update chatkit to include file attachments and cover more thread item types

* update readme with mermaid diagram

* update diagram

* update instructions

* update chatkit dependency

* fix converter imports

* move to demos/

* move to demos/ -- rename references

* support multiple session instead of using global variable in sample

* support chunk streaming

* fix tests

* Update python/samples/demos/chatkit-integration/store.py

Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>

* use local host

---------

Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
2025-11-05 02:11:40 +00:00

122 lines
4.5 KiB
Python

# Copyright (c) Microsoft. All rights reserved.
"""File-based AttachmentStore implementation for ChatKit.
This module provides a simple AttachmentStore implementation that stores
uploaded files on the local filesystem. In production, you should use
cloud storage like S3, Azure Blob Storage, or Google Cloud Storage.
"""
from pathlib import Path
from typing import Any, TYPE_CHECKING
from chatkit.store import AttachmentStore
from chatkit.types import Attachment, AttachmentCreateParams, FileAttachment, ImageAttachment
from pydantic import AnyUrl
if TYPE_CHECKING:
from store import SQLiteStore
class FileBasedAttachmentStore(AttachmentStore[dict[str, Any]]):
"""File-based AttachmentStore that stores files on local disk.
This implementation stores uploaded files in a local directory and provides
upload URLs that point to the FastAPI upload endpoint. It supports both
image and file attachments.
Features:
- Stores files in a local uploads directory
- Generates upload URLs for two-phase upload
- Generates preview URLs for images
- Proper cleanup on deletion
Note: This is for demonstration purposes. In production, use cloud storage
with signed URLs for better security and scalability.
"""
def __init__(
self,
uploads_dir: str = "./uploads",
base_url: str = "http://localhost:8001",
data_store: "SQLiteStore | None" = None,
):
"""Initialize the file-based attachment store.
Args:
uploads_dir: Directory where uploaded files will be stored
base_url: Base URL for generating upload and preview URLs
data_store: Optional data store to persist attachment metadata
"""
self.uploads_dir = Path(uploads_dir)
self.base_url = base_url.rstrip("/")
self.data_store = data_store
# Create uploads directory if it doesn't exist
self.uploads_dir.mkdir(parents=True, exist_ok=True)
def get_file_path(self, attachment_id: str) -> Path:
"""Get the filesystem path for an attachment."""
return self.uploads_dir / attachment_id
async def delete_attachment(self, attachment_id: str, context: dict[str, Any]) -> None:
"""Delete an attachment and its file from disk."""
file_path = self.get_file_path(attachment_id)
if file_path.exists():
file_path.unlink()
async def create_attachment(
self, input: AttachmentCreateParams, context: dict[str, Any]
) -> Attachment:
"""Create an attachment with upload URL for two-phase upload.
This creates the attachment metadata and returns upload URLs that
the client will use to POST the actual file bytes.
"""
# Generate unique ID for this attachment
attachment_id = self.generate_attachment_id(input.mime_type, context)
# Generate upload URL that points to our FastAPI upload endpoint
upload_url = f"{self.base_url}/upload/{attachment_id}"
# Create appropriate attachment type based on MIME type
if input.mime_type.startswith("image/"):
# For images, also provide a preview URL
preview_url = f"{self.base_url}/preview/{attachment_id}"
attachment = ImageAttachment(
id=attachment_id,
type="image",
mime_type=input.mime_type,
name=input.name,
upload_url=AnyUrl(upload_url),
preview_url=AnyUrl(preview_url),
)
else:
# For files, just provide upload URL
attachment = FileAttachment(
id=attachment_id,
type="file",
mime_type=input.mime_type,
name=input.name,
upload_url=AnyUrl(upload_url),
)
# Save attachment metadata to data store so it's available during upload
if self.data_store is not None:
await self.data_store.save_attachment(attachment, context)
return attachment
async def read_attachment_bytes(self, attachment_id: str) -> bytes:
"""Read the raw bytes of an uploaded attachment.
This is used by the ThreadItemConverter to create base64-encoded
content for sending to the Agent Framework.
"""
file_path = self.get_file_path(attachment_id)
if not file_path.exists():
raise FileNotFoundError(f"Attachment {attachment_id} not found on disk")
return file_path.read_bytes()