diff --git a/servers/fastapi/api/v1/ppt/endpoints/presentation.py b/servers/fastapi/api/v1/ppt/endpoints/presentation.py index 7d8439a8..7c389d9c 100644 --- a/servers/fastapi/api/v1/ppt/endpoints/presentation.py +++ b/servers/fastapi/api/v1/ppt/endpoints/presentation.py @@ -22,6 +22,8 @@ from models.generate_presentation_api import ( PresentationPathAndEditPath, ) from services.get_layout_by_name import get_layout_by_name +from services.icon_finder_service import IconFinderService +from services.image_generation_service import ImageGenerationService from utils.llm_calls.generate_presentation_outlines import generate_ppt_outline from models.sql.slide import SlideModel from models.sse_response import SSECompleteResponse, SSEResponse @@ -30,7 +32,7 @@ from services.database import get_sql_session from services.documents_loader import DocumentsLoader from models.sql.presentation import PresentationModel from services.pptx_presentation_creator import PptxPresentationCreator -from utils.asset_directory_utils import get_exports_directory +from utils.asset_directory_utils import get_exports_directory, get_images_directory from utils.llm_calls.generate_document_summary import generate_document_summary from utils.llm_calls.generate_presentation_structure import ( generate_presentation_structure, @@ -195,6 +197,9 @@ async def stream_presentation(presentation_id: str): detail="Outlines can not be empty", ) + image_generation_service = ImageGenerationService(get_images_directory()) + icon_finder_service = IconFinderService() + async def inner(): structure = presentation.get_structure() layout = presentation.get_layout() @@ -223,7 +228,11 @@ async def stream_presentation(presentation_id: str): slides.append(slide) # This will mutate slide - async_assets_generation_tasks.append(process_slide_and_fetch_assets(slide)) + async_assets_generation_tasks.append( + process_slide_and_fetch_assets( + image_generation_service, icon_finder_service, slide + ) + ) # Give control to the event loop await asyncio.sleep(0) @@ -423,9 +432,13 @@ async def generate_presentation_api( # Process slides to fetch assets (images, icons, etc.) print("Processing slides to fetch assets") + image_generation_service = ImageGenerationService(get_images_directory()) + icon_finder_service = IconFinderService() for slide in slides: try: - await process_slide_and_fetch_assets(slide) + await process_slide_and_fetch_assets( + image_generation_service, icon_finder_service, slide + ) print(f"Processed slide {slide.index} successfully") except Exception as e: print(f"Error processing slide {slide.index}: {e}") diff --git a/servers/fastapi/api/v1/ppt/endpoints/slide.py b/servers/fastapi/api/v1/ppt/endpoints/slide.py index c473fc03..111d7e3e 100644 --- a/servers/fastapi/api/v1/ppt/endpoints/slide.py +++ b/servers/fastapi/api/v1/ppt/endpoints/slide.py @@ -4,6 +4,9 @@ from fastapi import APIRouter, Body, HTTPException from models.sql.presentation import PresentationModel from models.sql.slide import SlideModel from services.database import get_sql_session +from services.icon_finder_service import IconFinderService +from services.image_generation_service import ImageGenerationService +from utils.asset_directory_utils import get_images_directory from utils.llm_calls.edit_slide import get_edited_slide_content from utils.llm_calls.edit_slide_html import get_edited_slide_html from utils.llm_calls.select_slide_type_on_edit import get_slide_layout_from_prompt @@ -34,9 +37,15 @@ async def edit_slide(id: Annotated[str, Body()], prompt: Annotated[str, Body()]) prompt, slide_layout, slide, presentation.language ) + image_generation_service = ImageGenerationService(get_images_directory()) + icon_finder_service = IconFinderService() + # This will mutate edited_slide_content new_assets = await process_old_and_new_slides_and_fetch_assets( - slide.content, edited_slide_content + image_generation_service, + icon_finder_service, + slide.content, + edited_slide_content, ) # Always assign a new unique id to the slide diff --git a/servers/fastapi/chroma/843eb4ee-36d1-4b11-be77-d076f16f7418/data_level0.bin b/servers/fastapi/chroma/843eb4ee-36d1-4b11-be77-d076f16f7418/data_level0.bin deleted file mode 100644 index 8011f99d..00000000 Binary files a/servers/fastapi/chroma/843eb4ee-36d1-4b11-be77-d076f16f7418/data_level0.bin and /dev/null differ diff --git a/servers/fastapi/chroma/843eb4ee-36d1-4b11-be77-d076f16f7418/header.bin b/servers/fastapi/chroma/843eb4ee-36d1-4b11-be77-d076f16f7418/header.bin deleted file mode 100644 index 47422986..00000000 Binary files a/servers/fastapi/chroma/843eb4ee-36d1-4b11-be77-d076f16f7418/header.bin and /dev/null differ diff --git a/servers/fastapi/chroma/843eb4ee-36d1-4b11-be77-d076f16f7418/index_metadata.pickle b/servers/fastapi/chroma/843eb4ee-36d1-4b11-be77-d076f16f7418/index_metadata.pickle deleted file mode 100644 index 58ead877..00000000 Binary files a/servers/fastapi/chroma/843eb4ee-36d1-4b11-be77-d076f16f7418/index_metadata.pickle and /dev/null differ diff --git a/servers/fastapi/chroma/843eb4ee-36d1-4b11-be77-d076f16f7418/length.bin b/servers/fastapi/chroma/843eb4ee-36d1-4b11-be77-d076f16f7418/length.bin deleted file mode 100644 index ba4322f2..00000000 Binary files a/servers/fastapi/chroma/843eb4ee-36d1-4b11-be77-d076f16f7418/length.bin and /dev/null differ diff --git a/servers/fastapi/chroma/843eb4ee-36d1-4b11-be77-d076f16f7418/link_lists.bin b/servers/fastapi/chroma/843eb4ee-36d1-4b11-be77-d076f16f7418/link_lists.bin deleted file mode 100644 index 3384c940..00000000 Binary files a/servers/fastapi/chroma/843eb4ee-36d1-4b11-be77-d076f16f7418/link_lists.bin and /dev/null differ diff --git a/servers/fastapi/chroma/chroma.sqlite3 b/servers/fastapi/chroma/chroma.sqlite3 deleted file mode 100644 index 96e83cb6..00000000 Binary files a/servers/fastapi/chroma/chroma.sqlite3 and /dev/null differ diff --git a/servers/fastapi/qdrant/.lock b/servers/fastapi/qdrant/.lock new file mode 100644 index 00000000..a4b31f42 --- /dev/null +++ b/servers/fastapi/qdrant/.lock @@ -0,0 +1 @@ +tmp lock file \ No newline at end of file diff --git a/servers/fastapi/qdrant/collection/icons/storage.sqlite b/servers/fastapi/qdrant/collection/icons/storage.sqlite new file mode 100644 index 00000000..ec2c4f1b Binary files /dev/null and b/servers/fastapi/qdrant/collection/icons/storage.sqlite differ diff --git a/servers/fastapi/qdrant/meta.json b/servers/fastapi/qdrant/meta.json new file mode 100644 index 00000000..49f0a321 --- /dev/null +++ b/servers/fastapi/qdrant/meta.json @@ -0,0 +1 @@ +{"collections": {"icons": {"vectors": {"fast-bge-small-en": {"size": 384, "distance": "Cosine", "hnsw_config": null, "quantization_config": null, "on_disk": null, "datatype": null, "multivector_config": null}}, "shard_number": null, "sharding_method": null, "replication_factor": null, "write_consistency_factor": null, "on_disk_payload": null, "hnsw_config": null, "wal_config": null, "optimizers_config": null, "init_from": null, "quantization_config": null, "sparse_vectors": null, "strict_mode_config": null}}, "aliases": {}} \ No newline at end of file diff --git a/servers/fastapi/requirements.txt b/servers/fastapi/requirements.txt index 4be70028..847185be 100644 --- a/servers/fastapi/requirements.txt +++ b/servers/fastapi/requirements.txt @@ -12,7 +12,6 @@ cachetools==5.5.2 certifi==2025.7.14 cffi==1.17.1 charset-normalizer==3.4.2 -chromadb==1.0.15 click==8.2.1 coloredlogs==15.0.1 cryptography==45.0.5 @@ -23,6 +22,7 @@ email_validator==2.2.0 fastapi==0.116.1 fastapi-cli==0.0.8 fastapi-cloud-cli==0.1.4 +fastembed==0.7.1 filelock==3.18.0 flatbuffers==25.2.10 frozenlist==1.7.0 @@ -33,12 +33,15 @@ googleapis-common-protos==1.70.0 greenlet==3.2.3 grpcio==1.74.0 h11==0.16.0 +h2==4.2.0 hf-xet==1.1.5 +hpack==4.1.0 httpcore==1.0.9 httptools==0.6.4 httpx==0.28.1 huggingface-hub==0.34.1 humanfriendly==10.0 +hyperframe==6.1.0 idna==3.10 importlib_metadata==8.7.0 importlib_resources==6.5.2 @@ -74,6 +77,7 @@ pdfminer.six==20250506 pdfplumber==0.11.7 pillow==11.3.0 pluggy==1.6.0 +portalocker==3.2.0 posthog==5.4.0 propcache==0.3.2 protobuf==6.31.1 @@ -95,6 +99,7 @@ python-dotenv==1.1.1 python-multipart==0.0.20 python-pptx==1.0.2 PyYAML==6.0.2 +qdrant-client==1.15.0 redis==6.2.0 referencing==0.36.2 requests==2.32.4 diff --git a/servers/fastapi/server.py b/servers/fastapi/server.py index b03ba2ac..1592bbb2 100644 --- a/servers/fastapi/server.py +++ b/servers/fastapi/server.py @@ -1,6 +1,8 @@ import uvicorn import argparse +from services.icon_finder_service import IconFinderService + if __name__ == "__main__": parser = argparse.ArgumentParser(description="Run the FastAPI server") diff --git a/servers/fastapi/services/icon_finder_service.py b/servers/fastapi/services/icon_finder_service.py index faa89763..d6ea2e34 100644 --- a/servers/fastapi/services/icon_finder_service.py +++ b/servers/fastapi/services/icon_finder_service.py @@ -1,55 +1,45 @@ import asyncio import json -import chromadb -from chromadb.config import Settings -from chromadb.utils import embedding_functions +from qdrant_client import QdrantClient class IconFinderService: def __init__(self): self.collection_name = "icons" - self.vector_store = self.get_icons_vectorstore() - - def get_icons_vectorstore(self): - - client = chromadb.PersistentClient( - settings=Settings(anonymized_telemetry=False) - ) - embedding_function = embedding_functions.ONNXMiniLM_L6_V2() + self.client = QdrantClient(path="qdrant") + self._initialize_icons_collection() + def _initialize_icons_collection(self): try: - collection = client.get_collection( - self.collection_name, embedding_function=embedding_function + self.client.get_collection(self.collection_name) + except Exception: + self._populate_icons_collection() + + def _populate_icons_collection(self): + with open("assets/icons.json", "r") as f: + icons = json.load(f) + + documents = [] + metadata = [] + + for each in icons["icons"]: + if each["name"].split("-")[-1] == "bold": + doc_text = f"{each['name']} {each['tags']}" + documents.append(doc_text) + metadata.append({"name": each["name"]}) + + if documents: + self.client.add( + collection_name=self.collection_name, + documents=documents, + metadata=metadata, ) - except: - collection = client.create_collection( - self.collection_name, embedding_function=embedding_function - ) - - with open("assets/icons.json", "r") as f: - icons = json.load(f) - - documents = [] - ids = [] - - for each in icons["icons"]: - if each["name"].split("-")[-1] == "bold": - doc_text = f"{each['name']} {each['tags']}" - documents.append(doc_text) - ids.append(each["name"]) - - if documents: - collection.add(documents=documents, ids=ids) - - return collection async def search_icons(self, query: str, k: int = 1): result = await asyncio.to_thread( - self.vector_store.query, query_texts=[query], n_results=k + self.client.query, + collection_name=self.collection_name, + query_text=query, + limit=k, ) - - icon_names = [] - if result["ids"] and result["ids"][0]: - icon_names = result["ids"][0] - - return [f"/static/icons/bold/{icon_name}.png" for icon_name in icon_names] + return [f"/static/icons/bold/{each.metadata['name']}.png" for each in result] diff --git a/servers/fastapi/utils/process_slides.py b/servers/fastapi/utils/process_slides.py index c280cdb2..6ffca66f 100644 --- a/servers/fastapi/utils/process_slides.py +++ b/servers/fastapi/utils/process_slides.py @@ -10,12 +10,10 @@ from utils.dict_utils import get_dict_at_path, get_dict_paths_with_key, set_dict async def process_slide_and_fetch_assets( + image_generation_service: ImageGenerationService, + icon_finder_service: IconFinderService, slide: SlideModel, ) -> List[ImageAsset]: - image_directory = get_images_directory() - - image_generation_service = ImageGenerationService(image_directory) - icon_finder_service = IconFinderService() async_tasks = [] @@ -61,6 +59,8 @@ async def process_slide_and_fetch_assets( async def process_old_and_new_slides_and_fetch_assets( + image_generation_service: ImageGenerationService, + icon_finder_service: IconFinderService, old_slide_content: dict, new_slide_content: dict, ) -> List[ImageAsset]: @@ -98,10 +98,6 @@ async def process_old_and_new_slides_and_fetch_assets( get_dict_at_path(new_slide_content, path) for path in new_icon_dict_paths ] - # Creates services - image_generation_service = ImageGenerationService(get_images_directory()) - icon_finder_service = IconFinderService() - # Creates async tasks for fetching new images async_image_fetch_tasks = [] new_images_fetch_status = []