feat(fastapi): adds logic to generate speaker notes and slide note support in export
This commit is contained in:
parent
01d39d71be
commit
29841bdd06
8 changed files with 82 additions and 4 deletions
|
|
@ -221,6 +221,7 @@ async def stream_presentation(
|
|||
layout_group=layout.name,
|
||||
layout=slide_layout.id,
|
||||
index=i,
|
||||
speaker_note=slide_content.get("__speaker_note__", ""),
|
||||
content=slide_content,
|
||||
)
|
||||
slides.append(slide)
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ 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
|
||||
from utils.process_slides import process_old_and_new_slides_and_fetch_assets
|
||||
from utils.randomizers import get_random_uuid
|
||||
from utils.schema_utils import remove_fields_from_schema
|
||||
|
||||
|
||||
SLIDE_ROUTER = APIRouter(prefix="/slide", tags=["Slide"])
|
||||
|
|
@ -59,6 +58,7 @@ async def edit_slide(
|
|||
sql_session.add(slide)
|
||||
slide.content = edited_slide_content
|
||||
slide.layout = slide_layout.id
|
||||
slide.speaker_note = edited_slide_content.get("__speaker_note__", "")
|
||||
sql_session.add_all(new_assets)
|
||||
await sql_session.commit()
|
||||
|
||||
|
|
|
|||
|
|
@ -156,6 +156,7 @@ class PptxConnectorModel(PptxShapeModel):
|
|||
|
||||
class PptxSlideModel(BaseModel):
|
||||
background: Optional[PptxFillModel] = None
|
||||
note: Optional[str] = None
|
||||
shapes: List[
|
||||
PptxTextBoxModel
|
||||
| PptxAutoShapeBoxModel
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ class SlideModel(SQLModel, table=True):
|
|||
index: int
|
||||
content: dict = Field(sa_column=Column(JSON))
|
||||
html_content: Optional[str]
|
||||
speaker_note: str
|
||||
properties: Optional[dict] = Field(sa_column=Column(JSON))
|
||||
|
||||
def get_new_slide(self, presentation_id: str, content: Optional[dict] = None):
|
||||
|
|
@ -21,6 +22,7 @@ class SlideModel(SQLModel, table=True):
|
|||
layout_group=self.layout_group,
|
||||
layout=self.layout,
|
||||
index=self.index,
|
||||
speaker_note=self.speaker_note,
|
||||
content=content or self.content,
|
||||
properties=self.properties,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -147,6 +147,9 @@ class PptxPresentationCreator:
|
|||
if slide_model.background:
|
||||
self.apply_fill_to_shape(slide.background, slide_model.background)
|
||||
|
||||
if slide_model.note:
|
||||
slide.notes_slide.notes_text_frame.text = slide_model.note
|
||||
|
||||
for shape_model in slide_model.shapes:
|
||||
model_type = type(shape_model)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,10 +3,11 @@ from models.presentation_layout import SlideLayoutModel
|
|||
from models.sql.slide import SlideModel
|
||||
from services.llm_client import LLMClient
|
||||
from utils.llm_provider import get_model
|
||||
from utils.schema_utils import remove_fields_from_schema
|
||||
from utils.schema_utils import add_field_in_schema, remove_fields_from_schema
|
||||
|
||||
system_prompt = """
|
||||
Edit Slide data based on provided prompt, follow mentioned steps and notes and provide structured output.
|
||||
Edit Slide data and speaker note based on provided prompt, follow mentioned steps and notes and provide structured output.
|
||||
|
||||
|
||||
# Notes
|
||||
- Provide output in language mentioned in **Input**.
|
||||
|
|
@ -14,6 +15,8 @@ system_prompt = """
|
|||
- Do not change **Image prompts** and **Icon queries** if not asked for in prompt.
|
||||
- Generate **Image prompts** and **Icon queries** if asked to generate or change in prompt.
|
||||
- Make sure to follow language guidelines.
|
||||
- Speaker note should be normal text, not markdown.
|
||||
- Speaker note should be simple, clear, concise and to the point.
|
||||
|
||||
**Go through all notes and steps and make sure they are followed, including mentioned constraints**
|
||||
"""
|
||||
|
|
@ -61,6 +64,18 @@ async def get_edited_slide_content(
|
|||
response_schema = remove_fields_from_schema(
|
||||
slide_layout.json_schema, ["__image_url__", "__icon_url__"]
|
||||
)
|
||||
response_schema = add_field_in_schema(
|
||||
response_schema,
|
||||
{
|
||||
"__speaker_note__": {
|
||||
"type": "string",
|
||||
"minLength": 100,
|
||||
"maxLength": 250,
|
||||
"description": "Speaker note for the slide",
|
||||
}
|
||||
},
|
||||
True,
|
||||
)
|
||||
|
||||
client = LLMClient()
|
||||
response = await client.generate_structured(
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from models.presentation_layout import SlideLayoutModel
|
|||
from models.presentation_outline_model import SlideOutlineModel
|
||||
from services.llm_client import LLMClient
|
||||
from utils.llm_provider import get_model
|
||||
from utils.schema_utils import remove_fields_from_schema
|
||||
from utils.schema_utils import add_field_in_schema, remove_fields_from_schema
|
||||
|
||||
system_prompt = """
|
||||
Generate structured slide based on provided outline, follow mentioned steps and notes and provide structured output.
|
||||
|
|
@ -11,6 +11,7 @@ system_prompt = """
|
|||
# Steps
|
||||
1. Analyze the outline.
|
||||
2. Generate structured slide based on the outline.
|
||||
3. Generate speaker note that is simple, clear, concise and to the point.
|
||||
|
||||
# Notes
|
||||
- Slide body should not use words like "This slide", "This presentation".
|
||||
|
|
@ -19,6 +20,7 @@ system_prompt = """
|
|||
- Provide query to search icon on "__icon_query__" property.
|
||||
- Only use markdown to highlight important points.
|
||||
- Make sure to follow language guidelines.
|
||||
- Speaker note should be normal text, not markdown.
|
||||
**Strictly follow the max and min character limit for every property in the slide.**
|
||||
"""
|
||||
|
||||
|
|
@ -57,6 +59,18 @@ async def get_slide_content_from_type_and_outline(
|
|||
response_schema = remove_fields_from_schema(
|
||||
slide_layout.json_schema, ["__image_url__", "__icon_url__"]
|
||||
)
|
||||
response_schema = add_field_in_schema(
|
||||
response_schema,
|
||||
{
|
||||
"__speaker_note__": {
|
||||
"type": "string",
|
||||
"minLength": 100,
|
||||
"maxLength": 250,
|
||||
"description": "Speaker note for the slide",
|
||||
}
|
||||
},
|
||||
True,
|
||||
)
|
||||
|
||||
response = await client.generate_structured(
|
||||
model=model,
|
||||
|
|
|
|||
|
|
@ -45,6 +45,48 @@ def remove_fields_from_schema(schema: dict, fields_to_remove: List[str]):
|
|||
return schema
|
||||
|
||||
|
||||
def add_field_in_schema(schema: dict, field: dict, required: bool = False) -> dict:
|
||||
|
||||
if not isinstance(field, dict) or len(field) != 1:
|
||||
raise ValueError(
|
||||
"`field` must be a dict with exactly one entry: {name: schema_dict}"
|
||||
)
|
||||
|
||||
field_name, field_schema = next(iter(field.items()))
|
||||
if not isinstance(field_name, str):
|
||||
raise TypeError("Field name must be a string")
|
||||
if not isinstance(field_schema, dict):
|
||||
raise TypeError("Field schema must be a dictionary")
|
||||
|
||||
updated_schema: dict = deepcopy(schema)
|
||||
|
||||
root_properties = updated_schema.get("properties")
|
||||
if not isinstance(root_properties, dict):
|
||||
updated_schema["properties"] = {}
|
||||
root_properties = updated_schema["properties"]
|
||||
|
||||
root_properties[field_name] = field_schema
|
||||
|
||||
# Update root-level required based on the flag
|
||||
existing_required = updated_schema.get("required")
|
||||
if not isinstance(existing_required, list):
|
||||
existing_required = []
|
||||
|
||||
if required:
|
||||
if field_name not in existing_required:
|
||||
existing_required.append(field_name)
|
||||
else:
|
||||
if field_name in existing_required:
|
||||
existing_required = [name for name in existing_required if name != field_name]
|
||||
|
||||
if existing_required:
|
||||
updated_schema["required"] = existing_required
|
||||
else:
|
||||
updated_schema.pop("required", None)
|
||||
|
||||
return updated_schema
|
||||
|
||||
|
||||
# From OpenAI
|
||||
def ensure_strict_json_schema(
|
||||
json_schema: object,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue