import json import logging import os import asyncio import tempfile import time from typing import List, Dict, Optional, Any import openai from core.config import settings from core.prompts import get_canvas_system_prompt, get_canvas_edit_prompt from services.s3_service import s3_service logger = logging.getLogger(__name__) class CanvasService: def __init__(self): self.openai_client = openai.OpenAI(api_key=settings.OPENAI_API_KEY) async def generate_canvas_summary( self, file_key: Optional[str] = None, text_input: Optional[str] = None ) -> str: """ Generates a Markdown summary of the provided content. """ try: system_prompt = get_canvas_system_prompt() user_msg = "Please analyze the provided content and create a comprehensive, well-structured summary in Markdown format. Output ONLY the raw markdown content without code block markers." if file_key: # ... (downloading code unchanged) tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") tmp_path = tmp.name tmp.close() try: await asyncio.to_thread( s3_service.s3_client.download_file, settings.AWS_S3_BUCKET, file_key, tmp_path ) def upload_to_openai(): with open(tmp_path, "rb") as f: return self.openai_client.files.create(file=f, purpose="assistants") uploaded_file = await asyncio.to_thread(upload_to_openai) messages = [ {"role": "system", "content": system_prompt}, { "role": "user", "content": [ {"type": "file", "file": {"file_id": uploaded_file.id}}, {"type": "text", "text": user_msg} ] } ] response = await asyncio.to_thread( self.openai_client.chat.completions.create, model="gpt-4o", messages=messages, temperature=0.7 ) await asyncio.to_thread(self.openai_client.files.delete, uploaded_file.id) return self._clean_markdown_content(response.choices[0].message.content) finally: if os.path.exists(tmp_path): await asyncio.to_thread(os.remove, tmp_path) elif text_input: messages = [ {"role": "system", "content": system_prompt}, {"role": "user", "content": f"{user_msg}\n\nCONTENT:\n{text_input}"} ] response = await asyncio.to_thread( self.openai_client.chat.completions.create, model="gpt-4o-mini", messages=messages, temperature=0.7 ) return self._clean_markdown_content(response.choices[0].message.content) else: raise ValueError("Either file_key or text_input must be provided") except Exception as e: logger.error(f"Canvas summary generation failed: {e}") raise async def refine_canvas( self, instruction: str, current_content: str ) -> str: """ Refines existing Markdown content based on user instructions. """ try: system_prompt = get_canvas_system_prompt() refine_prompt = get_canvas_edit_prompt(instruction, current_content) messages = [ {"role": "system", "content": system_prompt}, {"role": "user", "content": refine_prompt} ] response = await asyncio.to_thread( self.openai_client.chat.completions.create, model="gpt-4o", messages=messages, temperature=0.7 ) return self._clean_markdown_content(response.choices[0].message.content) except Exception as e: logger.error(f"Canvas refinement failed: {e}") raise def _clean_markdown_content(self, content: str) -> str: """Strips markdown code block markers (```markdown ... ```) if present.""" content = content.strip() if content.startswith("```"): # Remove opening marker content = content.split("\n", 1)[-1] if "\n" in content else content[3:] # Remove closing marker if content.endswith("```"): content = content[:-3] return content.strip() canvas_service = CanvasService()