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