diff --git a/README.md b/README.md index 6ba8804d..1f081cee 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,14 @@ pip install -r requirements.txt streamlit run app/Home.py +## PDF export +Install wkhtmltopdf to be able to generate the final story in PDF: + +Windows: [Download wkhtmltopdf installer](https://wkhtmltopdf.org/downloads.html) + +Linux: `sudo apt-get install wkhtmltopdf` + +MacOS: `brew install homebrew/cask/wkhtmltopdf` # Project diff --git a/app/util/download_pdf.py b/app/util/download_pdf.py new file mode 100644 index 00000000..40edee1e --- /dev/null +++ b/app/util/download_pdf.py @@ -0,0 +1,28 @@ +import tempfile +import markdown2 +import pdfkit +from util.wkhtmltopdf import config_pdfkit, pdfkit_options +import streamlit as st + +css = '''body { + font-family: 'helvetica'; +} +''' +#itk-label +text_label = 'Report generated using Intelligence Toolkit (https://aka.ms/itk)' + +def add_download_pdf(name, text, button_text='Download PDF', is_markdown=True, disabled=False): + if not name.endswith('.pdf'): + name += '.pdf' + text = f'{text}
{text_label}' + # Convert text to HTML if it's in Markdown format + text = markdown2.markdown(text) if is_markdown else text + + # Generate PDF from HTML string + config_pdf = config_pdfkit() + with tempfile.NamedTemporaryFile(suffix='.pdf', delete=False) as temp_file: + pdfkit.from_string(text, temp_file.name, options=pdfkit_options, configuration=config_pdf) + + # Provide download button for the generated PDF + with open(temp_file.name, 'rb') as f: + st.download_button(button_text, f, file_name=name, mime='application/pdf', disabled=disabled) diff --git a/app/util/wkhtmltopdf.py b/app/util/wkhtmltopdf.py new file mode 100644 index 00000000..2f63f820 --- /dev/null +++ b/app/util/wkhtmltopdf.py @@ -0,0 +1,27 @@ +import os +import pdfkit + +# Specify the name of the executable +executable = 'wkhtmltopdf' + +# Check if the executable is in the system PATH +def is_in_path(executable): + for path in os.environ["PATH"].split(os.pathsep): + if os.path.exists(os.path.join(path.strip('"'), executable)): + return True + return False + +def config_pdfkit(): + path_wkhtmltopdf = 'C:\\Program Files\\wkhtmltopdf\\bin\\wkhtmltopdf.exe' + # Verify if wkhtmltopdf is in PATH + if is_in_path(executable): + path_wkhtmltopdf='' + else: + path_wkhtmltopdf = 'C:\\Program Files\\wkhtmltopdf\\bin\\wkhtmltopdf.exe' + + return pdfkit.configuration(wkhtmltopdf=path_wkhtmltopdf) + +pdfkit_options = { + 'encoding': 'UTF-8', + 'enable-local-file-access': True, +} \ No newline at end of file diff --git a/app/workflows/attribute_patterns/workflow.py b/app/workflows/attribute_patterns/workflow.py index a177f0d7..e84c229b 100644 --- a/app/workflows/attribute_patterns/workflow.py +++ b/app/workflows/attribute_patterns/workflow.py @@ -10,13 +10,10 @@ GridUpdateMode, ColumnsAutoSizeMode ) - +from util.download_pdf import add_download_pdf import workflows.attribute_patterns.functions as functions import workflows.attribute_patterns.classes as classes -import workflows.attribute_patterns.config as config -import workflows.attribute_patterns.prompts as prompts import workflows.attribute_patterns.variables as vars - import workflows.attribute_patterns.config as config import util.AI_API @@ -176,5 +173,9 @@ def create(): prefix='' ) sv.attribute_report.value = result - report_placeholder.markdown(sv.attribute_report.value) - st.download_button('Download AI pattern report', data=sv.attribute_report.value, file_name='attribute_pattern_report.md', mime='text/markdown', disabled=sv.attribute_report.value == '') + report_data = sv.attribute_report.value + report_placeholder.markdown(report_data) + is_download_disabled = report_data == '' + name = 'attribute_pattern_report' + add_download_pdf(f'{name}.pdf', report_data, 'Download PDF report', disabled=is_download_disabled) + st.download_button('Download markdown report', data=report_data, file_name=f'{name}.md', mime='text/markdown', disabled=is_download_disabled) \ No newline at end of file diff --git a/app/workflows/group_narratives/workflow.py b/app/workflows/group_narratives/workflow.py index f26c2857..d63708b6 100644 --- a/app/workflows/group_narratives/workflow.py +++ b/app/workflows/group_narratives/workflow.py @@ -1,6 +1,7 @@ import streamlit as st import pandas as pd +from util.download_pdf import add_download_pdf import workflows.group_narratives.config as config import workflows.group_narratives.variables as vars @@ -200,4 +201,8 @@ def create(): ) sv.narrative_report.value = result narrative_placeholder.markdown(sv.narrative_report.value) - st.download_button('Download AI group report', data=sv.narrative_report.value, file_name='narrative_report.md', mime='text/markdown', disabled=sv.narrative_report.value == '') \ No newline at end of file + report_data = sv.narrative_report.value + is_download_disabled = report_data == '' + reports_name = 'narrative_report' + add_download_pdf(f'{reports_name}.pdf', report_data, button_text='Download PDF report', disabled=is_download_disabled) + st.download_button('Download markdown report', data=report_data, file_name=f'{reports_name}.md', mime='text/markdown', disabled=is_download_disabled) \ No newline at end of file diff --git a/app/workflows/question_answering/workflow.py b/app/workflows/question_answering/workflow.py index 8daac1af..443f223f 100644 --- a/app/workflows/question_answering/workflow.py +++ b/app/workflows/question_answering/workflow.py @@ -5,6 +5,7 @@ import json import scipy.spatial.distance +from util.download_pdf import add_download_pdf import workflows.question_answering.functions as functions import workflows.question_answering.classes as classes import workflows.question_answering.config as config @@ -217,6 +218,10 @@ def create(): ) sv.answering_lazy_answer_text.value = result report_placeholder.markdown(sv.answering_lazy_answer_text.value) - + report_data = sv.answering_lazy_answer_text.value + is_download_disabled = report_data == '' + name = sv.answering_lazy_answer_text.value.split('\n')[0].replace('#','').strip().replace(' ', '_') full_text = sv.answering_lazy_answer_text.value + '\n\n## Supporting FAQ\n\n' + re.sub(r' Q[\d]+: ', ' ', '\n\n'.join(sv.answering_matches.value.split('\n\n')[2:]), re.MULTILINE).replace('###### ', '### ') - st.download_button('Download AI answer report', data=full_text, file_name=sv.answering_lazy_answer_text.value.split('\n')[0].replace('#','').strip().replace(' ', '_')+'.md', mime='text/markdown', disabled=sv.answering_lazy_answer_text.value == '', key='lazy_download_button') + + add_download_pdf(f'{name}.pdf', full_text, 'Download PDF report', disabled=is_download_disabled) + st.download_button('Download markdown report', data=full_text, file_name=f'{name}.md', mime='text/markdown', disabled=sv.answering_lazy_answer_text.value == '', key='lazy_download_button') diff --git a/app/workflows/risk_networks/workflow.py b/app/workflows/risk_networks/workflow.py index 06cfb6ae..8856fc5b 100644 --- a/app/workflows/risk_networks/workflow.py +++ b/app/workflows/risk_networks/workflow.py @@ -1,7 +1,6 @@ import streamlit as st import pandas as pd import networkx as nx -import numpy as np from collections import defaultdict from sklearn.neighbors import NearestNeighbors @@ -15,9 +14,9 @@ DataReturnMode, GridOptionsBuilder, GridUpdateMode, - ColumnsAutoSizeMode ) +from util.download_pdf import add_download_pdf import workflows.risk_networks.functions as functions import workflows.risk_networks.classes as classes import workflows.risk_networks.config as config @@ -508,4 +507,10 @@ def create(): ) sv.network_report.value = result report_placeholder.markdown(sv.network_report.value) - st.download_button('Download AI network report', data=sv.network_report.value, file_name='network_report.md', mime='text/markdown', disabled=sv.network_report.value == '') + + report_data = sv.network_report.value + is_download_disabled = report_data == '' + name = 'network_report' + + add_download_pdf(f'{name}.pdf', report_data, 'Download PDF report', disabled=is_download_disabled) + st.download_button('Download AI network report', data=report_data, file_name=f'{name}.md', mime='text/markdown', disabled=is_download_disabled) diff --git a/requirements.txt b/requirements.txt index 9339d088..48ca9c8d 100644 Binary files a/requirements.txt and b/requirements.txt differ