Create app.py
Browse files
app.py
ADDED
@@ -0,0 +1,604 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from typing import List, Tuple, TypedDict
|
2 |
+
import os
|
3 |
+
|
4 |
+
import gradio as gr
|
5 |
+
from docx import Document
|
6 |
+
from docx.shared import Pt
|
7 |
+
from fpdf import FPDF
|
8 |
+
|
9 |
+
from .editor import create_editable_section, update_highlighted_text
|
10 |
+
from .services import (
|
11 |
+
organization_pair_autofill,
|
12 |
+
cost_estimator,
|
13 |
+
draft_letter,
|
14 |
+
help_mission_statement,
|
15 |
+
send_feedback
|
16 |
+
)
|
17 |
+
from .constants import JS, CATEGORIES
|
18 |
+
|
19 |
+
|
20 |
+
class LoggedComponents(TypedDict):
|
21 |
+
context: List[gr.components.Component]
|
22 |
+
letter: List[gr.components.Component]
|
23 |
+
relevance: gr.components.Component
|
24 |
+
coherence: gr.components.Component
|
25 |
+
fluency: gr.components.Component
|
26 |
+
overall_quality: gr.components.Component
|
27 |
+
comments: gr.components.Component
|
28 |
+
email: gr.components.Component
|
29 |
+
|
30 |
+
|
31 |
+
def update_links(selected_categories: List[str]) -> str:
|
32 |
+
"""Generates links of verified data sources of selected categories
|
33 |
+
|
34 |
+
Parameters
|
35 |
+
----------
|
36 |
+
selected_categories : List[str]
|
37 |
+
One or more categories of interest
|
38 |
+
|
39 |
+
Returns
|
40 |
+
-------
|
41 |
+
str
|
42 |
+
Formatted links for display
|
43 |
+
"""
|
44 |
+
all_links = ""
|
45 |
+
for category in selected_categories:
|
46 |
+
links = CATEGORIES.get(category, f"No resources available for {category}.")
|
47 |
+
all_links += f"### {category}\n" + links + "\n\n"
|
48 |
+
return all_links
|
49 |
+
|
50 |
+
|
51 |
+
def generate_pdf(*sections: str) -> str:
|
52 |
+
"""Generates a downloadable PDF of LOI
|
53 |
+
|
54 |
+
Returns
|
55 |
+
-------
|
56 |
+
str
|
57 |
+
Path to the saved PDF
|
58 |
+
"""
|
59 |
+
pdf = FPDF()
|
60 |
+
# pdf.set_auto_page_break(0)
|
61 |
+
pdf.add_page()
|
62 |
+
|
63 |
+
root = os.path.dirname(os.path.abspath(__file__))
|
64 |
+
pdf.add_font(family="Arial", fname=os.path.join(root, "ARIAL.TTF"))
|
65 |
+
pdf.set_font("Arial", size=12)
|
66 |
+
line_height = pdf.font_size * 1.2
|
67 |
+
|
68 |
+
pdf.multi_cell(w=0, h=line_height, text="[Your name]\n")
|
69 |
+
pdf.multi_cell(w=0, h=line_height, text="[Organization name]\n")
|
70 |
+
pdf.multi_cell(w=0, h=line_height, text="[Address]\n")
|
71 |
+
pdf.ln(line_height)
|
72 |
+
pdf.multi_cell(w=0, h=line_height, text="[Today's date]\n")
|
73 |
+
pdf.ln(line_height)
|
74 |
+
pdf.multi_cell(w=0, h=line_height, text="[Funder contact name]\n")
|
75 |
+
pdf.multi_cell(w=0, h=line_height, text="[Funder organization name]\n")
|
76 |
+
pdf.multi_cell(w=0, h=line_height, text="[Address]\n")
|
77 |
+
pdf.ln(line_height)
|
78 |
+
pdf.multi_cell(w=0, h=line_height, text="Re: [Project title]\n")
|
79 |
+
pdf.ln(line_height)
|
80 |
+
|
81 |
+
for section in sections:
|
82 |
+
# debug
|
83 |
+
print("Adding section:", section)
|
84 |
+
pdf.multi_cell(w=0, h=line_height, text=section)
|
85 |
+
pdf.ln(line_height) # Single spacing
|
86 |
+
|
87 |
+
pdf_output = "/tmp/LOI_by_Candid_AI.pdf"
|
88 |
+
pdf.output(pdf_output)
|
89 |
+
return pdf_output
|
90 |
+
|
91 |
+
|
92 |
+
def generate_docx(*sections: str) -> str:
|
93 |
+
"""Generates a downloadable Word document of LOI
|
94 |
+
|
95 |
+
Returns
|
96 |
+
-------
|
97 |
+
str
|
98 |
+
Path to the saved Word document
|
99 |
+
"""
|
100 |
+
doc = Document()
|
101 |
+
style = doc.styles['Normal']
|
102 |
+
style.font.name = 'Arial' # Arial font
|
103 |
+
style.font.size = Pt(12) # 12 point
|
104 |
+
style.paragraph_format.line_spacing = 1.0 # Single spacing
|
105 |
+
|
106 |
+
doc.add_paragraph("[Your name]")
|
107 |
+
doc.add_paragraph("[Organization name]")
|
108 |
+
doc.add_paragraph("[Address]")
|
109 |
+
doc.add_paragraph("")
|
110 |
+
doc.add_paragraph("[Today's date]")
|
111 |
+
doc.add_paragraph("")
|
112 |
+
doc.add_paragraph("[Funder contact name]")
|
113 |
+
doc.add_paragraph("[Funder organization name]")
|
114 |
+
doc.add_paragraph("[Address]")
|
115 |
+
doc.add_paragraph("")
|
116 |
+
doc.add_paragraph("Re: [Project title]")
|
117 |
+
doc.add_paragraph("")
|
118 |
+
|
119 |
+
for section in sections:
|
120 |
+
doc.add_paragraph(section)
|
121 |
+
|
122 |
+
docx_output = "/tmp/LOI_by_Candid_AI.docx"
|
123 |
+
doc.save(docx_output)
|
124 |
+
return docx_output
|
125 |
+
|
126 |
+
|
127 |
+
def build_drafter() -> Tuple[LoggedComponents, gr.Blocks]:
|
128 |
+
with gr.Blocks(theme=gr.themes.Soft(), title="LOI writer") as demo:
|
129 |
+
gr.Markdown("<h1>Demo: LOI writer</h1>")
|
130 |
+
|
131 |
+
with gr.Row():
|
132 |
+
with gr.Column():
|
133 |
+
gr.Markdown(
|
134 |
+
"""<h2>
|
135 |
+
Step 1: Enter the name/EIN of your nonprofit and the name/EIN of the intended funder.
|
136 |
+
Use <a href='https://beta.candid.org/' target="_blank" rel="noopener noreferrer">Candid</a>
|
137 |
+
to find this information, if not known.
|
138 |
+
</h2>"""
|
139 |
+
)
|
140 |
+
org_name_text = gr.Textbox(label="*Name of your organization", lines=1)
|
141 |
+
org_ein_text = gr.Textbox(label="EIN of your organization", lines=1)
|
142 |
+
foundation_name_text = gr.Textbox(label="*To which foundation you are writing an LOI to", lines=1)
|
143 |
+
foundation_ein_text = gr.Textbox(label="EIN of the foundation", lines=1)
|
144 |
+
|
145 |
+
gr.HTML("<div style='margin-bottom: 20px;'></div>")
|
146 |
+
|
147 |
+
with gr.Row():
|
148 |
+
with gr.Column(scale=7):
|
149 |
+
gr.Markdown(
|
150 |
+
"""<h2>
|
151 |
+
Step 2: Autofill with data you have
|
152 |
+
<a
|
153 |
+
href='https://candid.org/share-your-impact'
|
154 |
+
target="_blank"
|
155 |
+
rel="noopener noreferrer"
|
156 |
+
> shared with Candid</a>.
|
157 |
+
Contents can be reviewed and edited before used for LOI generation
|
158 |
+
</h2>"""
|
159 |
+
)
|
160 |
+
with gr.Column(scale=1):
|
161 |
+
autofill_btn = gr.Button("AutoFill (Optional)")
|
162 |
+
|
163 |
+
recip_json = gr.State()
|
164 |
+
# recip_candid_id = gr.State()
|
165 |
+
recip_candid_id = gr.Text(visible=False, interactive=False)
|
166 |
+
funder_json = gr.State()
|
167 |
+
# funder_candid_id = gr.State()
|
168 |
+
funder_candid_id = gr.Text(visible=False, interactive=False)
|
169 |
+
|
170 |
+
gr.HTML("<div style='margin-bottom: 20px;'></div>")
|
171 |
+
gr.Markdown('<h2>Step 3: Provide the basic information to make your "ask" clear</h2>')
|
172 |
+
|
173 |
+
projects_text = gr.Textbox(
|
174 |
+
label="*Main projects",
|
175 |
+
info="Select from the following projects or enter your own project",
|
176 |
+
interactive=True,
|
177 |
+
lines=2
|
178 |
+
)
|
179 |
+
|
180 |
+
project_name_text = gr.Textbox(
|
181 |
+
label="Project name / title",
|
182 |
+
info="Something pithy a reader remembers",
|
183 |
+
interactive=True,
|
184 |
+
lines=1
|
185 |
+
)
|
186 |
+
project_purpose_text = gr.Textbox(label="Purpose of the project", lines=1)
|
187 |
+
amount_text = gr.Textbox(label="Amount needed or requested", lines=1)
|
188 |
+
gr.Markdown(
|
189 |
+
"""
|
190 |
+
<strong>Need help with estimating amount?</strong>
|
191 |
+
Run our cost estimator to get important details to consider.
|
192 |
+
Read the recommendations, decide on a number, and fill in the field above.
|
193 |
+
"""
|
194 |
+
)
|
195 |
+
with gr.Row(variant='panel'):
|
196 |
+
amount_estimate_text = gr.Textbox(
|
197 |
+
label="Guide on How Much to Request",
|
198 |
+
value="Click on \"Estimate amount\" to get results",
|
199 |
+
lines=3,
|
200 |
+
scale=5
|
201 |
+
)
|
202 |
+
cost_calc_button = gr.Button(value="Estimate amount")
|
203 |
+
|
204 |
+
org_mission_statement_text = gr.Textbox(
|
205 |
+
label="Mission statement of your organization",
|
206 |
+
interactive=True, lines=2
|
207 |
+
)
|
208 |
+
|
209 |
+
with gr.Accordion("Need Help with writing mission statement? Expand to see details", open=False):
|
210 |
+
gr.Markdown(
|
211 |
+
"""
|
212 |
+
<strong>Need help with writing mission statement?</strong>
|
213 |
+
Enter info to include in mission statement, and click "Get help" to generate.
|
214 |
+
"""
|
215 |
+
)
|
216 |
+
textbox_info = gr.Textbox(label="Enter info")
|
217 |
+
button_help = gr.Button("Get help")
|
218 |
+
|
219 |
+
# pylint: disable=no-member
|
220 |
+
button_help.click(
|
221 |
+
help_mission_statement,
|
222 |
+
inputs=[org_name_text, textbox_info],
|
223 |
+
outputs=[org_mission_statement_text]
|
224 |
+
)
|
225 |
+
|
226 |
+
foundation_mission_statement_text = gr.Textbox(
|
227 |
+
label="Mission statement of the foundation",
|
228 |
+
interactive=True, lines=2
|
229 |
+
)
|
230 |
+
prior_contact_text = gr.Textbox(
|
231 |
+
label="Prior contact with or prior funding from the foundation (if applicable)",
|
232 |
+
interactive=True, lines=1
|
233 |
+
)
|
234 |
+
connection_text = gr.Textbox(label="Connection to the foundation's guidelines", lines=1)
|
235 |
+
|
236 |
+
with gr.Accordion():
|
237 |
+
gr.Markdown("""
|
238 |
+
<h2>Step 4: Consider answering the following questions to make your LOI sections more detailed and compelling</h2>
|
239 |
+
""")
|
240 |
+
gr.Markdown("<h3>Organization Description</h3>")
|
241 |
+
capacity_text = gr.Textbox(
|
242 |
+
label="What is capacity of the organization to meet the stated need?",
|
243 |
+
interactive=True, lines=3
|
244 |
+
)
|
245 |
+
history_text = gr.Textbox(
|
246 |
+
label=(
|
247 |
+
"Share a very brief history/description of current programs that showcase credibility "
|
248 |
+
"and commitment."
|
249 |
+
),
|
250 |
+
interactive=True, lines=3
|
251 |
+
)
|
252 |
+
path_text = gr.Textbox(
|
253 |
+
label=(
|
254 |
+
"Demonstrate direct connection between what is currently being done and what you wish to "
|
255 |
+
"accomplish with the requested funding."
|
256 |
+
),
|
257 |
+
interactive=True, lines=3
|
258 |
+
)
|
259 |
+
accomplishment_text = gr.Textbox(
|
260 |
+
label="What recent organizational accomplishment is most aligned with this project?",
|
261 |
+
interactive=True, lines=3
|
262 |
+
)
|
263 |
+
|
264 |
+
gr.Markdown("<h3>Need Statement</h3>")
|
265 |
+
target_text = gr.Textbox(
|
266 |
+
label=(
|
267 |
+
"A description of the target population and geographical area. Who will benefit from this "
|
268 |
+
"project?"
|
269 |
+
),
|
270 |
+
interactive=True, lines=2
|
271 |
+
)
|
272 |
+
data_text = gr.Textbox(
|
273 |
+
label=(
|
274 |
+
"Appropriate statistical data in abbreviated form."
|
275 |
+
),
|
276 |
+
info="Please cite reliable sources: current media, research studies / reports",
|
277 |
+
interactive=True, lines=3
|
278 |
+
)
|
279 |
+
|
280 |
+
gr.Markdown(
|
281 |
+
"""<strong>Need help with finding data sources?</strong>
|
282 |
+
See below for selected statistics sources recommended by Candid Learning.
|
283 |
+
"""
|
284 |
+
)
|
285 |
+
|
286 |
+
with gr.Row():
|
287 |
+
category_dropdown = gr.Dropdown(
|
288 |
+
label="Select One or More Categories",
|
289 |
+
choices=list(CATEGORIES.keys()),
|
290 |
+
multiselect=True
|
291 |
+
)
|
292 |
+
data_source_button = gr.Button(value="Get Resource Links by Category")
|
293 |
+
link_markdown = gr.Markdown()
|
294 |
+
|
295 |
+
gr.Markdown("<h3>Project Description</h3>")
|
296 |
+
desired_objectives_text = gr.Textbox(
|
297 |
+
label="Describe the desired objectives for this project.",
|
298 |
+
interactive=True, lines=2
|
299 |
+
)
|
300 |
+
major_activities_text = gr.Textbox(
|
301 |
+
label="Describe the major activities of this project.",
|
302 |
+
interactive=True, lines=2
|
303 |
+
)
|
304 |
+
key_staff_text = gr.Textbox(
|
305 |
+
label="Names and titles of key project staff.",
|
306 |
+
interactive=True, lines=2
|
307 |
+
)
|
308 |
+
stand_out_text = gr.Textbox(
|
309 |
+
label="What is going to make your project/organization stand out?",
|
310 |
+
interactive=True, lines=2
|
311 |
+
)
|
312 |
+
success_text = gr.Textbox(label="How will you know if the project is successful?", lines=2)
|
313 |
+
|
314 |
+
gr.Markdown("<h3>Funding Request</h3>")
|
315 |
+
funding_history_text = gr.Textbox(label="What is your funding history?", interactive=True, lines=2)
|
316 |
+
other_funding_text = gr.Textbox(
|
317 |
+
label=(
|
318 |
+
"Other funding sources being approached for support of this project should be listed in a "
|
319 |
+
"brief sentence/paragraph."
|
320 |
+
),
|
321 |
+
interactive=True, lines=3
|
322 |
+
)
|
323 |
+
follow_up_text = gr.Textbox(
|
324 |
+
label=(
|
325 |
+
"Affirm your readiness to answer further questions and suggest how the funder can learn "
|
326 |
+
"more. Be clear about how to follow up."
|
327 |
+
),
|
328 |
+
interactive=True, lines=2
|
329 |
+
)
|
330 |
+
|
331 |
+
gr.Markdown(
|
332 |
+
'<h2>Step 5: Review your input and submit for generation</h2>'
|
333 |
+
)
|
334 |
+
write_btn = gr.Button("Write LOI")
|
335 |
+
|
336 |
+
with gr.Column():
|
337 |
+
gr.Markdown(
|
338 |
+
"""<h2>
|
339 |
+
Step 6: Review the generated sections.
|
340 |
+
Click on "Edit Section" to run the editorial AI for rewrite suggestions, or make your own edits.
|
341 |
+
</h2>"""
|
342 |
+
)
|
343 |
+
opening = gr.Textbox(label="Opening", interactive=True, lines=3)
|
344 |
+
create_editable_section("Opening", JS, update_highlighted_text, opening)
|
345 |
+
|
346 |
+
|
347 |
+
org_desc = gr.Textbox(label="Organization Description", interactive=True, lines=10)
|
348 |
+
create_editable_section("Organization Description", JS, update_highlighted_text, org_desc)
|
349 |
+
|
350 |
+
need = gr.Textbox(label="Need", interactive=True, lines=10)
|
351 |
+
create_editable_section("Need", JS, update_highlighted_text, need)
|
352 |
+
|
353 |
+
project_desc = gr.Textbox(label="Project Description", interactive=True, lines=10)
|
354 |
+
create_editable_section("Project Description", JS, update_highlighted_text, project_desc)
|
355 |
+
|
356 |
+
fund_request = gr.Textbox(label="Funding Request", interactive=True, lines=5)
|
357 |
+
create_editable_section("Funding Request", JS, update_highlighted_text, fund_request)
|
358 |
+
|
359 |
+
conclusion = gr.Textbox(label="Conclusion", interactive=True, lines=5)
|
360 |
+
create_editable_section("Conclusion", JS, update_highlighted_text, conclusion)
|
361 |
+
|
362 |
+
gr.Markdown(
|
363 |
+
"""<h2>
|
364 |
+
Step 7: Select your preferred document type
|
365 |
+
and scroll to the bottom of the page to download the file.
|
366 |
+
</h2>"""
|
367 |
+
)
|
368 |
+
|
369 |
+
with gr.Row():
|
370 |
+
download_pdf_button = gr.Button("Download as PDF")
|
371 |
+
download_word_button = gr.Button("Download as docx")
|
372 |
+
|
373 |
+
# pylint: disable=no-member
|
374 |
+
download_pdf_button.click(
|
375 |
+
generate_pdf,
|
376 |
+
inputs=[opening, org_desc, need, project_desc, fund_request, conclusion],
|
377 |
+
outputs=gr.File(label="Download as PDF")
|
378 |
+
)
|
379 |
+
|
380 |
+
# pylint: disable=no-member
|
381 |
+
download_word_button.click(
|
382 |
+
generate_docx,
|
383 |
+
inputs=[opening, org_desc, need, project_desc, fund_request, conclusion],
|
384 |
+
outputs=gr.File(label="Download as docx")
|
385 |
+
)
|
386 |
+
|
387 |
+
# pylint: disable=no-member
|
388 |
+
autofill_btn.click(
|
389 |
+
fn=organization_pair_autofill,
|
390 |
+
inputs=[org_name_text, org_ein_text, foundation_name_text, foundation_ein_text],
|
391 |
+
outputs=[
|
392 |
+
projects_text,
|
393 |
+
capacity_text,
|
394 |
+
follow_up_text,
|
395 |
+
accomplishment_text,
|
396 |
+
org_mission_statement_text,
|
397 |
+
foundation_mission_statement_text,
|
398 |
+
funding_history_text,
|
399 |
+
recip_json,
|
400 |
+
recip_candid_id,
|
401 |
+
funder_json,
|
402 |
+
funder_candid_id
|
403 |
+
],
|
404 |
+
api_name=False,
|
405 |
+
queue=True,
|
406 |
+
)
|
407 |
+
|
408 |
+
cost_calc_button.click(
|
409 |
+
fn=cost_estimator,
|
410 |
+
inputs=[
|
411 |
+
recip_candid_id,
|
412 |
+
recip_json,
|
413 |
+
funder_json,
|
414 |
+
projects_text
|
415 |
+
],
|
416 |
+
outputs=[amount_estimate_text],
|
417 |
+
api_name=False,
|
418 |
+
queue=True
|
419 |
+
)
|
420 |
+
|
421 |
+
data_source_button.click(fn=update_links, inputs=category_dropdown, outputs=link_markdown)
|
422 |
+
|
423 |
+
write_btn.click(
|
424 |
+
fn=draft_letter,
|
425 |
+
inputs=[
|
426 |
+
org_name_text,
|
427 |
+
foundation_name_text,
|
428 |
+
projects_text,
|
429 |
+
amount_text,
|
430 |
+
org_mission_statement_text,
|
431 |
+
foundation_mission_statement_text,
|
432 |
+
project_name_text,
|
433 |
+
project_purpose_text,
|
434 |
+
prior_contact_text,
|
435 |
+
connection_text,
|
436 |
+
capacity_text,
|
437 |
+
path_text,
|
438 |
+
accomplishment_text,
|
439 |
+
history_text,
|
440 |
+
funding_history_text,
|
441 |
+
target_text,
|
442 |
+
data_text,
|
443 |
+
desired_objectives_text,
|
444 |
+
major_activities_text,
|
445 |
+
key_staff_text,
|
446 |
+
stand_out_text,
|
447 |
+
success_text,
|
448 |
+
other_funding_text,
|
449 |
+
follow_up_text
|
450 |
+
],
|
451 |
+
outputs=[opening, org_desc, need, project_desc, fund_request, conclusion],
|
452 |
+
api_name=False,
|
453 |
+
queue=True,
|
454 |
+
)
|
455 |
+
|
456 |
+
logged = LoggedComponents(
|
457 |
+
context=[
|
458 |
+
recip_candid_id,
|
459 |
+
funder_candid_id,
|
460 |
+
org_mission_statement_text,
|
461 |
+
foundation_mission_statement_text,
|
462 |
+
project_name_text,
|
463 |
+
project_purpose_text,
|
464 |
+
amount_text,
|
465 |
+
prior_contact_text,
|
466 |
+
connection_text,
|
467 |
+
capacity_text,
|
468 |
+
history_text,
|
469 |
+
accomplishment_text,
|
470 |
+
target_text,
|
471 |
+
data_text,
|
472 |
+
desired_objectives_text,
|
473 |
+
funding_history_text,
|
474 |
+
other_funding_text
|
475 |
+
],
|
476 |
+
letter=[opening, org_desc, need, project_desc, fund_request, conclusion]
|
477 |
+
)
|
478 |
+
return logged, demo
|
479 |
+
|
480 |
+
|
481 |
+
def build_feedback(components: LoggedComponents) -> gr.Blocks:
|
482 |
+
def handle_feedback(*args):
|
483 |
+
try:
|
484 |
+
context = {
|
485 |
+
"recipient_candid_entity_id": args[0],
|
486 |
+
"funder_candid_entity_id": args[1],
|
487 |
+
"recipient_mission": args[2],
|
488 |
+
"funder_mission": args[3],
|
489 |
+
"project_name": args[4],
|
490 |
+
"project_purpose": args[5],
|
491 |
+
"amount": args[6],
|
492 |
+
"prior_contact": args[7],
|
493 |
+
"connection": args[8],
|
494 |
+
"capacity": args[9],
|
495 |
+
"history": args[10],
|
496 |
+
"accomplishment": args[11],
|
497 |
+
"target": args[12],
|
498 |
+
"data": args[13],
|
499 |
+
"desired_objectives": args[14],
|
500 |
+
"funding_history": args[15],
|
501 |
+
"other_funding": args[16],
|
502 |
+
}
|
503 |
+
|
504 |
+
letter_fields = (
|
505 |
+
"opening", "org_description", "need",
|
506 |
+
"project_description", "funding_request", "conclusion"
|
507 |
+
)
|
508 |
+
letter = dict(zip(letter_fields, args[17: (17 + 6)]))
|
509 |
+
send_feedback(
|
510 |
+
context=context,
|
511 |
+
letter=letter,
|
512 |
+
relevance=args[-6],
|
513 |
+
coherence=args[-5],
|
514 |
+
fluency=args[-4],
|
515 |
+
overall_quality=args[-3],
|
516 |
+
comments=args[-2],
|
517 |
+
email=args[-1]
|
518 |
+
)
|
519 |
+
gr.Info("Thank you for providing feedback!")
|
520 |
+
except Exception as ex:
|
521 |
+
raise gr.Error(f"Failed to submit feedback: {ex}")
|
522 |
+
|
523 |
+
with gr.Blocks(theme=gr.themes.Soft(), title="LOI writer") as demo:
|
524 |
+
gr.Markdown("<h1 align='center'>Help us improve this tool with your valuable feedback</h1>")
|
525 |
+
gr.Markdown(
|
526 |
+
"<h3> Please rate each of the following aspects on a rating scale of 1-5, "
|
527 |
+
"where 1 is 'very poor' and 5 is 'very good'.</h3>"
|
528 |
+
)
|
529 |
+
|
530 |
+
with gr.Row():
|
531 |
+
with gr.Column():
|
532 |
+
relevance = gr.Radio(
|
533 |
+
[1, 2, 3, 4, 5],
|
534 |
+
label="Relevance",
|
535 |
+
info=(
|
536 |
+
"Relevance measures the extent to which the model's generated responses are pertinent "
|
537 |
+
"and directly related to the given questions."
|
538 |
+
)
|
539 |
+
)
|
540 |
+
|
541 |
+
coherence = gr.Radio(
|
542 |
+
[1, 2, 3, 4, 5],
|
543 |
+
label="Coherence",
|
544 |
+
info=(
|
545 |
+
"Coherence evaluates how well the language model can produce output that flows smoothly, "
|
546 |
+
"reads naturally, and resembles human-like language."
|
547 |
+
)
|
548 |
+
)
|
549 |
+
|
550 |
+
fluency = gr.Radio(
|
551 |
+
[1, 2, 3, 4, 5],
|
552 |
+
label="Fluency",
|
553 |
+
info=(
|
554 |
+
"Fluency evaluates the language proficiency of a generative AI's predicted answer. It assesses "
|
555 |
+
"how well the generated text adheres to grammatical rules, syntactic structures, and "
|
556 |
+
"appropriate usage of vocabulary, resulting in linguistically correct and "
|
557 |
+
"natural-sounding responses."
|
558 |
+
)
|
559 |
+
)
|
560 |
+
|
561 |
+
overall_quality = gr.Radio([1, 2, 3, 4, 5], label="Overall Quality")
|
562 |
+
comment = gr.Textbox(label="Additional comments (optional)", lines=4)
|
563 |
+
email = gr.Textbox(label="Your email (optional)", lines=1)
|
564 |
+
|
565 |
+
components["relevance"] = relevance
|
566 |
+
components["coherence"] = coherence
|
567 |
+
components["fluency"] = fluency
|
568 |
+
components["overall_quality"] = overall_quality
|
569 |
+
components["comments"] = comment
|
570 |
+
components["email"] = email
|
571 |
+
|
572 |
+
submit = gr.Button("Submit Feedback")
|
573 |
+
|
574 |
+
# pylint: disable=no-member
|
575 |
+
submit.click(
|
576 |
+
fn=handle_feedback,
|
577 |
+
inputs=[comp for k, cl in components.items() for comp in (cl if isinstance(cl, list) else [cl])],
|
578 |
+
outputs=None,
|
579 |
+
preprocess=False,
|
580 |
+
)
|
581 |
+
return demo
|
582 |
+
|
583 |
+
|
584 |
+
def build_demo():
|
585 |
+
logger, drafter = build_drafter()
|
586 |
+
feedback = build_feedback(logger)
|
587 |
+
|
588 |
+
return gr.TabbedInterface(
|
589 |
+
interface_list=[drafter, feedback],
|
590 |
+
tab_names=["LOI writer", "Feedback"],
|
591 |
+
title="Candid's letter of intent (LOI) writer",
|
592 |
+
theme=gr.themes.Soft()
|
593 |
+
)
|
594 |
+
|
595 |
+
|
596 |
+
if __name__ == '__main__':
|
597 |
+
app = build_demo()
|
598 |
+
app.queue(max_size=5).launch(
|
599 |
+
show_api=False,
|
600 |
+
auth=[
|
601 |
+
(os.getenv("APP_USERNAME"), os.getenv("APP_PASSWORD")),
|
602 |
+
],
|
603 |
+
auth_message="Login to Candid's letter of intent demo"
|
604 |
+
)
|