Spaces:
Running
Running
google-labs-jules[bot]
commited on
Commit
Β·
d051ea8
1
Parent(s):
68e7bc4
fix: Address PR comments
Browse files- Add sys.path modification in app.py to resolve ModuleNotFoundError.
- Move ChatMessage, HumanMessage, and AIMessage classes to chat_helper.py and import them in app.py.
- Update global_config.py to use absolute paths for all file paths.
- Add chat_helper import in app.py.
This view is limited to 50 files because it contains too many changes. Β
See raw diff
- MANIFEST.in +6 -0
- app.py +53 -192
- pyproject.toml +39 -0
- {helpers β src/slidedeckai}/__init__.py +0 -0
- src/slidedeckai/_version.py +1 -0
- src/slidedeckai/cli.py +36 -0
- src/slidedeckai/core.py +198 -0
- {file_embeddings β src/slidedeckai/file_embeddings}/embeddings.npy +0 -0
- {file_embeddings β src/slidedeckai/file_embeddings}/icons.npy +0 -0
- global_config.py β src/slidedeckai/global_config.py +13 -11
- src/slidedeckai/helpers/__init__.py +0 -0
- {helpers β src/slidedeckai/helpers}/chat_helper.py +6 -13
- {helpers β src/slidedeckai/helpers}/file_manager.py +1 -4
- {helpers β src/slidedeckai/helpers}/icons_embeddings.py +3 -6
- {helpers β src/slidedeckai/helpers}/image_search.py +0 -0
- {helpers β src/slidedeckai/helpers}/llm_helper.py +1 -3
- {helpers β src/slidedeckai/helpers}/pptx_helper.py +3 -6
- {helpers β src/slidedeckai/helpers}/text_helper.py +0 -0
- {icons β src/slidedeckai/icons}/png128/0-circle.png +0 -0
- {icons β src/slidedeckai/icons}/png128/1-circle.png +0 -0
- {icons β src/slidedeckai/icons}/png128/123.png +0 -0
- {icons β src/slidedeckai/icons}/png128/2-circle.png +0 -0
- {icons β src/slidedeckai/icons}/png128/3-circle.png +0 -0
- {icons β src/slidedeckai/icons}/png128/4-circle.png +0 -0
- {icons β src/slidedeckai/icons}/png128/5-circle.png +0 -0
- {icons β src/slidedeckai/icons}/png128/6-circle.png +0 -0
- {icons β src/slidedeckai/icons}/png128/7-circle.png +0 -0
- {icons β src/slidedeckai/icons}/png128/8-circle.png +0 -0
- {icons β src/slidedeckai/icons}/png128/9-circle.png +0 -0
- {icons β src/slidedeckai/icons}/png128/activity.png +0 -0
- {icons β src/slidedeckai/icons}/png128/airplane.png +0 -0
- {icons β src/slidedeckai/icons}/png128/alarm.png +0 -0
- {icons β src/slidedeckai/icons}/png128/alien-head.png +0 -0
- {icons β src/slidedeckai/icons}/png128/alphabet.png +0 -0
- {icons β src/slidedeckai/icons}/png128/amazon.png +0 -0
- {icons β src/slidedeckai/icons}/png128/amritsar-golden-temple.png +0 -0
- {icons β src/slidedeckai/icons}/png128/amsterdam-canal.png +0 -0
- {icons β src/slidedeckai/icons}/png128/amsterdam-windmill.png +0 -0
- {icons β src/slidedeckai/icons}/png128/android.png +0 -0
- {icons β src/slidedeckai/icons}/png128/angkor-wat.png +0 -0
- {icons β src/slidedeckai/icons}/png128/apple.png +0 -0
- {icons β src/slidedeckai/icons}/png128/archive.png +0 -0
- {icons β src/slidedeckai/icons}/png128/argentina-obelisk.png +0 -0
- {icons β src/slidedeckai/icons}/png128/artificial-intelligence-brain.png +0 -0
- {icons β src/slidedeckai/icons}/png128/atlanta.png +0 -0
- {icons β src/slidedeckai/icons}/png128/austin.png +0 -0
- {icons β src/slidedeckai/icons}/png128/automation-decision.png +0 -0
- {icons β src/slidedeckai/icons}/png128/award.png +0 -0
- {icons β src/slidedeckai/icons}/png128/balloon.png +0 -0
- {icons β src/slidedeckai/icons}/png128/ban.png +0 -0
MANIFEST.in
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
include src/slidedeckai/strings.json
|
| 2 |
+
recursive-include src/slidedeckai/prompts *.txt
|
| 3 |
+
recursive-include src/slidedeckai/pptx_templates *.pptx
|
| 4 |
+
recursive-include src/slidedeckai/icons *.png
|
| 5 |
+
recursive-include src/slidedeckai/icons *.txt
|
| 6 |
+
recursive-include src/slidedeckai/file_embeddings *.npy
|
app.py
CHANGED
|
@@ -6,6 +6,7 @@ import logging
|
|
| 6 |
import os
|
| 7 |
import pathlib
|
| 8 |
import random
|
|
|
|
| 9 |
import tempfile
|
| 10 |
from typing import List, Union
|
| 11 |
|
|
@@ -17,13 +18,35 @@ import requests
|
|
| 17 |
import streamlit as st
|
| 18 |
from dotenv import load_dotenv
|
| 19 |
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
from
|
| 23 |
-
from
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
|
| 25 |
load_dotenv()
|
| 26 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
RUN_IN_OFFLINE_MODE = os.getenv('RUN_IN_OFFLINE_MODE', 'False').lower() == 'true'
|
| 28 |
|
| 29 |
|
|
@@ -368,99 +391,38 @@ def set_up_chat_ui():
|
|
| 368 |
st.session_state[PDF_FILE_KEY],
|
| 369 |
(st.session_state['start_page'], st.session_state['end_page'])
|
| 370 |
)
|
| 371 |
-
provider, llm_name = llm_helper.get_provider_model(
|
| 372 |
-
llm_provider_to_use,
|
| 373 |
-
use_ollama=RUN_IN_OFFLINE_MODE
|
| 374 |
-
)
|
| 375 |
|
| 376 |
-
# Validate that provider and model were parsed successfully
|
| 377 |
-
if not provider or not llm_name:
|
| 378 |
-
handle_error(
|
| 379 |
-
f'Failed to parse provider and model from: "{llm_provider_to_use}". '
|
| 380 |
-
f'Please select a valid LLM from the dropdown.',
|
| 381 |
-
True
|
| 382 |
-
)
|
| 383 |
-
return
|
| 384 |
-
|
| 385 |
-
user_key = api_key_token.strip()
|
| 386 |
-
az_deployment = azure_deployment.strip()
|
| 387 |
-
az_endpoint = azure_endpoint.strip()
|
| 388 |
-
api_ver = api_version.strip()
|
| 389 |
-
|
| 390 |
-
if not are_all_inputs_valid(
|
| 391 |
-
prompt_text, provider, llm_name, user_key,
|
| 392 |
-
az_deployment, az_endpoint, api_ver
|
| 393 |
-
):
|
| 394 |
-
return
|
| 395 |
-
|
| 396 |
-
logger.info(
|
| 397 |
-
'User input: %s | #characters: %d | LLM: %s',
|
| 398 |
-
prompt_text, len(prompt_text), llm_name
|
| 399 |
-
)
|
| 400 |
st.chat_message('user').write(prompt_text)
|
| 401 |
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
formatted_template = prompt_template.format(
|
| 409 |
-
**{
|
| 410 |
-
'instructions': '\n'.join(list_of_msgs),
|
| 411 |
-
'previous_content': _get_last_response(),
|
| 412 |
-
'additional_info': st.session_state.get(ADDITIONAL_INFO, ''),
|
| 413 |
-
}
|
| 414 |
-
)
|
| 415 |
-
else:
|
| 416 |
-
formatted_template = prompt_template.format(
|
| 417 |
-
**{
|
| 418 |
-
'question': prompt_text,
|
| 419 |
-
'additional_info': st.session_state.get(ADDITIONAL_INFO, ''),
|
| 420 |
-
}
|
| 421 |
-
)
|
| 422 |
|
| 423 |
progress_bar = st.progress(0, 'Preparing to call LLM...')
|
| 424 |
-
|
|
|
|
|
|
|
| 425 |
|
| 426 |
try:
|
| 427 |
-
|
| 428 |
-
|
| 429 |
-
|
| 430 |
-
|
| 431 |
-
api_key=user_key,
|
| 432 |
-
azure_endpoint_url=az_endpoint,
|
| 433 |
-
azure_deployment_name=az_deployment,
|
| 434 |
-
azure_api_version=api_ver,
|
| 435 |
-
)
|
| 436 |
|
| 437 |
-
|
| 438 |
-
|
| 439 |
-
|
| 440 |
-
|
| 441 |
-
|
| 442 |
-
|
| 443 |
-
)
|
| 444 |
-
|
|
|
|
|
|
|
| 445 |
|
| 446 |
-
for chunk in llm.stream(formatted_template):
|
| 447 |
-
if isinstance(chunk, str):
|
| 448 |
-
response += chunk
|
| 449 |
-
else:
|
| 450 |
-
content = getattr(chunk, 'content', None)
|
| 451 |
-
if content is not None:
|
| 452 |
-
response += content
|
| 453 |
-
else:
|
| 454 |
-
response += str(chunk)
|
| 455 |
-
|
| 456 |
-
# Update the progress bar with an approx progress percentage
|
| 457 |
-
progress_bar.progress(
|
| 458 |
-
min(
|
| 459 |
-
len(response) / gcfg.get_max_output_tokens(llm_provider_to_use),
|
| 460 |
-
0.95
|
| 461 |
-
),
|
| 462 |
-
text='Streaming content...this might take a while...'
|
| 463 |
-
)
|
| 464 |
except (httpx.ConnectError, requests.exceptions.ConnectionError):
|
| 465 |
handle_error(
|
| 466 |
'A connection error occurred while streaming content from the LLM endpoint.'
|
|
@@ -469,22 +431,19 @@ def set_up_chat_ui():
|
|
| 469 |
' using Ollama, make sure that Ollama is already running on your system.',
|
| 470 |
True
|
| 471 |
)
|
| 472 |
-
return
|
| 473 |
except huggingface_hub.errors.ValidationError as ve:
|
| 474 |
handle_error(
|
| 475 |
f'An error occurred while trying to generate the content: {ve}'
|
| 476 |
'\nPlease try again with a significantly shorter input text.',
|
| 477 |
True
|
| 478 |
)
|
| 479 |
-
return
|
| 480 |
except ollama.ResponseError:
|
| 481 |
handle_error(
|
| 482 |
-
f'The model
|
| 483 |
-
f' Make sure that you have provided the correct LLM name or pull it
|
| 484 |
-
f'
|
| 485 |
True
|
| 486 |
)
|
| 487 |
-
return
|
| 488 |
except Exception as ex:
|
| 489 |
_msg = str(ex)
|
| 490 |
if 'payment required' in _msg.lower():
|
|
@@ -509,101 +468,6 @@ def set_up_chat_ui():
|
|
| 509 |
' Read **[how to get free LLM API keys](https://github.com/barun-saha/slide-deck-ai?tab=readme-ov-file#summary-of-the-llms)**.',
|
| 510 |
True
|
| 511 |
)
|
| 512 |
-
return
|
| 513 |
-
|
| 514 |
-
history.add_user_message(prompt_text)
|
| 515 |
-
history.add_ai_message(response)
|
| 516 |
-
|
| 517 |
-
# The content has been generated as JSON
|
| 518 |
-
# There maybe trailing ``` at the end of the response -- remove them
|
| 519 |
-
# To be careful: ``` may be part of the content as well when code is generated
|
| 520 |
-
response = text_helper.get_clean_json(response)
|
| 521 |
-
logger.info(
|
| 522 |
-
'Cleaned JSON length: %d', len(response)
|
| 523 |
-
)
|
| 524 |
-
|
| 525 |
-
# Now create the PPT file
|
| 526 |
-
progress_bar.progress(
|
| 527 |
-
GlobalConfig.LLM_PROGRESS_MAX,
|
| 528 |
-
text='Finding photos online and generating the slide deck...'
|
| 529 |
-
)
|
| 530 |
-
progress_bar.progress(1.0, text='Done!')
|
| 531 |
-
st.chat_message('ai').code(response, language='json')
|
| 532 |
-
|
| 533 |
-
if path := generate_slide_deck(response):
|
| 534 |
-
_display_download_button(path)
|
| 535 |
-
|
| 536 |
-
logger.info(
|
| 537 |
-
'#messages in history / 2: %d',
|
| 538 |
-
len(st.session_state[CHAT_MESSAGES]) / 2
|
| 539 |
-
)
|
| 540 |
-
|
| 541 |
-
|
| 542 |
-
def generate_slide_deck(json_str: str) -> Union[pathlib.Path, None]:
|
| 543 |
-
"""
|
| 544 |
-
Create a slide deck and return the file path. In case there is any error creating the slide
|
| 545 |
-
deck, the path may be to an empty file.
|
| 546 |
-
|
| 547 |
-
:param json_str: The content in *valid* JSON format.
|
| 548 |
-
:return: The path to the .pptx file or `None` in case of error.
|
| 549 |
-
"""
|
| 550 |
-
|
| 551 |
-
try:
|
| 552 |
-
parsed_data = json5.loads(json_str)
|
| 553 |
-
except ValueError:
|
| 554 |
-
handle_error(
|
| 555 |
-
'Encountered error while parsing JSON...will fix it and retry',
|
| 556 |
-
True
|
| 557 |
-
)
|
| 558 |
-
try:
|
| 559 |
-
parsed_data = json5.loads(text_helper.fix_malformed_json(json_str))
|
| 560 |
-
except ValueError:
|
| 561 |
-
handle_error(
|
| 562 |
-
'Encountered an error again while fixing JSON...'
|
| 563 |
-
'the slide deck cannot be created, unfortunately βΉ'
|
| 564 |
-
'\nPlease try again later.',
|
| 565 |
-
True
|
| 566 |
-
)
|
| 567 |
-
return None
|
| 568 |
-
except RecursionError:
|
| 569 |
-
handle_error(
|
| 570 |
-
'Encountered a recursion error while parsing JSON...'
|
| 571 |
-
'the slide deck cannot be created, unfortunately βΉ'
|
| 572 |
-
'\nPlease try again later.',
|
| 573 |
-
True
|
| 574 |
-
)
|
| 575 |
-
return None
|
| 576 |
-
except Exception:
|
| 577 |
-
handle_error(
|
| 578 |
-
'Encountered an error while parsing JSON...'
|
| 579 |
-
'the slide deck cannot be created, unfortunately βΉ'
|
| 580 |
-
'\nPlease try again later.',
|
| 581 |
-
True
|
| 582 |
-
)
|
| 583 |
-
return None
|
| 584 |
-
|
| 585 |
-
if DOWNLOAD_FILE_KEY in st.session_state:
|
| 586 |
-
path = pathlib.Path(st.session_state[DOWNLOAD_FILE_KEY])
|
| 587 |
-
else:
|
| 588 |
-
temp = tempfile.NamedTemporaryFile(delete=False, suffix='.pptx')
|
| 589 |
-
path = pathlib.Path(temp.name)
|
| 590 |
-
st.session_state[DOWNLOAD_FILE_KEY] = str(path)
|
| 591 |
-
|
| 592 |
-
if temp:
|
| 593 |
-
temp.close()
|
| 594 |
-
|
| 595 |
-
try:
|
| 596 |
-
logger.debug('Creating PPTX file: %s...', st.session_state[DOWNLOAD_FILE_KEY])
|
| 597 |
-
pptx_helper.generate_powerpoint_presentation(
|
| 598 |
-
parsed_data,
|
| 599 |
-
slides_template=pptx_template,
|
| 600 |
-
output_file_path=path
|
| 601 |
-
)
|
| 602 |
-
except Exception as ex:
|
| 603 |
-
st.error(APP_TEXT['content_generation_error'])
|
| 604 |
-
logger.exception('Caught a generic exception: %s', str(ex))
|
| 605 |
-
|
| 606 |
-
return path
|
| 607 |
|
| 608 |
|
| 609 |
def _is_it_refinement() -> bool:
|
|
@@ -643,9 +507,6 @@ def _get_last_response() -> str:
|
|
| 643 |
:return: The response text.
|
| 644 |
"""
|
| 645 |
|
| 646 |
-
return st.session_state[CHAT_MESSAGES][-1].content
|
| 647 |
-
|
| 648 |
-
|
| 649 |
def _display_messages_history(view_messages: st.expander):
|
| 650 |
"""
|
| 651 |
Display the history of messages.
|
|
|
|
| 6 |
import os
|
| 7 |
import pathlib
|
| 8 |
import random
|
| 9 |
+
import sys
|
| 10 |
import tempfile
|
| 11 |
from typing import List, Union
|
| 12 |
|
|
|
|
| 18 |
import streamlit as st
|
| 19 |
from dotenv import load_dotenv
|
| 20 |
|
| 21 |
+
sys.path.insert(0, os.path.abspath('src'))
|
| 22 |
+
from slidedeckai.core import SlideDeckAI
|
| 23 |
+
from slidedeckai import global_config as gcfg
|
| 24 |
+
from slidedeckai.global_config import GlobalConfig
|
| 25 |
+
from slidedeckai.helpers import llm_helper, text_helper
|
| 26 |
+
import slidedeckai.helpers.file_manager as filem
|
| 27 |
+
from slidedeckai.helpers.chat_helper import ChatMessage, HumanMessage, AIMessage
|
| 28 |
+
from slidedeckai.helpers import chat_helper
|
| 29 |
|
| 30 |
load_dotenv()
|
| 31 |
|
| 32 |
+
class StreamlitChatMessageHistory:
|
| 33 |
+
"""Chat message history stored in Streamlit session state."""
|
| 34 |
+
|
| 35 |
+
def __init__(self, key: str):
|
| 36 |
+
self.key = key
|
| 37 |
+
if key not in st.session_state:
|
| 38 |
+
st.session_state[key] = []
|
| 39 |
+
|
| 40 |
+
@property
|
| 41 |
+
def messages(self):
|
| 42 |
+
return st.session_state[self.key]
|
| 43 |
+
|
| 44 |
+
def add_user_message(self, content: str):
|
| 45 |
+
st.session_state[self.key].append(HumanMessage(content))
|
| 46 |
+
|
| 47 |
+
def add_ai_message(self, content: str):
|
| 48 |
+
st.session_state[self.key].append(AIMessage(content))
|
| 49 |
+
|
| 50 |
RUN_IN_OFFLINE_MODE = os.getenv('RUN_IN_OFFLINE_MODE', 'False').lower() == 'true'
|
| 51 |
|
| 52 |
|
|
|
|
| 391 |
st.session_state[PDF_FILE_KEY],
|
| 392 |
(st.session_state['start_page'], st.session_state['end_page'])
|
| 393 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 394 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 395 |
st.chat_message('user').write(prompt_text)
|
| 396 |
|
| 397 |
+
slide_generator = SlideDeckAI(
|
| 398 |
+
model=llm_provider_to_use,
|
| 399 |
+
topic=prompt_text,
|
| 400 |
+
api_key=api_key_token.strip(),
|
| 401 |
+
template_idx=list(GlobalConfig.PPTX_TEMPLATE_FILES.keys()).index(pptx_template),
|
| 402 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 403 |
|
| 404 |
progress_bar = st.progress(0, 'Preparing to call LLM...')
|
| 405 |
+
|
| 406 |
+
def progress_callback(current_progress):
|
| 407 |
+
progress_bar.progress(min(current_progress / gcfg.get_max_output_tokens(llm_provider_to_use), 0.95), text='Streaming content...this might take a while...')
|
| 408 |
|
| 409 |
try:
|
| 410 |
+
if _is_it_refinement():
|
| 411 |
+
path = slide_generator.revise(instructions=prompt_text, progress_callback=progress_callback)
|
| 412 |
+
else:
|
| 413 |
+
path = slide_generator.generate(progress_callback=progress_callback)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 414 |
|
| 415 |
+
progress_bar.progress(1.0, text='Done!')
|
| 416 |
+
|
| 417 |
+
if path:
|
| 418 |
+
st.session_state[DOWNLOAD_FILE_KEY] = str(path)
|
| 419 |
+
history.add_user_message(prompt_text)
|
| 420 |
+
history.add_ai_message(slide_generator.last_response)
|
| 421 |
+
st.chat_message('ai').code(slide_generator.last_response, language='json')
|
| 422 |
+
_display_download_button(path)
|
| 423 |
+
else:
|
| 424 |
+
handle_error("Failed to generate slide deck.", True)
|
| 425 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 426 |
except (httpx.ConnectError, requests.exceptions.ConnectionError):
|
| 427 |
handle_error(
|
| 428 |
'A connection error occurred while streaming content from the LLM endpoint.'
|
|
|
|
| 431 |
' using Ollama, make sure that Ollama is already running on your system.',
|
| 432 |
True
|
| 433 |
)
|
|
|
|
| 434 |
except huggingface_hub.errors.ValidationError as ve:
|
| 435 |
handle_error(
|
| 436 |
f'An error occurred while trying to generate the content: {ve}'
|
| 437 |
'\nPlease try again with a significantly shorter input text.',
|
| 438 |
True
|
| 439 |
)
|
|
|
|
| 440 |
except ollama.ResponseError:
|
| 441 |
handle_error(
|
| 442 |
+
f'The model is unavailable with Ollama on your system.'
|
| 443 |
+
f' Make sure that you have provided the correct LLM name or pull it.'
|
| 444 |
+
f' View LLMs available locally by running `ollama list`.',
|
| 445 |
True
|
| 446 |
)
|
|
|
|
| 447 |
except Exception as ex:
|
| 448 |
_msg = str(ex)
|
| 449 |
if 'payment required' in _msg.lower():
|
|
|
|
| 468 |
' Read **[how to get free LLM API keys](https://github.com/barun-saha/slide-deck-ai?tab=readme-ov-file#summary-of-the-llms)**.',
|
| 469 |
True
|
| 470 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 471 |
|
| 472 |
|
| 473 |
def _is_it_refinement() -> bool:
|
|
|
|
| 507 |
:return: The response text.
|
| 508 |
"""
|
| 509 |
|
|
|
|
|
|
|
|
|
|
| 510 |
def _display_messages_history(view_messages: st.expander):
|
| 511 |
"""
|
| 512 |
Display the history of messages.
|
pyproject.toml
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[build-system]
|
| 2 |
+
requires = ["setuptools>=77.0.3"]
|
| 3 |
+
build-backend = "setuptools.build_meta"
|
| 4 |
+
|
| 5 |
+
[project]
|
| 6 |
+
name = "slidedeckai"
|
| 7 |
+
authors = [
|
| 8 |
+
{ name="Barun Saha", email="[email protected]" }
|
| 9 |
+
]
|
| 10 |
+
description = "A Python package to generate slide decks using AI."
|
| 11 |
+
readme = "README.md"
|
| 12 |
+
requires-python = ">=3.10"
|
| 13 |
+
classifiers = [
|
| 14 |
+
"Programming Language :: Python :: 3",
|
| 15 |
+
"License :: OSI Approved :: MIT License",
|
| 16 |
+
"Operating System :: OS Independent"
|
| 17 |
+
]
|
| 18 |
+
dynamic = ["dependencies", "version"]
|
| 19 |
+
|
| 20 |
+
[tool.setuptools]
|
| 21 |
+
package-dir = {"" = "src"}
|
| 22 |
+
include-package-data = true
|
| 23 |
+
|
| 24 |
+
[tool.setuptools.packages.find]
|
| 25 |
+
where = ["src"]
|
| 26 |
+
|
| 27 |
+
[tool.setuptools.dynamic]
|
| 28 |
+
dependencies = {file = ["requirements.txt"]}
|
| 29 |
+
version = {attr = "slidedeckai._version.__version__"}
|
| 30 |
+
|
| 31 |
+
[tool.setuptools.package-data]
|
| 32 |
+
slidedeckai = ["prompts/**/*.txt", "strings.json", "pptx_templates/*.pptx", "icons/png128/*.png", "icons/svg_repo.txt", "file_embeddings/*.npy"]
|
| 33 |
+
|
| 34 |
+
[project.urls]
|
| 35 |
+
"Homepage" = "https://github.com/barun-saha/slide-deck-ai"
|
| 36 |
+
"Bug Tracker" = "https://github.com/barun-saha/slide-deck-ai/issues"
|
| 37 |
+
|
| 38 |
+
[project.scripts]
|
| 39 |
+
slidedeckai = "slidedeckai.cli:main"
|
{helpers β src/slidedeckai}/__init__.py
RENAMED
|
File without changes
|
src/slidedeckai/_version.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
__version__ = "8.0.0"
|
src/slidedeckai/cli.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Command-line interface for SlideDeckAI.
|
| 3 |
+
"""
|
| 4 |
+
import argparse
|
| 5 |
+
from .core import SlideDeckAI
|
| 6 |
+
|
| 7 |
+
def main():
|
| 8 |
+
"""
|
| 9 |
+
The main function for the CLI.
|
| 10 |
+
"""
|
| 11 |
+
parser = argparse.ArgumentParser(description='Generate slide decks with SlideDeckAI.')
|
| 12 |
+
parser.add_argument('--model', required=True, help='The name of the LLM model to use.')
|
| 13 |
+
parser.add_argument('--topic', required=True, help='The topic of the slide deck.')
|
| 14 |
+
parser.add_argument('--api-key', help='The API key for the LLM provider.')
|
| 15 |
+
parser.add_argument('--template-id', type=int, default=0, help='The index of the PowerPoint template to use.')
|
| 16 |
+
parser.add_argument('--output-path', help='The path to save the generated .pptx file.')
|
| 17 |
+
args = parser.parse_args()
|
| 18 |
+
|
| 19 |
+
slide_generator = SlideDeckAI(
|
| 20 |
+
model=args.model,
|
| 21 |
+
topic=args.topic,
|
| 22 |
+
api_key=args.api_key,
|
| 23 |
+
template_idx=args.template_id,
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
pptx_path = slide_generator.generate()
|
| 27 |
+
|
| 28 |
+
if args.output_path:
|
| 29 |
+
import shutil
|
| 30 |
+
shutil.move(str(pptx_path), args.output_path)
|
| 31 |
+
print(f"Slide deck saved to {args.output_path}")
|
| 32 |
+
else:
|
| 33 |
+
print(f"Slide deck saved to {pptx_path}")
|
| 34 |
+
|
| 35 |
+
if __name__ == '__main__':
|
| 36 |
+
main()
|
src/slidedeckai/core.py
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Core classes for SlideDeckAI.
|
| 3 |
+
"""
|
| 4 |
+
import logging
|
| 5 |
+
import os
|
| 6 |
+
import pathlib
|
| 7 |
+
import tempfile
|
| 8 |
+
from typing import Union
|
| 9 |
+
|
| 10 |
+
import json5
|
| 11 |
+
from dotenv import load_dotenv
|
| 12 |
+
|
| 13 |
+
from . import global_config as gcfg
|
| 14 |
+
from .global_config import GlobalConfig
|
| 15 |
+
from .helpers import llm_helper, pptx_helper, text_helper
|
| 16 |
+
from .helpers.chat_helper import ChatMessageHistory
|
| 17 |
+
|
| 18 |
+
load_dotenv()
|
| 19 |
+
|
| 20 |
+
RUN_IN_OFFLINE_MODE = os.getenv('RUN_IN_OFFLINE_MODE', 'False').lower() == 'true'
|
| 21 |
+
|
| 22 |
+
logger = logging.getLogger(__name__)
|
| 23 |
+
|
| 24 |
+
class SlideDeckAI:
|
| 25 |
+
"""
|
| 26 |
+
The main class for generating slide decks.
|
| 27 |
+
"""
|
| 28 |
+
|
| 29 |
+
def __init__(self, model, topic, api_key=None, pdf_file_path=None, pdf_page_range=None, template_idx=0):
|
| 30 |
+
"""
|
| 31 |
+
Initializes the SlideDeckAI object.
|
| 32 |
+
|
| 33 |
+
:param model: The name of the LLM model to use.
|
| 34 |
+
:param topic: The topic of the slide deck.
|
| 35 |
+
:param api_key: The API key for the LLM provider.
|
| 36 |
+
:param pdf_file_path: The path to a PDF file to use as a source for the slide deck.
|
| 37 |
+
:param pdf_page_range: A tuple representing the page range to use from the PDF file.
|
| 38 |
+
:param template_idx: The index of the PowerPoint template to use.
|
| 39 |
+
"""
|
| 40 |
+
self.model = model
|
| 41 |
+
self.topic = topic
|
| 42 |
+
self.api_key = api_key
|
| 43 |
+
self.pdf_file_path = pdf_file_path
|
| 44 |
+
self.pdf_page_range = pdf_page_range
|
| 45 |
+
self.template_idx = template_idx
|
| 46 |
+
self.chat_history = ChatMessageHistory()
|
| 47 |
+
self.last_response = None
|
| 48 |
+
|
| 49 |
+
def _get_prompt_template(self, is_refinement: bool) -> str:
|
| 50 |
+
"""
|
| 51 |
+
Return a prompt template.
|
| 52 |
+
|
| 53 |
+
:param is_refinement: Whether this is the initial or refinement prompt.
|
| 54 |
+
:return: The prompt template as f-string.
|
| 55 |
+
"""
|
| 56 |
+
if is_refinement:
|
| 57 |
+
with open(GlobalConfig.REFINEMENT_PROMPT_TEMPLATE, 'r', encoding='utf-8') as in_file:
|
| 58 |
+
template = in_file.read()
|
| 59 |
+
else:
|
| 60 |
+
with open(GlobalConfig.INITIAL_PROMPT_TEMPLATE, 'r', encoding='utf-8') as in_file:
|
| 61 |
+
template = in_file.read()
|
| 62 |
+
return template
|
| 63 |
+
|
| 64 |
+
def generate(self, progress_callback=None):
|
| 65 |
+
"""
|
| 66 |
+
Generates the initial slide deck.
|
| 67 |
+
:return: The path to the generated .pptx file.
|
| 68 |
+
"""
|
| 69 |
+
self.chat_history.add_user_message(self.topic)
|
| 70 |
+
prompt_template = self._get_prompt_template(is_refinement=False)
|
| 71 |
+
formatted_template = prompt_template.format(question=self.topic, additional_info='')
|
| 72 |
+
|
| 73 |
+
provider, llm_name = llm_helper.get_provider_model(self.model, use_ollama=RUN_IN_OFFLINE_MODE)
|
| 74 |
+
|
| 75 |
+
llm = llm_helper.get_litellm_llm(
|
| 76 |
+
provider=provider,
|
| 77 |
+
model=llm_name,
|
| 78 |
+
max_new_tokens=gcfg.get_max_output_tokens(self.model),
|
| 79 |
+
api_key=self.api_key,
|
| 80 |
+
)
|
| 81 |
+
|
| 82 |
+
response = ""
|
| 83 |
+
for chunk in llm.stream(formatted_template):
|
| 84 |
+
if isinstance(chunk, str):
|
| 85 |
+
response += chunk
|
| 86 |
+
else:
|
| 87 |
+
content = getattr(chunk, 'content', None)
|
| 88 |
+
if content is not None:
|
| 89 |
+
response += content
|
| 90 |
+
else:
|
| 91 |
+
response += str(chunk)
|
| 92 |
+
if progress_callback:
|
| 93 |
+
progress_callback(len(response))
|
| 94 |
+
|
| 95 |
+
self.last_response = text_helper.get_clean_json(response)
|
| 96 |
+
self.chat_history.add_ai_message(self.last_response)
|
| 97 |
+
|
| 98 |
+
return self._generate_slide_deck(self.last_response)
|
| 99 |
+
|
| 100 |
+
def revise(self, instructions, progress_callback=None):
|
| 101 |
+
"""
|
| 102 |
+
Revises the slide deck with new instructions.
|
| 103 |
+
|
| 104 |
+
:param instructions: The instructions for revising the slide deck.
|
| 105 |
+
:return: The path to the revised .pptx file.
|
| 106 |
+
"""
|
| 107 |
+
if not self.last_response:
|
| 108 |
+
raise ValueError("You must generate a slide deck before you can revise it.")
|
| 109 |
+
|
| 110 |
+
if len(self.chat_history.messages) >= 16:
|
| 111 |
+
raise ValueError("Chat history is full. Please reset to continue.")
|
| 112 |
+
|
| 113 |
+
self.chat_history.add_user_message(instructions)
|
| 114 |
+
|
| 115 |
+
prompt_template = self._get_prompt_template(is_refinement=True)
|
| 116 |
+
|
| 117 |
+
list_of_msgs = [f'{idx + 1}. {msg.content}' for idx, msg in enumerate(self.chat_history.messages) if msg.role == 'user']
|
| 118 |
+
|
| 119 |
+
formatted_template = prompt_template.format(
|
| 120 |
+
instructions='\n'.join(list_of_msgs),
|
| 121 |
+
previous_content=self.last_response,
|
| 122 |
+
additional_info='',
|
| 123 |
+
)
|
| 124 |
+
|
| 125 |
+
provider, llm_name = llm_helper.get_provider_model(self.model, use_ollama=RUN_IN_OFFLINE_MODE)
|
| 126 |
+
|
| 127 |
+
llm = llm_helper.get_litellm_llm(
|
| 128 |
+
provider=provider,
|
| 129 |
+
model=llm_name,
|
| 130 |
+
max_new_tokens=gcfg.get_max_output_tokens(self.model),
|
| 131 |
+
api_key=self.api_key,
|
| 132 |
+
)
|
| 133 |
+
|
| 134 |
+
response = ""
|
| 135 |
+
for chunk in llm.stream(formatted_template):
|
| 136 |
+
if isinstance(chunk, str):
|
| 137 |
+
response += chunk
|
| 138 |
+
else:
|
| 139 |
+
content = getattr(chunk, 'content', None)
|
| 140 |
+
if content is not None:
|
| 141 |
+
response += content
|
| 142 |
+
else:
|
| 143 |
+
response += str(chunk)
|
| 144 |
+
if progress_callback:
|
| 145 |
+
progress_callback(len(response))
|
| 146 |
+
|
| 147 |
+
self.last_response = text_helper.get_clean_json(response)
|
| 148 |
+
self.chat_history.add_ai_message(self.last_response)
|
| 149 |
+
|
| 150 |
+
return self._generate_slide_deck(self.last_response)
|
| 151 |
+
|
| 152 |
+
def _generate_slide_deck(self, json_str: str) -> Union[pathlib.Path, None]:
|
| 153 |
+
"""
|
| 154 |
+
Create a slide deck and return the file path.
|
| 155 |
+
|
| 156 |
+
:param json_str: The content in *valid* JSON format.
|
| 157 |
+
:return: The path to the .pptx file or `None` in case of error.
|
| 158 |
+
"""
|
| 159 |
+
try:
|
| 160 |
+
parsed_data = json5.loads(json_str)
|
| 161 |
+
except (ValueError, RecursionError) as e:
|
| 162 |
+
logger.error("Error parsing JSON: %s", e)
|
| 163 |
+
try:
|
| 164 |
+
parsed_data = json5.loads(text_helper.fix_malformed_json(json_str))
|
| 165 |
+
except (ValueError, RecursionError) as e2:
|
| 166 |
+
logger.error("Error parsing fixed JSON: %s", e2)
|
| 167 |
+
return None
|
| 168 |
+
|
| 169 |
+
temp = tempfile.NamedTemporaryFile(delete=False, suffix='.pptx')
|
| 170 |
+
path = pathlib.Path(temp.name)
|
| 171 |
+
temp.close()
|
| 172 |
+
|
| 173 |
+
try:
|
| 174 |
+
pptx_helper.generate_powerpoint_presentation(
|
| 175 |
+
parsed_data,
|
| 176 |
+
slides_template=list(GlobalConfig.PPTX_TEMPLATE_FILES.keys())[self.template_idx],
|
| 177 |
+
output_file_path=path
|
| 178 |
+
)
|
| 179 |
+
except Exception as ex:
|
| 180 |
+
logger.exception('Caught a generic exception: %s', str(ex))
|
| 181 |
+
return None
|
| 182 |
+
|
| 183 |
+
return path
|
| 184 |
+
|
| 185 |
+
def set_template(self, idx):
|
| 186 |
+
"""
|
| 187 |
+
Sets the PowerPoint template to use.
|
| 188 |
+
|
| 189 |
+
:param idx: The index of the template to use.
|
| 190 |
+
"""
|
| 191 |
+
self.template_idx = idx
|
| 192 |
+
|
| 193 |
+
def reset(self):
|
| 194 |
+
"""
|
| 195 |
+
Resets the chat history.
|
| 196 |
+
"""
|
| 197 |
+
self.chat_history = ChatMessageHistory()
|
| 198 |
+
self.last_response = None
|
{file_embeddings β src/slidedeckai/file_embeddings}/embeddings.npy
RENAMED
|
File without changes
|
{file_embeddings β src/slidedeckai/file_embeddings}/icons.npy
RENAMED
|
File without changes
|
global_config.py β src/slidedeckai/global_config.py
RENAMED
|
@@ -4,6 +4,7 @@ A set of configurations used by the app.
|
|
| 4 |
import logging
|
| 5 |
import os
|
| 6 |
import re
|
|
|
|
| 7 |
|
| 8 |
from dataclasses import dataclass
|
| 9 |
from dotenv import load_dotenv
|
|
@@ -11,6 +12,7 @@ from dotenv import load_dotenv
|
|
| 11 |
|
| 12 |
load_dotenv()
|
| 13 |
|
|
|
|
| 14 |
|
| 15 |
@dataclass(frozen=True)
|
| 16 |
class GlobalConfig:
|
|
@@ -128,32 +130,32 @@ class GlobalConfig:
|
|
| 128 |
|
| 129 |
LOG_LEVEL = 'DEBUG'
|
| 130 |
COUNT_TOKENS = False
|
| 131 |
-
APP_STRINGS_FILE = 'strings.json'
|
| 132 |
-
PRELOAD_DATA_FILE = 'examples/example_02.json'
|
| 133 |
-
INITIAL_PROMPT_TEMPLATE = 'prompts/initial_template_v4_two_cols_img.txt'
|
| 134 |
-
REFINEMENT_PROMPT_TEMPLATE = 'prompts/refinement_template_v4_two_cols_img.txt'
|
| 135 |
|
| 136 |
LLM_PROGRESS_MAX = 90
|
| 137 |
-
ICONS_DIR = 'icons/png128/'
|
| 138 |
TINY_BERT_MODEL = 'gaunernst/bert-mini-uncased'
|
| 139 |
-
EMBEDDINGS_FILE_NAME = 'file_embeddings/embeddings.npy'
|
| 140 |
-
ICONS_FILE_NAME = 'file_embeddings/icons.npy'
|
| 141 |
|
| 142 |
PPTX_TEMPLATE_FILES = {
|
| 143 |
'Basic': {
|
| 144 |
-
'file': 'pptx_templates/Blank.pptx',
|
| 145 |
'caption': 'A good start (Uses [photos](https://unsplash.com/photos/AFZ-qBPEceA) by [cetteup](https://unsplash.com/@cetteup?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash) on [Unsplash](https://unsplash.com/photos/a-foggy-forest-filled-with-lots-of-trees-d3ci37Gcgxg?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash)) π§'
|
| 146 |
},
|
| 147 |
'Ion Boardroom': {
|
| 148 |
-
'file': 'pptx_templates/Ion_Boardroom.pptx',
|
| 149 |
'caption': 'Make some bold decisions π₯'
|
| 150 |
},
|
| 151 |
'Minimalist Sales Pitch': {
|
| 152 |
-
'file': 'pptx_templates/Minimalist_sales_pitch.pptx',
|
| 153 |
'caption': 'In high contrast β¬'
|
| 154 |
},
|
| 155 |
'Urban Monochrome': {
|
| 156 |
-
'file': 'pptx_templates/Urban_monochrome.pptx',
|
| 157 |
'caption': 'Marvel in a monochrome dream β¬'
|
| 158 |
},
|
| 159 |
}
|
|
|
|
| 4 |
import logging
|
| 5 |
import os
|
| 6 |
import re
|
| 7 |
+
from pathlib import Path
|
| 8 |
|
| 9 |
from dataclasses import dataclass
|
| 10 |
from dotenv import load_dotenv
|
|
|
|
| 12 |
|
| 13 |
load_dotenv()
|
| 14 |
|
| 15 |
+
_SRC_DIR = Path(__file__).resolve().parent
|
| 16 |
|
| 17 |
@dataclass(frozen=True)
|
| 18 |
class GlobalConfig:
|
|
|
|
| 130 |
|
| 131 |
LOG_LEVEL = 'DEBUG'
|
| 132 |
COUNT_TOKENS = False
|
| 133 |
+
APP_STRINGS_FILE = _SRC_DIR / 'strings.json'
|
| 134 |
+
PRELOAD_DATA_FILE = _SRC_DIR / 'examples/example_02.json'
|
| 135 |
+
INITIAL_PROMPT_TEMPLATE = _SRC_DIR / 'prompts/initial_template_v4_two_cols_img.txt'
|
| 136 |
+
REFINEMENT_PROMPT_TEMPLATE = _SRC_DIR / 'prompts/refinement_template_v4_two_cols_img.txt'
|
| 137 |
|
| 138 |
LLM_PROGRESS_MAX = 90
|
| 139 |
+
ICONS_DIR = _SRC_DIR / 'icons/png128/'
|
| 140 |
TINY_BERT_MODEL = 'gaunernst/bert-mini-uncased'
|
| 141 |
+
EMBEDDINGS_FILE_NAME = _SRC_DIR / 'file_embeddings/embeddings.npy'
|
| 142 |
+
ICONS_FILE_NAME = _SRC_DIR / 'file_embeddings/icons.npy'
|
| 143 |
|
| 144 |
PPTX_TEMPLATE_FILES = {
|
| 145 |
'Basic': {
|
| 146 |
+
'file': _SRC_DIR / 'pptx_templates/Blank.pptx',
|
| 147 |
'caption': 'A good start (Uses [photos](https://unsplash.com/photos/AFZ-qBPEceA) by [cetteup](https://unsplash.com/@cetteup?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash) on [Unsplash](https://unsplash.com/photos/a-foggy-forest-filled-with-lots-of-trees-d3ci37Gcgxg?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash)) π§'
|
| 148 |
},
|
| 149 |
'Ion Boardroom': {
|
| 150 |
+
'file': _SRC_DIR / 'pptx_templates/Ion_Boardroom.pptx',
|
| 151 |
'caption': 'Make some bold decisions π₯'
|
| 152 |
},
|
| 153 |
'Minimalist Sales Pitch': {
|
| 154 |
+
'file': _SRC_DIR / 'pptx_templates/Minimalist_sales_pitch.pptx',
|
| 155 |
'caption': 'In high contrast β¬'
|
| 156 |
},
|
| 157 |
'Urban Monochrome': {
|
| 158 |
+
'file': _SRC_DIR / 'pptx_templates/Urban_monochrome.pptx',
|
| 159 |
'caption': 'Marvel in a monochrome dream β¬'
|
| 160 |
},
|
| 161 |
}
|
src/slidedeckai/helpers/__init__.py
ADDED
|
File without changes
|
{helpers β src/slidedeckai/helpers}/chat_helper.py
RENAMED
|
@@ -27,24 +27,17 @@ class AIMessage(ChatMessage):
|
|
| 27 |
super().__init__(content, 'ai')
|
| 28 |
|
| 29 |
|
| 30 |
-
class
|
| 31 |
-
"""Chat message history stored in
|
| 32 |
|
| 33 |
-
def __init__(self
|
| 34 |
-
self.
|
| 35 |
-
if key not in st.session_state:
|
| 36 |
-
st.session_state[key] = []
|
| 37 |
-
|
| 38 |
-
@property
|
| 39 |
-
def messages(self):
|
| 40 |
-
return st.session_state[self.key]
|
| 41 |
|
| 42 |
def add_user_message(self, content: str):
|
| 43 |
-
|
| 44 |
|
| 45 |
def add_ai_message(self, content: str):
|
| 46 |
-
|
| 47 |
-
|
| 48 |
|
| 49 |
class ChatPromptTemplate:
|
| 50 |
"""Template for chat prompts."""
|
|
|
|
| 27 |
super().__init__(content, 'ai')
|
| 28 |
|
| 29 |
|
| 30 |
+
class ChatMessageHistory:
|
| 31 |
+
"""Chat message history stored in a list."""
|
| 32 |
|
| 33 |
+
def __init__(self):
|
| 34 |
+
self.messages = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
|
| 36 |
def add_user_message(self, content: str):
|
| 37 |
+
self.messages.append(HumanMessage(content))
|
| 38 |
|
| 39 |
def add_ai_message(self, content: str):
|
| 40 |
+
self.messages.append(AIMessage(content))
|
|
|
|
| 41 |
|
| 42 |
class ChatPromptTemplate:
|
| 43 |
"""Template for chat prompts."""
|
{helpers β src/slidedeckai/helpers}/file_manager.py
RENAMED
|
@@ -8,10 +8,7 @@ import sys
|
|
| 8 |
import streamlit as st
|
| 9 |
from pypdf import PdfReader
|
| 10 |
|
| 11 |
-
|
| 12 |
-
sys.path.append('../..')
|
| 13 |
-
|
| 14 |
-
from global_config import GlobalConfig
|
| 15 |
|
| 16 |
|
| 17 |
logger = logging.getLogger(__name__)
|
|
|
|
| 8 |
import streamlit as st
|
| 9 |
from pypdf import PdfReader
|
| 10 |
|
| 11 |
+
from ..global_config import GlobalConfig
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
|
| 14 |
logger = logging.getLogger(__name__)
|
{helpers β src/slidedeckai/helpers}/icons_embeddings.py
RENAMED
|
@@ -11,10 +11,7 @@ import numpy as np
|
|
| 11 |
from sklearn.metrics.pairwise import cosine_similarity
|
| 12 |
from transformers import BertTokenizer, BertModel
|
| 13 |
|
| 14 |
-
|
| 15 |
-
sys.path.append('../..')
|
| 16 |
-
|
| 17 |
-
from global_config import GlobalConfig
|
| 18 |
|
| 19 |
|
| 20 |
tokenizer = BertTokenizer.from_pretrained(GlobalConfig.TINY_BERT_MODEL)
|
|
@@ -28,9 +25,9 @@ def get_icons_list() -> List[str]:
|
|
| 28 |
:return: The icons file names.
|
| 29 |
"""
|
| 30 |
|
| 31 |
-
items =
|
| 32 |
items = [
|
| 33 |
-
|
| 34 |
]
|
| 35 |
|
| 36 |
return items
|
|
|
|
| 11 |
from sklearn.metrics.pairwise import cosine_similarity
|
| 12 |
from transformers import BertTokenizer, BertModel
|
| 13 |
|
| 14 |
+
from ..global_config import GlobalConfig
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
|
| 17 |
tokenizer = BertTokenizer.from_pretrained(GlobalConfig.TINY_BERT_MODEL)
|
|
|
|
| 25 |
:return: The icons file names.
|
| 26 |
"""
|
| 27 |
|
| 28 |
+
items = GlobalConfig.ICONS_DIR.glob('*.png')
|
| 29 |
items = [
|
| 30 |
+
item.stem for item in items
|
| 31 |
]
|
| 32 |
|
| 33 |
return items
|
{helpers β src/slidedeckai/helpers}/image_search.py
RENAMED
|
File without changes
|
{helpers β src/slidedeckai/helpers}/llm_helper.py
RENAMED
|
@@ -8,9 +8,7 @@ import urllib3
|
|
| 8 |
from typing import Tuple, Union, Iterator, Optional
|
| 9 |
|
| 10 |
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
from global_config import GlobalConfig
|
| 14 |
|
| 15 |
try:
|
| 16 |
import litellm
|
|
|
|
| 8 |
from typing import Tuple, Union, Iterator, Optional
|
| 9 |
|
| 10 |
|
| 11 |
+
from ..global_config import GlobalConfig
|
|
|
|
|
|
|
| 12 |
|
| 13 |
try:
|
| 14 |
import litellm
|
{helpers β src/slidedeckai/helpers}/pptx_helper.py
RENAMED
|
@@ -16,12 +16,9 @@ from dotenv import load_dotenv
|
|
| 16 |
from pptx.enum.shapes import MSO_AUTO_SHAPE_TYPE
|
| 17 |
from pptx.shapes.placeholder import PicturePlaceholder, SlidePlaceholder
|
| 18 |
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
import helpers.icons_embeddings as ice
|
| 23 |
-
import helpers.image_search as ims
|
| 24 |
-
from global_config import GlobalConfig
|
| 25 |
|
| 26 |
|
| 27 |
load_dotenv()
|
|
|
|
| 16 |
from pptx.enum.shapes import MSO_AUTO_SHAPE_TYPE
|
| 17 |
from pptx.shapes.placeholder import PicturePlaceholder, SlidePlaceholder
|
| 18 |
|
| 19 |
+
from . import icons_embeddings as ice
|
| 20 |
+
from . import image_search as ims
|
| 21 |
+
from ..global_config import GlobalConfig
|
|
|
|
|
|
|
|
|
|
| 22 |
|
| 23 |
|
| 24 |
load_dotenv()
|
{helpers β src/slidedeckai/helpers}/text_helper.py
RENAMED
|
File without changes
|
{icons β src/slidedeckai/icons}/png128/0-circle.png
RENAMED
|
File without changes
|
{icons β src/slidedeckai/icons}/png128/1-circle.png
RENAMED
|
File without changes
|
{icons β src/slidedeckai/icons}/png128/123.png
RENAMED
|
File without changes
|
{icons β src/slidedeckai/icons}/png128/2-circle.png
RENAMED
|
File without changes
|
{icons β src/slidedeckai/icons}/png128/3-circle.png
RENAMED
|
File without changes
|
{icons β src/slidedeckai/icons}/png128/4-circle.png
RENAMED
|
File without changes
|
{icons β src/slidedeckai/icons}/png128/5-circle.png
RENAMED
|
File without changes
|
{icons β src/slidedeckai/icons}/png128/6-circle.png
RENAMED
|
File without changes
|
{icons β src/slidedeckai/icons}/png128/7-circle.png
RENAMED
|
File without changes
|
{icons β src/slidedeckai/icons}/png128/8-circle.png
RENAMED
|
File without changes
|
{icons β src/slidedeckai/icons}/png128/9-circle.png
RENAMED
|
File without changes
|
{icons β src/slidedeckai/icons}/png128/activity.png
RENAMED
|
File without changes
|
{icons β src/slidedeckai/icons}/png128/airplane.png
RENAMED
|
File without changes
|
{icons β src/slidedeckai/icons}/png128/alarm.png
RENAMED
|
File without changes
|
{icons β src/slidedeckai/icons}/png128/alien-head.png
RENAMED
|
File without changes
|
{icons β src/slidedeckai/icons}/png128/alphabet.png
RENAMED
|
File without changes
|
{icons β src/slidedeckai/icons}/png128/amazon.png
RENAMED
|
File without changes
|
{icons β src/slidedeckai/icons}/png128/amritsar-golden-temple.png
RENAMED
|
File without changes
|
{icons β src/slidedeckai/icons}/png128/amsterdam-canal.png
RENAMED
|
File without changes
|
{icons β src/slidedeckai/icons}/png128/amsterdam-windmill.png
RENAMED
|
File without changes
|
{icons β src/slidedeckai/icons}/png128/android.png
RENAMED
|
File without changes
|
{icons β src/slidedeckai/icons}/png128/angkor-wat.png
RENAMED
|
File without changes
|
{icons β src/slidedeckai/icons}/png128/apple.png
RENAMED
|
File without changes
|
{icons β src/slidedeckai/icons}/png128/archive.png
RENAMED
|
File without changes
|
{icons β src/slidedeckai/icons}/png128/argentina-obelisk.png
RENAMED
|
File without changes
|
{icons β src/slidedeckai/icons}/png128/artificial-intelligence-brain.png
RENAMED
|
File without changes
|
{icons β src/slidedeckai/icons}/png128/atlanta.png
RENAMED
|
File without changes
|
{icons β src/slidedeckai/icons}/png128/austin.png
RENAMED
|
File without changes
|
{icons β src/slidedeckai/icons}/png128/automation-decision.png
RENAMED
|
File without changes
|
{icons β src/slidedeckai/icons}/png128/award.png
RENAMED
|
File without changes
|
{icons β src/slidedeckai/icons}/png128/balloon.png
RENAMED
|
File without changes
|
{icons β src/slidedeckai/icons}/png128/ban.png
RENAMED
|
File without changes
|