Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -1,207 +1,104 @@
|
|
1 |
import os
|
2 |
import sys
|
3 |
import time
|
4 |
-
import re
|
5 |
-
import math
|
6 |
-
import tempfile
|
7 |
-
import uuid
|
8 |
-
from io import BytesIO
|
9 |
import gradio as gr
|
10 |
-
import
|
11 |
-
from transformers import AutoTokenizer, AutoModelForCausalLM
|
12 |
from pptx import Presentation
|
13 |
-
from pptx.util import Pt
|
14 |
-
from
|
15 |
-
|
16 |
-
|
17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
18 |
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
#
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
"{description}"
|
40 |
-
|
41 |
-
Your proposal must include these sections:
|
42 |
-
1. Executive Summary - Concise overview of the entire proposal (1-2 paragraphs)
|
43 |
-
2. Project Background - Context, history, and need for the project (2-3 paragraphs)
|
44 |
-
3. Goals and Objectives - Clear, measurable outcomes using SMART criteria (specific, measurable, achievable, relevant, time-bound)
|
45 |
-
4. Methodology and Approach - Detailed explanation of how the project will be executed, including specific techniques and frameworks
|
46 |
-
5. Timeline - Realistic schedule with key milestones and deliverables (presented in chronological order with approximate dates)
|
47 |
-
6. Budget Considerations - Cost breakdown by category with justifications
|
48 |
-
7. Expected Outcomes - Tangible and intangible benefits with metrics for measuring success
|
49 |
-
8. Team and Resources - Key personnel, their qualifications, and resource requirements
|
50 |
-
9. Risk Assessment - Potential challenges and mitigation strategies
|
51 |
-
10. Conclusion - Compelling closing with clear next steps
|
52 |
-
|
53 |
-
For each section, include specific details that would convince stakeholders to approve this project.
|
54 |
-
Use professional language and maintain a confident, authoritative tone throughout.
|
55 |
-
Format each section with clear headings and organized paragraphs."""
|
56 |
-
|
57 |
-
# Format the prompt according to the model's expected format
|
58 |
-
# Zephyr uses a specific format with system and user messages
|
59 |
-
messages = [
|
60 |
-
{"role": "system", "content": system_prompt},
|
61 |
-
{"role": "user", "content": user_prompt}
|
62 |
-
]
|
63 |
-
|
64 |
-
# Convert messages to the format expected by the model
|
65 |
-
prompt = tokenizer.apply_chat_template(messages, tokenize=False)
|
66 |
-
|
67 |
-
# Tokenize and generate
|
68 |
-
inputs = tokenizer(prompt, return_tensors="pt").to(device)
|
69 |
-
|
70 |
-
with torch.no_grad():
|
71 |
-
outputs = model.generate(
|
72 |
-
inputs.input_ids,
|
73 |
-
max_new_tokens=max_length,
|
74 |
-
temperature=temperature,
|
75 |
-
do_sample=True,
|
76 |
-
top_p=0.9, # Add nucleus sampling for better quality
|
77 |
-
top_k=50, # Limit vocab to top 50 tokens at each step
|
78 |
-
repetition_penalty=1.2 # Reduce repetition
|
79 |
-
)
|
80 |
-
|
81 |
-
full_output = tokenizer.decode(outputs[0], skip_special_tokens=True)
|
82 |
-
|
83 |
-
# Extract just the generated proposal without the prompt
|
84 |
-
proposal = full_output[len(prompt):].strip()
|
85 |
-
|
86 |
-
# Post-processing to ensure clean formatting
|
87 |
-
proposal = clean_proposal_format(proposal)
|
88 |
-
|
89 |
-
return proposal
|
90 |
-
|
91 |
-
def clean_proposal_format(proposal_text):
|
92 |
-
"""Clean up the proposal text for better formatting"""
|
93 |
-
|
94 |
-
# Remove any assistant prefixes that might be generated
|
95 |
-
proposal_text = re.sub(r'^(Assistant:|A:|Response:)\s*', '', proposal_text)
|
96 |
-
|
97 |
-
# Ensure section titles are properly formatted
|
98 |
-
section_titles = [
|
99 |
-
"Executive Summary", "Project Background", "Goals and Objectives",
|
100 |
-
"Methodology and Approach", "Timeline", "Budget Considerations",
|
101 |
-
"Expected Outcomes", "Team and Resources", "Risk Assessment", "Conclusion"
|
102 |
-
]
|
103 |
-
|
104 |
-
for title in section_titles:
|
105 |
-
# Replace various formats of section titles with consistent formatting
|
106 |
-
proposal_text = re.sub(
|
107 |
-
rf'(?i)(^|\n)[\d\.\s]*{title}[\s\:]*(\n|\r)',
|
108 |
-
f'\n\n{title}\n\n',
|
109 |
-
proposal_text
|
110 |
)
|
|
|
|
|
|
|
111 |
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
# Ensure bullet points are consistent
|
116 |
-
proposal_text = re.sub(r'\n\s*•\s*', '\n- ', proposal_text)
|
117 |
-
proposal_text = re.sub(r'\n\s*\*\s*', '\n- ', proposal_text)
|
118 |
-
|
119 |
-
return proposal_text
|
120 |
-
|
121 |
-
# ===== ENHANCED POWERPOINT GENERATION FUNCTIONS ===== #
|
122 |
|
123 |
-
def create_slides(proposal
|
124 |
-
"""Create
|
125 |
-
|
126 |
-
# Create presentation with widescreen dimensions
|
127 |
prs = Presentation()
|
128 |
-
prs.slide_width = Inches(13.33)
|
129 |
-
prs.slide_height = Inches(7.5)
|
130 |
|
131 |
-
#
|
132 |
-
colors = {
|
133 |
-
'primary': RGBColor(0, 112, 192), # Blue
|
134 |
-
'secondary': RGBColor(0, 176, 80), # Green
|
135 |
-
'accent1': RGBColor(255, 102, 0), # Orange
|
136 |
-
'accent2': RGBColor(112, 48, 160), # Purple
|
137 |
-
'dark': RGBColor(54, 54, 54), # Dark Gray
|
138 |
-
'light': RGBColor(244, 244, 244) # Light Gray
|
139 |
-
}
|
140 |
-
|
141 |
-
# Add title slide with professional styling
|
142 |
title_slide = prs.slides.add_slide(prs.slide_layouts[0])
|
143 |
-
title_slide.shapes.title.text =
|
144 |
-
title_slide.shapes.title.text_frame.paragraphs[0].font.size = Pt(44)
|
145 |
-
title_slide.shapes.title.text_frame.paragraphs[0].font.color.rgb = colors['primary']
|
146 |
-
title_slide.shapes.title.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER
|
147 |
-
|
148 |
-
# Add subtitle
|
149 |
subtitle = title_slide.placeholders[1]
|
150 |
-
subtitle.text = "
|
151 |
-
subtitle.text_frame.paragraphs[0].font.size = Pt(28)
|
152 |
-
subtitle.text_frame.paragraphs[0].font.color.rgb = colors['dark']
|
153 |
-
|
154 |
-
# Add background shape for visual interest
|
155 |
-
left = top = 0
|
156 |
-
width = prs.slide_width
|
157 |
-
height = prs.slide_height
|
158 |
-
|
159 |
-
# Add decorative element to title slide
|
160 |
-
shape = title_slide.shapes.add_shape(
|
161 |
-
1, Inches(0), Inches(6.5), prs.slide_width, Inches(1)
|
162 |
-
)
|
163 |
-
shape.fill.solid()
|
164 |
-
shape.fill.fore_color.rgb = colors['primary']
|
165 |
-
shape.line.color.rgb = colors['primary']
|
166 |
|
167 |
# List of sections to look for
|
168 |
sections = [
|
169 |
"Executive Summary",
|
170 |
"Project Background",
|
171 |
"Goals and Objectives",
|
172 |
-
"Methodology
|
173 |
"Timeline",
|
174 |
-
"Budget
|
175 |
"Expected Outcomes",
|
176 |
"Team and Resources",
|
177 |
"Risk Assessment",
|
178 |
"Conclusion"
|
179 |
]
|
180 |
|
181 |
-
#
|
182 |
-
toc_slide = prs.slides.add_slide(prs.slide_layouts[2])
|
183 |
-
toc_slide.shapes.title.text = "Table of Contents"
|
184 |
-
toc_slide.shapes.title.text_frame.paragraphs[0].font.size = Pt(40)
|
185 |
-
toc_slide.shapes.title.text_frame.paragraphs[0].font.color.rgb = colors['primary']
|
186 |
-
|
187 |
-
# Add decorative element to TOC slide
|
188 |
-
shape = toc_slide.shapes.add_shape(
|
189 |
-
1, Inches(0), Inches(0), Inches(1), prs.slide_height
|
190 |
-
)
|
191 |
-
shape.fill.solid()
|
192 |
-
shape.fill.fore_color.rgb = colors['primary']
|
193 |
-
shape.line.color.rgb = colors['primary']
|
194 |
-
|
195 |
-
# Add TOC content
|
196 |
-
toc_content = toc_slide.placeholders[1].text_frame
|
197 |
-
for i, section in enumerate(sections, 1):
|
198 |
-
p = toc_content.add_paragraph()
|
199 |
-
p.text = f"{i}. {section}"
|
200 |
-
p.font.size = Pt(24)
|
201 |
-
p.font.color.rgb = colors['dark']
|
202 |
-
p.space_after = Pt(12)
|
203 |
-
|
204 |
-
# Split text into paragraphs and identify sections
|
205 |
paragraphs = proposal.split('\n\n')
|
206 |
|
207 |
# Process each paragraph
|
@@ -217,15 +114,13 @@ def create_slides(proposal, project_title="Project Proposal"):
|
|
217 |
# Check if this is a section header
|
218 |
is_header = False
|
219 |
for section in sections:
|
220 |
-
|
221 |
-
if (section.lower() in para.lower() and len(para) < 100) or \
|
222 |
-
re.match(r'^[0-9]+\.?\s*' + section, para, re.IGNORECASE):
|
223 |
# Save previous section
|
224 |
if current_section and current_content:
|
225 |
found_sections.append((current_section, current_content))
|
226 |
|
227 |
# Start new section
|
228 |
-
current_section =
|
229 |
current_content = []
|
230 |
is_header = True
|
231 |
break
|
@@ -238,930 +133,93 @@ def create_slides(proposal, project_title="Project Proposal"):
|
|
238 |
found_sections.append((current_section, current_content))
|
239 |
|
240 |
# Create slides for each section
|
241 |
-
for
|
242 |
-
# Section title slide
|
243 |
section_slide = prs.slides.add_slide(prs.slide_layouts[2])
|
244 |
section_slide.shapes.title.text = title
|
245 |
-
section_slide.shapes.title.text_frame.paragraphs[0].font.size = Pt(40)
|
246 |
-
section_slide.shapes.title.text_frame.paragraphs[0].font.color.rgb = colors['primary']
|
247 |
-
|
248 |
-
# Add decorative accent based on section type
|
249 |
-
accent_color = colors['primary']
|
250 |
-
if "goal" in title.lower() or "objective" in title.lower():
|
251 |
-
accent_color = colors['secondary']
|
252 |
-
elif "risk" in title.lower():
|
253 |
-
accent_color = colors['accent1']
|
254 |
-
elif "conclusion" in title.lower():
|
255 |
-
accent_color = colors['accent2']
|
256 |
-
|
257 |
-
# Add section number
|
258 |
-
subtitle = section_slide.placeholders[1]
|
259 |
-
subtitle.text = f"Section {section_index + 1}"
|
260 |
-
subtitle.text_frame.paragraphs[0].font.size = Pt(28)
|
261 |
-
subtitle.text_frame.paragraphs[0].font.color.rgb = accent_color
|
262 |
|
263 |
-
#
|
264 |
-
shape = section_slide.shapes.add_shape(
|
265 |
-
1, Inches(0), Inches(6.5), prs.slide_width, Inches(1)
|
266 |
-
)
|
267 |
-
shape.fill.solid()
|
268 |
-
shape.fill.fore_color.rgb = accent_color
|
269 |
-
shape.line.color.rgb = accent_color
|
270 |
-
|
271 |
-
# Content slides with better formatting
|
272 |
current_slide = None
|
273 |
-
text_frame = None
|
274 |
paragraphs_on_slide = 0
|
275 |
|
276 |
-
for
|
277 |
-
# Detect if paragraph is a bullet point
|
278 |
-
is_bullet = para.strip().startswith("-") or para.strip().startswith("*")
|
279 |
-
is_numbered = bool(re.match(r'^\d+\.', para.strip()))
|
280 |
-
|
281 |
# Start a new slide if needed
|
282 |
-
|
283 |
-
max_paragraphs = 3 if len(para) > 200 else 4
|
284 |
-
if current_slide is None or paragraphs_on_slide >= max_paragraphs:
|
285 |
current_slide = prs.slides.add_slide(prs.slide_layouts[1])
|
286 |
current_slide.shapes.title.text = title
|
287 |
-
current_slide.shapes.title.text_frame.paragraphs[0].font.size = Pt(36)
|
288 |
-
current_slide.shapes.title.text_frame.paragraphs[0].font.color.rgb = colors['primary']
|
289 |
-
|
290 |
-
# Add decorative element
|
291 |
-
shape = current_slide.shapes.add_shape(
|
292 |
-
1, Inches(0), Inches(0), Inches(0.3), prs.slide_height
|
293 |
-
)
|
294 |
-
shape.fill.solid()
|
295 |
-
shape.fill.fore_color.rgb = accent_color
|
296 |
-
shape.line.color.rgb = accent_color
|
297 |
-
|
298 |
text_frame = current_slide.placeholders[1].text_frame
|
299 |
-
text_frame.word_wrap = True
|
300 |
paragraphs_on_slide = 0
|
|
|
|
|
301 |
|
302 |
-
|
303 |
-
if para_index > 0:
|
304 |
-
p = text_frame.add_paragraph()
|
305 |
-
p.text = "Continued..."
|
306 |
-
p.font.italic = True
|
307 |
-
p.font.size = Pt(14)
|
308 |
-
p.font.color.rgb = colors['dark']
|
309 |
-
paragraphs_on_slide += 0.5 # Count as half a paragraph
|
310 |
-
|
311 |
-
# Add the paragraph with appropriate formatting
|
312 |
p = text_frame.add_paragraph()
|
|
|
313 |
|
314 |
-
#
|
315 |
-
if
|
316 |
-
clean_text = para.strip()[1:].strip()
|
317 |
-
p.text = clean_text
|
318 |
-
p.level = 1
|
319 |
-
elif is_numbered:
|
320 |
-
p.text = para.strip()
|
321 |
p.level = 1
|
322 |
-
else:
|
323 |
-
p.text = para.strip()
|
324 |
-
|
325 |
-
# Apply formatting
|
326 |
-
p.font.size = Pt(20)
|
327 |
-
p.font.color.rgb = colors['dark']
|
328 |
-
p.space_after = Pt(12)
|
329 |
-
|
330 |
-
# Highlight key phrases with color
|
331 |
-
text_frame.fit_text(max_size=Pt(20))
|
332 |
|
333 |
paragraphs_on_slide += 1
|
334 |
|
335 |
-
# Add a closing slide
|
336 |
-
closing_slide = prs.slides.add_slide(prs.slide_layouts[5])
|
337 |
-
title_shape = closing_slide.shapes.title
|
338 |
-
title_shape.text = "Thank You"
|
339 |
-
title_shape.text_frame.paragraphs[0].font.size = Pt(54)
|
340 |
-
title_shape.text_frame.paragraphs[0].font.color.rgb = colors['primary']
|
341 |
-
title_shape.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER
|
342 |
-
|
343 |
-
subtitle_shape = closing_slide.shapes.placeholders[1]
|
344 |
-
subtitle_shape.text = "Questions & Discussion"
|
345 |
-
subtitle_shape.text_frame.paragraphs[0].font.size = Pt(32)
|
346 |
-
subtitle_shape.text_frame.paragraphs[0].font.color.rgb = colors['dark']
|
347 |
-
subtitle_shape.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER
|
348 |
-
|
349 |
-
# Add large decorative shape to closing slide
|
350 |
-
shape = closing_slide.shapes.add_shape(
|
351 |
-
1, Inches(0), Inches(6.5), prs.slide_width, Inches(1)
|
352 |
-
)
|
353 |
-
shape.fill.solid()
|
354 |
-
shape.fill.fore_color.rgb = colors['primary']
|
355 |
-
shape.line.color.rgb = colors['primary']
|
356 |
-
|
357 |
-
# Generate a unique filename
|
358 |
-
unique_id = str(uuid.uuid4())[:8]
|
359 |
-
output_path = f"proposal_slides_{unique_id}.pptx"
|
360 |
-
|
361 |
# Save the presentation
|
|
|
362 |
prs.save(output_path)
|
363 |
return output_path
|
364 |
|
365 |
-
|
366 |
-
|
367 |
-
|
368 |
-
|
369 |
-
|
370 |
-
return prs
|
371 |
-
|
372 |
-
# Extract potential data for charts from the proposal text
|
373 |
-
timeline_data = extract_timeline_data(proposal_text)
|
374 |
-
budget_data = extract_budget_data(proposal_text)
|
375 |
-
risk_data = extract_risk_data(proposal_text)
|
376 |
-
|
377 |
-
# Add a timeline slide if data available
|
378 |
-
if timeline_data and len(timeline_data) >= 2:
|
379 |
-
timeline_slide = prs.slides.add_slide(prs.slide_layouts[6]) # Blank layout
|
380 |
-
|
381 |
-
# Add title
|
382 |
-
title_shape = timeline_slide.shapes.add_textbox(
|
383 |
-
Inches(0.5), Inches(0.5), Inches(9), Inches(1)
|
384 |
-
)
|
385 |
-
title_frame = title_shape.text_frame
|
386 |
-
title_frame.text = "Project Timeline"
|
387 |
-
title_frame.paragraphs[0].font.size = Pt(40)
|
388 |
-
title_frame.paragraphs[0].font.bold = True
|
389 |
-
title_frame.paragraphs[0].font.color.rgb = RGBColor(0, 112, 192)
|
390 |
-
|
391 |
-
# Create chart data
|
392 |
-
chart_data = ChartData()
|
393 |
-
chart_data.categories = [item[0] for item in timeline_data] # Milestone names
|
394 |
-
|
395 |
-
# Convert timeline data to numeric values (months from start)
|
396 |
-
def month_to_number(month_name):
|
397 |
-
months = {'January': 1, 'February': 2, 'March': 3, 'April': 4,
|
398 |
-
'May': 5, 'June': 6, 'July': 7, 'August': 8,
|
399 |
-
'September': 9, 'October': 10, 'November': 11, 'December': 12}
|
400 |
-
for month, num in months.items():
|
401 |
-
if month.lower() in month_name.lower():
|
402 |
-
return num
|
403 |
-
return 0
|
404 |
-
|
405 |
-
# Normalize timeline values based on extracted data
|
406 |
-
start_month = min(month_to_number(item[1]) for item in timeline_data) if timeline_data else 1
|
407 |
-
timeline_values = [max(1, month_to_number(item[1]) - start_month + 1) for item in timeline_data]
|
408 |
-
|
409 |
-
chart_data.add_series('Duration (months)', timeline_values)
|
410 |
-
|
411 |
-
# Add chart to the slide
|
412 |
-
x, y, cx, cy = Inches(1), Inches(2), Inches(11), Inches(5)
|
413 |
-
chart = timeline_slide.shapes.add_chart(
|
414 |
-
XL_CHART_TYPE.BAR_CLUSTERED, x, y, cx, cy, chart_data
|
415 |
-
).chart
|
416 |
-
|
417 |
-
# Style the chart
|
418 |
-
chart.has_legend = True
|
419 |
-
chart.legend.position = XL_LEGEND_POSITION.BOTTOM
|
420 |
-
chart.legend.include_in_layout = False
|
421 |
-
|
422 |
-
plot = chart.plots[0]
|
423 |
-
plot.has_data_labels = True
|
424 |
-
data_labels = plot.data_labels
|
425 |
-
data_labels.font.size = Pt(12)
|
426 |
-
data_labels.font.color.rgb = RGBColor(0, 0, 0)
|
427 |
-
data_labels.position = 2 # Inside End
|
428 |
-
|
429 |
-
# Add a budget breakdown slide if data available
|
430 |
-
if budget_data and len(budget_data) >= 2:
|
431 |
-
budget_slide = prs.slides.add_slide(prs.slide_layouts[6]) # Blank layout
|
432 |
-
|
433 |
-
# Add title
|
434 |
-
title_shape = budget_slide.shapes.add_textbox(
|
435 |
-
Inches(0.5), Inches(0.5), Inches(9), Inches(1)
|
436 |
-
)
|
437 |
-
title_frame = title_shape.text_frame
|
438 |
-
title_frame.text = "Budget Allocation"
|
439 |
-
title_frame.paragraphs[0].font.size = Pt(40)
|
440 |
-
title_frame.paragraphs[0].font.bold = True
|
441 |
-
title_frame.paragraphs[0].font.color.rgb = RGBColor(0, 112, 192)
|
442 |
-
|
443 |
-
# Create pie chart data
|
444 |
-
chart_data = CategoryChartData()
|
445 |
-
chart_data.categories = [item[0] for item in budget_data] # Category names
|
446 |
-
chart_data.add_series('Budget', [item[1] for item in budget_data]) # Values
|
447 |
-
|
448 |
-
# Add chart to the slide
|
449 |
-
x, y, cx, cy = Inches(2), Inches(2), Inches(9), Inches(5)
|
450 |
-
chart = budget_slide.shapes.add_chart(
|
451 |
-
XL_CHART_TYPE.PIE, x, y, cx, cy, chart_data
|
452 |
-
).chart
|
453 |
-
|
454 |
-
# Style the chart
|
455 |
-
chart.has_legend = True
|
456 |
-
chart.legend.position = XL_LEGEND_POSITION.RIGHT
|
457 |
-
chart.legend.font.size = Pt(14)
|
458 |
-
|
459 |
-
plot = chart.plots[0]
|
460 |
-
plot.has_data_labels = True
|
461 |
-
data_labels = plot.data_labels
|
462 |
-
data_labels.font.size = Pt(12)
|
463 |
-
data_labels.position = 1 # Outside End
|
464 |
-
data_labels.number_format = '0%'
|
465 |
-
|
466 |
-
# Add a risk assessment matrix if data available
|
467 |
-
if risk_data and len(risk_data) >= 2:
|
468 |
-
risk_slide = prs.slides.add_slide(prs.slide_layouts[6]) # Blank layout
|
469 |
-
|
470 |
-
# Add title
|
471 |
-
title_shape = risk_slide.shapes.add_textbox(
|
472 |
-
Inches(0.5), Inches(0.5), Inches(9), Inches(1)
|
473 |
-
)
|
474 |
-
title_frame = title_shape.text_frame
|
475 |
-
title_frame.text = "Risk Assessment Matrix"
|
476 |
-
title_frame.paragraphs[0].font.size = Pt(40)
|
477 |
-
title_frame.paragraphs[0].font.bold = True
|
478 |
-
title_frame.paragraphs[0].font.color.rgb = RGBColor(0, 112, 192)
|
479 |
-
|
480 |
-
# Create a table for the risk matrix
|
481 |
-
rows, cols = len(risk_data) + 1, 3 # +1 for header
|
482 |
-
table_width, table_height = Inches(10), Inches(5)
|
483 |
-
table = risk_slide.shapes.add_table(
|
484 |
-
rows, cols,
|
485 |
-
Inches(1.5), Inches(2),
|
486 |
-
table_width, table_height
|
487 |
-
).table
|
488 |
-
|
489 |
-
# Set column widths
|
490 |
-
table.columns[0].width = Inches(5) # Risk description
|
491 |
-
table.columns[1].width = Inches(2.5) # Probability
|
492 |
-
table.columns[2].width = Inches(2.5) # Impact
|
493 |
-
|
494 |
-
# Add header row
|
495 |
-
cell = table.cell(0, 0)
|
496 |
-
cell.text = "Risk Description"
|
497 |
-
cell.text_frame.paragraphs[0].font.bold = True
|
498 |
-
cell.text_frame.paragraphs[0].font.size = Pt(16)
|
499 |
-
|
500 |
-
cell = table.cell(0, 1)
|
501 |
-
cell.text = "Probability"
|
502 |
-
cell.text_frame.paragraphs[0].font.bold = True
|
503 |
-
cell.text_frame.paragraphs[0].font.size = Pt(16)
|
504 |
-
|
505 |
-
cell = table.cell(0, 2)
|
506 |
-
cell.text = "Impact"
|
507 |
-
cell.text_frame.paragraphs[0].font.bold = True
|
508 |
-
cell.text_frame.paragraphs[0].font.size = Pt(16)
|
509 |
-
|
510 |
-
# Add risk data rows
|
511 |
-
for i, (risk, probability, impact) in enumerate(risk_data, 1):
|
512 |
-
# Risk description
|
513 |
-
cell = table.cell(i, 0)
|
514 |
-
cell.text = risk
|
515 |
-
cell.text_frame.paragraphs[0].font.size = Pt(14)
|
516 |
-
|
517 |
-
# Probability
|
518 |
-
cell = table.cell(i, 1)
|
519 |
-
cell.text = probability
|
520 |
-
cell.text_frame.paragraphs[0].font.size = Pt(14)
|
521 |
-
|
522 |
-
# Impact
|
523 |
-
cell = table.cell(i, 2)
|
524 |
-
cell.text = impact
|
525 |
-
cell.text_frame.paragraphs[0].font.size = Pt(14)
|
526 |
-
|
527 |
-
# Color code based on overall risk level
|
528 |
-
prob_level = get_risk_level(probability)
|
529 |
-
impact_level = get_risk_level(impact)
|
530 |
-
overall_risk = prob_level * impact_level
|
531 |
-
|
532 |
-
# Set background color based on risk level
|
533 |
-
fill_color = RGBColor(255, 255, 255) # Default white
|
534 |
-
if overall_risk >= 9:
|
535 |
-
fill_color = RGBColor(255, 153, 153) # Red for high risk
|
536 |
-
elif overall_risk >= 4:
|
537 |
-
fill_color = RGBColor(255, 204, 153) # Orange for medium risk
|
538 |
-
else:
|
539 |
-
fill_color = RGBColor(198, 239, 206) # Green for low risk
|
540 |
-
|
541 |
-
for col in range(3):
|
542 |
-
table.cell(i, col).fill.solid()
|
543 |
-
table.cell(i, col).fill.fore_color.rgb = fill_color
|
544 |
-
|
545 |
-
# Add a SMART objectives slide - a generic visual we can add regardless of content
|
546 |
-
objectives_slide = prs.slides.add_slide(prs.slide_layouts[6]) # Blank layout
|
547 |
-
|
548 |
-
# Add title
|
549 |
-
title_shape = objectives_slide.shapes.add_textbox(
|
550 |
-
Inches(0.5), Inches(0.5), Inches(9), Inches(1)
|
551 |
-
)
|
552 |
-
title_frame = title_shape.text_frame
|
553 |
-
title_frame.text = "SMART Objectives Framework"
|
554 |
-
title_frame.paragraphs[0].font.size = Pt(40)
|
555 |
-
title_frame.paragraphs[0].font.bold = True
|
556 |
-
title_frame.paragraphs[0].font.color.rgb = RGBColor(0, 112, 192)
|
557 |
-
|
558 |
-
# Create a SmartArt-like diagram for SMART objectives
|
559 |
-
# Since python-pptx doesn't directly support SmartArt, we'll simulate it with shapes
|
560 |
-
|
561 |
-
# Center point for the circular layout
|
562 |
-
center_x, center_y = Inches(6.5), Inches(4)
|
563 |
-
radius = Inches(2.5)
|
564 |
-
|
565 |
-
# SMART components with colors
|
566 |
-
smart_components = [
|
567 |
-
("Specific", RGBColor(91, 155, 213)), # Blue
|
568 |
-
("Measurable", RGBColor(112, 173, 71)), # Green
|
569 |
-
("Achievable", RGBColor(237, 125, 49)), # Orange
|
570 |
-
("Relevant", RGBColor(165, 105, 189)), # Purple
|
571 |
-
("Time-bound", RGBColor(68, 114, 196)) # Dark Blue
|
572 |
-
]
|
573 |
-
|
574 |
-
# Add central circle
|
575 |
-
central_shape = objectives_slide.shapes.add_shape(
|
576 |
-
1, center_x - Inches(1), center_y - Inches(1), Inches(2), Inches(2)
|
577 |
-
)
|
578 |
-
central_shape.fill.solid()
|
579 |
-
central_shape.fill.fore_color.rgb = RGBColor(0, 112, 192)
|
580 |
-
central_shape.line.color.rgb = RGBColor(255, 255, 255)
|
581 |
-
central_shape.line.width = Pt(2)
|
582 |
-
|
583 |
-
# Add text to central circle
|
584 |
-
text_frame = central_shape.text_frame
|
585 |
-
text_frame.text = "SMART\nObjectives"
|
586 |
-
text_frame.paragraphs[0].alignment = 1 # Center
|
587 |
-
text_frame.paragraphs[0].font.color.rgb = RGBColor(255, 255, 255)
|
588 |
-
text_frame.paragraphs[0].font.size = Pt(18)
|
589 |
-
text_frame.paragraphs[0].font.bold = True
|
590 |
-
|
591 |
-
# Add surrounding circles for each SMART component
|
592 |
-
for i, (component, color) in enumerate(smart_components):
|
593 |
-
angle = (2 * 3.14159 * i) / len(smart_components)
|
594 |
-
x = center_x + radius * 0.8 * math.cos(angle) - Inches(1)
|
595 |
-
y = center_y + radius * 0.8 * math.sin(angle) - Inches(0.75)
|
596 |
-
|
597 |
-
# Component circle
|
598 |
-
component_shape = objectives_slide.shapes.add_shape(
|
599 |
-
1, x, y, Inches(2), Inches(1.5)
|
600 |
-
)
|
601 |
-
component_shape.fill.solid()
|
602 |
-
component_shape.fill.fore_color.rgb = color
|
603 |
-
component_shape.line.color.rgb = RGBColor(255, 255, 255)
|
604 |
-
component_shape.line.width = Pt(2)
|
605 |
-
|
606 |
-
# Add component text
|
607 |
-
text_frame = component_shape.text_frame
|
608 |
-
text_frame.text = component
|
609 |
-
text_frame.paragraphs[0].alignment = 1 # Center
|
610 |
-
text_frame.paragraphs[0].font.color.rgb = RGBColor(255, 255, 255)
|
611 |
-
text_frame.paragraphs[0].font.size = Pt(16)
|
612 |
-
text_frame.paragraphs[0].font.bold = True
|
613 |
-
|
614 |
-
# Add connecting line
|
615 |
-
line = objectives_slide.shapes.add_connector(
|
616 |
-
3, # Straight connector
|
617 |
-
central_shape.left + Inches(1),
|
618 |
-
central_shape.top + Inches(1),
|
619 |
-
component_shape.left + Inches(1),
|
620 |
-
component_shape.top + Inches(0.75)
|
621 |
-
)
|
622 |
-
line.line.color.rgb = RGBColor(0, 0, 0)
|
623 |
-
line.line.width = Pt(1.5)
|
624 |
-
|
625 |
-
return prs
|
626 |
-
|
627 |
-
def extract_timeline_data(proposal_text):
|
628 |
-
"""Extract timeline data from the proposal text using regex patterns"""
|
629 |
-
|
630 |
-
# Look for timeline section
|
631 |
-
timeline_section = re.search(r'(?i)Timeline.*?(?=\n\n[A-Z]|$)', proposal_text, re.DOTALL)
|
632 |
-
if not timeline_section:
|
633 |
-
# Generate example data if no real data found
|
634 |
-
return [
|
635 |
-
("Project Initiation", "Month 1"),
|
636 |
-
("Requirements Analysis", "Month 2"),
|
637 |
-
("Design Phase", "Month 3"),
|
638 |
-
("Implementation", "Month 4-5"),
|
639 |
-
("Testing", "Month 6"),
|
640 |
-
("Deployment", "Month 7"),
|
641 |
-
("Post-Implementation Review", "Month 8")
|
642 |
-
]
|
643 |
-
|
644 |
-
timeline_text = timeline_section.group(0)
|
645 |
-
|
646 |
-
# Look for milestone patterns like "Phase 1: Project Initiation (Month 1)"
|
647 |
-
# or "Project Initiation - Month 1" or "Week 1-2: Requirements Gathering"
|
648 |
-
milestone_patterns = [
|
649 |
-
r'([^:]+):\s*([^(]+)\s*\(([^)]+)\)', # Phase 1: Project Initiation (Month 1)
|
650 |
-
r'([^-]+)\s*-\s*([^(]+)', # Project Initiation - Month 1
|
651 |
-
r'((?:Week|Month)[^:]+):\s*([^(]+)', # Week 1-2: Requirements Gathering
|
652 |
-
r'([^:]+):\s*([^(]+)' # Any other pattern with colon
|
653 |
-
]
|
654 |
-
|
655 |
-
extracted_data = []
|
656 |
-
for pattern in milestone_patterns:
|
657 |
-
matches = re.finditer(pattern, timeline_text)
|
658 |
-
for match in matches:
|
659 |
-
if len(match.groups()) >= 2:
|
660 |
-
milestone = match.group(1).strip()
|
661 |
-
timeframe = match.group(2).strip()
|
662 |
-
|
663 |
-
# Clean up the milestone and timeframe
|
664 |
-
milestone = re.sub(r'^[0-9]+\.\s*', '', milestone) # Remove leading numbers
|
665 |
-
|
666 |
-
# Add to extracted data
|
667 |
-
extracted_data.append((milestone, timeframe))
|
668 |
-
|
669 |
-
# If no data extracted, create example data
|
670 |
-
if not extracted_data:
|
671 |
-
# Generate some example data
|
672 |
-
return [
|
673 |
-
("Project Initiation", "Month 1"),
|
674 |
-
("Requirements Analysis", "Month 2"),
|
675 |
-
("Design Phase", "Month 3"),
|
676 |
-
("Implementation", "Month 4-5"),
|
677 |
-
("Testing", "Month 6"),
|
678 |
-
("Deployment", "Month 7"),
|
679 |
-
("Post-Implementation Review", "Month 8")
|
680 |
-
]
|
681 |
-
|
682 |
-
return extracted_data
|
683 |
-
|
684 |
-
def extract_budget_data(proposal_text):
|
685 |
-
"""Extract budget data from the proposal text using regex patterns"""
|
686 |
-
|
687 |
-
# Look for budget section
|
688 |
-
budget_section = re.search(r'(?i)Budget.*?(?=\n\n[A-Z]|$)', proposal_text, re.DOTALL)
|
689 |
-
if not budget_section:
|
690 |
-
# Generate example data if no real data found
|
691 |
-
return [
|
692 |
-
("Hardware", 30),
|
693 |
-
("Software", 25),
|
694 |
-
("Personnel", 35),
|
695 |
-
("Training", 10),
|
696 |
-
("Contingency", 10)
|
697 |
-
]
|
698 |
-
|
699 |
-
budget_text = budget_section.group(0)
|
700 |
-
|
701 |
-
# Pattern for budget items with percentages or amounts
|
702 |
-
# e.g. "Hardware: $50,000 (20%)" or "Personnel: 35% of total budget"
|
703 |
-
budget_patterns = [
|
704 |
-
r'([^:]+):\s*\$?[\d,]+\s*\((\d+)%\)', # Hardware: $50,000 (20%)
|
705 |
-
r'([^:]+):\s*(\d+)%', # Personnel: 35%
|
706 |
-
r'([^-]+)\s*-\s*(\d+)%' # Software - 25%
|
707 |
-
]
|
708 |
-
|
709 |
-
extracted_data = []
|
710 |
-
for pattern in budget_patterns:
|
711 |
-
matches = re.finditer(pattern, budget_text)
|
712 |
-
for match in matches:
|
713 |
-
if len(match.groups()) >= 2:
|
714 |
-
category = match.group(1).strip()
|
715 |
-
percentage = int(match.group(2).strip())
|
716 |
-
|
717 |
-
# Clean up the category
|
718 |
-
category = re.sub(r'^[0-9]+\.\s*', '', category) # Remove leading numbers
|
719 |
-
|
720 |
-
# Add to extracted data
|
721 |
-
extracted_data.append((category, percentage))
|
722 |
-
|
723 |
-
# If no data extracted, look for dollar amounts instead
|
724 |
-
if not extracted_data:
|
725 |
-
# Pattern for dollar amounts: "Hardware: $50,000"
|
726 |
-
amount_pattern = r'([^:]+):\s*\$?([\d,]+)'
|
727 |
-
matches = re.finditer(amount_pattern, budget_text)
|
728 |
-
|
729 |
-
total_amount = 0
|
730 |
-
temp_data = []
|
731 |
-
|
732 |
-
for match in matches:
|
733 |
-
if len(match.groups()) >= 2:
|
734 |
-
category = match.group(1).strip()
|
735 |
-
try:
|
736 |
-
amount = int(match.group(2).replace(',', ''))
|
737 |
-
total_amount += amount
|
738 |
-
temp_data.append((category, amount))
|
739 |
-
except ValueError:
|
740 |
-
continue
|
741 |
-
|
742 |
-
# Convert absolute amounts to percentages
|
743 |
-
if total_amount > 0:
|
744 |
-
for category, amount in temp_data:
|
745 |
-
percentage = round((amount / total_amount) * 100)
|
746 |
-
extracted_data.append((category, percentage))
|
747 |
-
|
748 |
-
# If still no data extracted, create example data
|
749 |
-
if not extracted_data:
|
750 |
-
return [
|
751 |
-
("Hardware", 30),
|
752 |
-
("Software", 25),
|
753 |
-
("Personnel", 35),
|
754 |
-
("Training", 10),
|
755 |
-
("Contingency", 10)
|
756 |
-
]
|
757 |
-
|
758 |
-
return extracted_data
|
759 |
-
|
760 |
-
def extract_risk_data(proposal_text):
|
761 |
-
"""Extract risk assessment data from the proposal text"""
|
762 |
-
|
763 |
-
# Look for risk section
|
764 |
-
risk_section = re.search(r'(?i)Risk Assessment.*?(?=\n\n[A-Z]|$)', proposal_text, re.DOTALL)
|
765 |
-
if not risk_section:
|
766 |
-
# Generate example data if no real data found
|
767 |
-
return [
|
768 |
-
("Resource availability constraints", "Medium", "High"),
|
769 |
-
("Technology integration issues", "High", "Medium"),
|
770 |
-
("Budget overruns", "Medium", "High"),
|
771 |
-
("Schedule delays", "High", "Medium"),
|
772 |
-
("Stakeholder resistance", "Medium", "Medium")
|
773 |
-
]
|
774 |
-
|
775 |
-
risk_text = risk_section.group(0)
|
776 |
-
|
777 |
-
# Split into lines
|
778 |
-
lines = risk_text.split('\n')
|
779 |
-
extracted_data = []
|
780 |
-
|
781 |
-
# Look for risk items in various formats
|
782 |
-
for line in lines:
|
783 |
-
# Skip empty lines
|
784 |
-
if not line.strip():
|
785 |
-
continue
|
786 |
-
|
787 |
-
# Look for risk items with probability and impact
|
788 |
-
# Patterns:
|
789 |
-
# 1. "Risk: Resource constraints - Probability: Medium, Impact: High"
|
790 |
-
# 2. "Resource constraints (Medium probability, High impact)"
|
791 |
-
# 3. "- Resource constraints: Medium probability, High impact"
|
792 |
-
|
793 |
-
# Pattern 1
|
794 |
-
match = re.search(r'(?i)(?:Risk:)?\s*([^-]+)\s*-\s*Probability:\s*(\w+),\s*Impact:\s*(\w+)', line)
|
795 |
-
if match:
|
796 |
-
risk = match.group(1).strip()
|
797 |
-
probability = match.group(2).strip()
|
798 |
-
impact = match.group(3).strip()
|
799 |
-
extracted_data.append((risk, probability, impact))
|
800 |
-
continue
|
801 |
-
|
802 |
-
# Pattern 2
|
803 |
-
match = re.search(r'([^(]+)\s*\((\w+)\s*probability,\s*(\w+)\s*impact\)', line)
|
804 |
-
if match:
|
805 |
-
risk = match.group(1).strip()
|
806 |
-
probability = match.group(2).strip()
|
807 |
-
impact = match.group(3).strip()
|
808 |
-
extracted_data.append((risk, probability, impact))
|
809 |
-
continue
|
810 |
-
|
811 |
-
# Pattern 3
|
812 |
-
match = re.search(r'(?:-|\*)\s*([^:]+):\s*(\w+)\s*probability,\s*(\w+)\s*impact', line)
|
813 |
-
if match:
|
814 |
-
risk = match.group(1).strip()
|
815 |
-
probability = match.group(2).strip()
|
816 |
-
impact = match.group(3).strip()
|
817 |
-
extracted_data.append((risk, probability, impact))
|
818 |
-
continue
|
819 |
-
|
820 |
-
# If we couldn't extract structured data, look for bullet points and guess
|
821 |
-
if not extracted_data:
|
822 |
-
bullet_items = re.findall(r'(?:^|\n)(?:-|\*|•|\d+\.)\s*([^\n]+)', risk_text)
|
823 |
-
for item in bullet_items:
|
824 |
-
# Clean the item text
|
825 |
-
item = item.strip()
|
826 |
-
|
827 |
-
# Guess probability and impact based on keywords
|
828 |
-
probability = "Medium"
|
829 |
-
impact = "Medium"
|
830 |
-
|
831 |
-
# Check for probability indicators
|
832 |
-
if re.search(r'(?i)\b(?:high probability|likely|frequently|often|high chance)\b', item):
|
833 |
-
probability = "High"
|
834 |
-
elif re.search(r'(?i)\b(?:low probability|unlikely|rarely|seldom|small chance)\b', item):
|
835 |
-
probability = "Low"
|
836 |
-
|
837 |
-
# Check for impact indicators
|
838 |
-
if re.search(r'(?i)\b(?:high impact|severe|critical|major|significant)\b', item):
|
839 |
-
impact = "High"
|
840 |
-
elif re.search(r'(?i)\b(?:low impact|minor|minimal|negligible)\b', item):
|
841 |
-
impact = "Low"
|
842 |
-
|
843 |
-
# Extract the risk description (removing any probability/impact text)
|
844 |
-
risk = re.sub(r'(?i)\b(?:high|medium|low)(?:\s+(?:probability|impact|chance|risk))\b', '', item)
|
845 |
-
risk = re.sub(r'(?i)\b(?:likely|unlikely|critical|severe|major|minor)\b', '', risk)
|
846 |
-
risk = risk.strip()
|
847 |
-
|
848 |
-
if risk:
|
849 |
-
extracted_data.append((risk, probability, impact))
|
850 |
-
|
851 |
-
# If still no data extracted, create example data
|
852 |
-
if not extracted_data:
|
853 |
-
return [
|
854 |
-
("Resource availability constraints", "Medium", "High"),
|
855 |
-
("Technology integration issues", "High", "Medium"),
|
856 |
-
("Budget overruns", "Medium", "High"),
|
857 |
-
("Schedule delays", "High", "Medium"),
|
858 |
-
("Stakeholder resistance", "Medium", "Medium")
|
859 |
-
]
|
860 |
-
|
861 |
-
return extracted_data
|
862 |
-
|
863 |
-
def get_risk_level(text):
|
864 |
-
"""Convert text risk level to numeric value"""
|
865 |
-
text = text.lower()
|
866 |
-
if "high" in text:
|
867 |
-
return 3
|
868 |
-
elif "medium" in text:
|
869 |
-
return 2
|
870 |
-
else:
|
871 |
-
return 1 # Low or default
|
872 |
-
|
873 |
-
# ===== ANALYSIS AND UTILITY FUNCTIONS ===== #
|
874 |
-
|
875 |
-
def analyze_proposal(proposal_text):
|
876 |
-
"""Analyze the generated proposal for metrics and structure"""
|
877 |
-
# Count words
|
878 |
-
word_count = len(proposal_text.split())
|
879 |
-
|
880 |
-
# Count sections
|
881 |
-
sections = [
|
882 |
-
"Executive Summary", "Project Background", "Goals and Objectives",
|
883 |
-
"Methodology", "Timeline", "Budget", "Expected Outcomes",
|
884 |
-
"Team and Resources", "Risk Assessment", "Conclusion"
|
885 |
-
]
|
886 |
-
section_count = 0
|
887 |
-
for section in sections:
|
888 |
-
if re.search(rf'\b{section}\b', proposal_text, re.IGNORECASE):
|
889 |
-
section_count += 1
|
890 |
-
|
891 |
-
# Calculate reading time (average 200 words per minute)
|
892 |
-
reading_time_min = word_count / 200
|
893 |
-
|
894 |
-
# Calculate readability (simple algorithm based on word and sentence length)
|
895 |
-
sentences = re.split(r'[.!?]+', proposal_text)
|
896 |
-
sentence_count = len([s for s in sentences if s.strip()])
|
897 |
-
avg_sentence_length = word_count / max(1, sentence_count)
|
898 |
-
|
899 |
-
# Simplified Flesch-Kincaid calculation
|
900 |
-
readability_score = 206.835 - (1.015 * avg_sentence_length) - (84.6 * 1.5 / avg_sentence_length)
|
901 |
-
readability_score = max(0, min(100, readability_score))
|
902 |
-
|
903 |
-
# Analyze keyword presence for common business terms
|
904 |
-
keywords = {
|
905 |
-
"Strategic": proposal_text.lower().count("strategic"),
|
906 |
-
"Innovation": proposal_text.lower().count("innovat"),
|
907 |
-
"Efficiency": proposal_text.lower().count("efficien"),
|
908 |
-
"ROI": proposal_text.lower().count("roi") + proposal_text.lower().count("return on investment"),
|
909 |
-
"Stakeholder": proposal_text.lower().count("stakeholder"),
|
910 |
-
"Sustainable": proposal_text.lower().count("sustainab"),
|
911 |
-
}
|
912 |
-
|
913 |
-
# Return analysis results
|
914 |
-
return {
|
915 |
-
"word_count": word_count,
|
916 |
-
"section_count": section_count,
|
917 |
-
"readability_score": round(readability_score),
|
918 |
-
"estimated_reading_time": f"{reading_time_min:.1f} minutes",
|
919 |
-
"keyword_analysis": keywords,
|
920 |
-
"sections_present": {section: (1 if re.search(rf'\b{section}\b', proposal_text, re.IGNORECASE) else 0) for section in sections},
|
921 |
-
}
|
922 |
-
|
923 |
-
def create_slide_preview(pptx_path):
|
924 |
-
"""Create a preview image of the first slide of the presentation"""
|
925 |
-
try:
|
926 |
-
# If using Windows with COM support, try this method
|
927 |
-
try:
|
928 |
-
from pptx import Presentation
|
929 |
-
import win32com.client
|
930 |
-
import os
|
931 |
-
|
932 |
-
# Get absolute path
|
933 |
-
abs_path = os.path.abspath(pptx_path)
|
934 |
-
|
935 |
-
# Export first slide as image using PowerPoint COM object
|
936 |
-
ppt_app = win32com.client.Dispatch('PowerPoint.Application')
|
937 |
-
presentation = ppt_app.Presentations.Open(abs_path)
|
938 |
-
|
939 |
-
# Save the first slide as PNG
|
940 |
-
output_path = abs_path.replace('.pptx', '_preview.png')
|
941 |
-
presentation.Slides[1].Export(output_path, 'PNG')
|
942 |
-
|
943 |
-
# Clean up
|
944 |
-
presentation.Close()
|
945 |
-
ppt_app.Quit()
|
946 |
-
|
947 |
-
return output_path
|
948 |
-
except:
|
949 |
-
# Fallback: return the PPT file path itself as we can't generate a preview
|
950 |
-
return pptx_path
|
951 |
-
|
952 |
-
except Exception as e:
|
953 |
-
print(f"Error creating slide preview: {e}")
|
954 |
-
# Return the PPT file path itself as we can't generate a preview
|
955 |
-
return pptx_path
|
956 |
-
|
957 |
-
def load_model():
|
958 |
-
"""Load the model and tokenizer"""
|
959 |
-
print(f"Loading model: {MODEL_NAME}...")
|
960 |
-
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
|
961 |
-
model = AutoModelForCausalLM.from_pretrained(
|
962 |
-
MODEL_NAME,
|
963 |
-
torch_dtype=torch.float16 if device == "cuda" else torch.float32,
|
964 |
-
low_cpu_mem_usage=True,
|
965 |
-
device_map="auto"
|
966 |
-
)
|
967 |
-
return model, tokenizer
|
968 |
-
|
969 |
-
def process_input(title, description, temperature=0.7, max_length=4000,
|
970 |
-
color_scheme="Professional Blue", include_visuals=True,
|
971 |
-
focus_areas=None):
|
972 |
-
"""Process the input and generate both proposal and slides with advanced options"""
|
973 |
-
# Load model if not already loaded
|
974 |
-
global model, tokenizer
|
975 |
-
if 'model' not in globals() or model is None:
|
976 |
-
model, tokenizer = load_model()
|
977 |
-
|
978 |
-
# Set parameters based on input
|
979 |
-
temperature_val = float(temperature)
|
980 |
-
max_length_val = int(max_length)
|
981 |
-
|
982 |
-
# Modify prompt based on focus areas
|
983 |
-
focus_instruction = ""
|
984 |
-
if focus_areas and len(focus_areas) > 0:
|
985 |
-
focus_instruction = f"Pay special attention to these aspects: {', '.join(focus_areas)}."
|
986 |
|
987 |
# Generate the proposal
|
988 |
-
proposal = generate_proposal(
|
989 |
-
temperature=temperature_val, max_length=max_length_val)
|
990 |
|
991 |
# Create the slides
|
992 |
-
ppt_path = create_slides(proposal
|
993 |
-
|
994 |
-
# Add charts and visuals if requested
|
995 |
-
if include_visuals:
|
996 |
-
prs = Presentation(ppt_path)
|
997 |
-
prs = add_charts_and_visuals(prs, proposal, include_visuals)
|
998 |
-
prs.save(ppt_path)
|
999 |
|
1000 |
-
|
1001 |
-
preview_path = create_slide_preview(ppt_path)
|
1002 |
-
|
1003 |
-
# Analyze the proposal
|
1004 |
-
analysis = analyze_proposal(proposal)
|
1005 |
-
|
1006 |
-
return proposal, ppt_path, preview_path, analysis
|
1007 |
-
|
1008 |
-
# ===== GRADIO INTERFACE FUNCTIONS ===== #
|
1009 |
|
|
|
1010 |
def create_interface():
|
1011 |
-
"
|
1012 |
-
|
1013 |
-
# Define color schemes for PowerPoint
|
1014 |
-
color_schemes = {
|
1015 |
-
"Professional Blue": {"primary": "#0070C0", "secondary": "#00B050"},
|
1016 |
-
"Elegant Gray": {"primary": "#404040", "secondary": "#7030A0"},
|
1017 |
-
"Bold Impact": {"primary": "#C00000", "secondary": "#FFC000"},
|
1018 |
-
"Modern Green": {"primary": "#00B050", "secondary": "#5B9BD5"},
|
1019 |
-
"Corporate Purple": {"primary": "#7030A0", "secondary": "#ED7D31"}
|
1020 |
-
}
|
1021 |
-
|
1022 |
-
with gr.Blocks(title="Advanced Project Proposal Generator", theme=gr.themes.Soft()) as app:
|
1023 |
gr.Markdown("# Professional Project Proposal Generator")
|
1024 |
-
gr.Markdown("
|
1025 |
|
1026 |
with gr.Row():
|
1027 |
-
with gr.Column(scale=
|
1028 |
-
# Input section
|
1029 |
-
project_title = gr.Textbox(
|
1030 |
-
label="Project Title",
|
1031 |
-
placeholder="Enter your project title",
|
1032 |
-
value="New Project Proposal"
|
1033 |
-
)
|
1034 |
-
|
1035 |
description_input = gr.Textbox(
|
1036 |
label="Project Description",
|
1037 |
-
placeholder="Describe your project in detail
|
1038 |
lines=10
|
1039 |
)
|
|
|
1040 |
|
1041 |
-
|
1042 |
-
|
1043 |
-
|
1044 |
-
|
1045 |
-
|
1046 |
-
|
1047 |
-
|
1048 |
-
|
1049 |
-
|
1050 |
-
|
1051 |
-
|
1052 |
-
|
1053 |
-
|
1054 |
-
value=4000,
|
1055 |
-
step=500,
|
1056 |
-
label="Maximum Length"
|
1057 |
-
)
|
1058 |
-
|
1059 |
-
with gr.Row():
|
1060 |
-
color_scheme = gr.Dropdown(
|
1061 |
-
choices=list(color_schemes.keys()),
|
1062 |
-
value="Professional Blue",
|
1063 |
-
label="Presentation Style"
|
1064 |
-
)
|
1065 |
-
|
1066 |
-
include_visuals = gr.Checkbox(
|
1067 |
-
label="Include Sample Charts/Diagrams",
|
1068 |
-
value=True
|
1069 |
-
)
|
1070 |
-
|
1071 |
-
with gr.Row():
|
1072 |
-
focus_areas = gr.CheckboxGroup(
|
1073 |
-
choices=[
|
1074 |
-
"Technical Details",
|
1075 |
-
"Business Impact",
|
1076 |
-
"Implementation Plan",
|
1077 |
-
"Cost Analysis",
|
1078 |
-
"Risk Management"
|
1079 |
-
],
|
1080 |
-
value=["Business Impact", "Implementation Plan"],
|
1081 |
-
label="Focus Areas (Emphasize these aspects)"
|
1082 |
-
)
|
1083 |
-
|
1084 |
-
with gr.Row():
|
1085 |
-
clear_button = gr.Button("Clear All", variant="secondary")
|
1086 |
-
example_button = gr.Button("Load Example", variant="secondary")
|
1087 |
-
generate_button = gr.Button("Generate Proposal & Slides", variant="primary")
|
1088 |
-
|
1089 |
-
with gr.Column(scale=3):
|
1090 |
-
# Output tabs
|
1091 |
-
with gr.Tabs():
|
1092 |
-
with gr.TabItem("Proposal Text"):
|
1093 |
-
proposal_output = gr.Textbox(
|
1094 |
-
label="Generated Proposal",
|
1095 |
-
lines=25,
|
1096 |
-
show_copy_button=True
|
1097 |
-
)
|
1098 |
-
|
1099 |
-
with gr.TabItem("PowerPoint Preview"):
|
1100 |
-
gr.Markdown("#### PowerPoint Slides Preview")
|
1101 |
-
slides_preview = gr.Image(
|
1102 |
-
label="Preview (first slide)",
|
1103 |
-
type="filepath",
|
1104 |
-
height=400
|
1105 |
-
)
|
1106 |
-
slides_output = gr.File(
|
1107 |
-
label="Download Complete Presentation"
|
1108 |
-
)
|
1109 |
-
|
1110 |
-
with gr.TabItem("Proposal Analysis"):
|
1111 |
-
analysis_output = gr.JSON(
|
1112 |
-
label="Proposal Structure Analysis"
|
1113 |
-
)
|
1114 |
-
|
1115 |
-
# Example project description
|
1116 |
-
example_description = """
|
1117 |
-
Our company needs to implement a new customer relationship management (CRM) system to replace our outdated solution. The current system is 8 years old and lacks modern features like cloud integration, mobile access, and AI-powered analytics. We have approximately 5,000 customer records that need to be migrated, and 75 employees across sales, marketing, and customer service departments who will use the system. The project should include software selection, data migration, staff training, and integration with our existing ERP system. Budget constraints are significant, with a maximum allocation of $250,000. The implementation needs to be completed within 6 months to align with our fiscal year planning.
|
1118 |
-
"""
|
1119 |
-
|
1120 |
-
# Set up event handlers
|
1121 |
-
def load_example():
|
1122 |
-
return "Enterprise CRM Implementation Project", example_description
|
1123 |
-
|
1124 |
-
example_button.click(
|
1125 |
-
load_example,
|
1126 |
-
outputs=[project_title, description_input]
|
1127 |
-
)
|
1128 |
-
|
1129 |
-
clear_button.click(
|
1130 |
-
lambda: ("", ""),
|
1131 |
-
outputs=[project_title, description_input]
|
1132 |
-
)
|
1133 |
|
1134 |
generate_button.click(
|
1135 |
process_input,
|
1136 |
-
inputs=
|
1137 |
-
|
1138 |
-
description_input,
|
1139 |
-
temperature_slider,
|
1140 |
-
max_length_slider,
|
1141 |
-
color_scheme,
|
1142 |
-
include_visuals,
|
1143 |
-
focus_areas
|
1144 |
-
],
|
1145 |
-
outputs=[
|
1146 |
-
proposal_output,
|
1147 |
-
slides_output,
|
1148 |
-
slides_preview,
|
1149 |
-
analysis_output
|
1150 |
-
]
|
1151 |
)
|
1152 |
-
|
1153 |
-
|
1154 |
-
|
1155 |
-
# ===== MAIN SCRIPT CODE ===== #
|
1156 |
-
|
1157 |
-
# Global model and tokenizer
|
1158 |
-
model = None
|
1159 |
-
tokenizer = None
|
1160 |
|
1161 |
-
#
|
1162 |
if __name__ == "__main__":
|
1163 |
-
print("Loading initial model...")
|
1164 |
-
model, tokenizer = load_model()
|
1165 |
print("Starting Gradio interface...")
|
1166 |
app = create_interface()
|
1167 |
app.launch(share=True)
|
|
|
1 |
import os
|
2 |
import sys
|
3 |
import time
|
|
|
|
|
|
|
|
|
|
|
4 |
import gradio as gr
|
5 |
+
from openai import OpenAI
|
|
|
6 |
from pptx import Presentation
|
7 |
+
from pptx.util import Pt
|
8 |
+
from dotenv import load_dotenv
|
9 |
+
|
10 |
+
# Load environment variables
|
11 |
+
load_dotenv()
|
12 |
+
|
13 |
+
# Get API key from environment variable
|
14 |
+
OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY")
|
15 |
+
if not OPENROUTER_API_KEY:
|
16 |
+
raise ValueError("OPENROUTER_API_KEY environment variable is not set")
|
17 |
+
|
18 |
+
# OpenRouter API configuration
|
19 |
+
MODEL_NAME = "meta-llama/llama-3.3-8b-instruct:free"
|
20 |
+
SITE_URL = "https://proposal-generator.io" # Replace with your actual site URL
|
21 |
+
SITE_NAME = "Professional Proposal Generator" # Replace with your actual site name
|
22 |
+
|
23 |
+
# Initialize OpenAI client for OpenRouter
|
24 |
+
client = OpenAI(
|
25 |
+
base_url="https://openrouter.ai/api/v1",
|
26 |
+
api_key=OPENROUTER_API_KEY,
|
27 |
+
)
|
28 |
+
|
29 |
+
def generate_proposal(description):
|
30 |
+
"""Generate a proposal from a description using OpenRouter API"""
|
31 |
+
prompt = f"""Create a detailed project proposal with these sections:
|
32 |
+
1. Executive Summary
|
33 |
+
2. Project Background
|
34 |
+
3. Goals and Objectives
|
35 |
+
4. Methodology and Approach
|
36 |
+
5. Timeline
|
37 |
+
6. Budget Considerations
|
38 |
+
7. Expected Outcomes
|
39 |
+
8. Team and Resources
|
40 |
+
9. Risk Assessment
|
41 |
+
10. Conclusion
|
42 |
+
|
43 |
+
Include specific details in each section.
|
44 |
+
|
45 |
+
Project Description: {description}
|
46 |
+
|
47 |
+
Complete Project Proposal:"""
|
48 |
|
49 |
+
try:
|
50 |
+
completion = client.chat.completions.create(
|
51 |
+
extra_headers={
|
52 |
+
"HTTP-Referer": SITE_URL, # Optional. Site URL for rankings on openrouter.ai.
|
53 |
+
"X-Title": SITE_NAME, # Optional. Site title for rankings on openrouter.ai.
|
54 |
+
},
|
55 |
+
model=MODEL_NAME,
|
56 |
+
messages=[
|
57 |
+
{
|
58 |
+
"role": "system",
|
59 |
+
"content": "You are a professional business proposal writer with expertise in creating detailed, well-structured project proposals."
|
60 |
+
},
|
61 |
+
{
|
62 |
+
"role": "user",
|
63 |
+
"content": prompt
|
64 |
+
}
|
65 |
+
],
|
66 |
+
temperature=0.7,
|
67 |
+
max_tokens=4000
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
68 |
)
|
69 |
+
|
70 |
+
proposal = completion.choices[0].message.content
|
71 |
+
return proposal
|
72 |
|
73 |
+
except Exception as e:
|
74 |
+
print(f"Error generating proposal: {e}")
|
75 |
+
return f"Error generating proposal: {e}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
76 |
|
77 |
+
def create_slides(proposal):
|
78 |
+
"""Create PowerPoint slides from the proposal"""
|
|
|
|
|
79 |
prs = Presentation()
|
|
|
|
|
80 |
|
81 |
+
# Add title slide
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
82 |
title_slide = prs.slides.add_slide(prs.slide_layouts[0])
|
83 |
+
title_slide.shapes.title.text = "Project Proposal"
|
|
|
|
|
|
|
|
|
|
|
84 |
subtitle = title_slide.placeholders[1]
|
85 |
+
subtitle.text = "Generated with AI"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
86 |
|
87 |
# List of sections to look for
|
88 |
sections = [
|
89 |
"Executive Summary",
|
90 |
"Project Background",
|
91 |
"Goals and Objectives",
|
92 |
+
"Methodology",
|
93 |
"Timeline",
|
94 |
+
"Budget",
|
95 |
"Expected Outcomes",
|
96 |
"Team and Resources",
|
97 |
"Risk Assessment",
|
98 |
"Conclusion"
|
99 |
]
|
100 |
|
101 |
+
# Split text into paragraphs
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
102 |
paragraphs = proposal.split('\n\n')
|
103 |
|
104 |
# Process each paragraph
|
|
|
114 |
# Check if this is a section header
|
115 |
is_header = False
|
116 |
for section in sections:
|
117 |
+
if section.lower() in para.lower() and len(para) < 100:
|
|
|
|
|
118 |
# Save previous section
|
119 |
if current_section and current_content:
|
120 |
found_sections.append((current_section, current_content))
|
121 |
|
122 |
# Start new section
|
123 |
+
current_section = para
|
124 |
current_content = []
|
125 |
is_header = True
|
126 |
break
|
|
|
133 |
found_sections.append((current_section, current_content))
|
134 |
|
135 |
# Create slides for each section
|
136 |
+
for title, content_paras in found_sections:
|
137 |
+
# Section title slide
|
138 |
section_slide = prs.slides.add_slide(prs.slide_layouts[2])
|
139 |
section_slide.shapes.title.text = title
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
140 |
|
141 |
+
# Content slides
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
142 |
current_slide = None
|
|
|
143 |
paragraphs_on_slide = 0
|
144 |
|
145 |
+
for para in content_paras:
|
|
|
|
|
|
|
|
|
146 |
# Start a new slide if needed
|
147 |
+
if current_slide is None or paragraphs_on_slide >= 5:
|
|
|
|
|
148 |
current_slide = prs.slides.add_slide(prs.slide_layouts[1])
|
149 |
current_slide.shapes.title.text = title
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
150 |
text_frame = current_slide.placeholders[1].text_frame
|
|
|
151 |
paragraphs_on_slide = 0
|
152 |
+
else:
|
153 |
+
text_frame.add_paragraph() # Add a blank line between paragraphs
|
154 |
|
155 |
+
# Add the paragraph
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
156 |
p = text_frame.add_paragraph()
|
157 |
+
p.text = para
|
158 |
|
159 |
+
# Basic formatting based on content
|
160 |
+
if para.startswith("-") or para.startswith("*"):
|
|
|
|
|
|
|
|
|
|
|
161 |
p.level = 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
162 |
|
163 |
paragraphs_on_slide += 1
|
164 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
165 |
# Save the presentation
|
166 |
+
output_path = "proposal_slides.pptx"
|
167 |
prs.save(output_path)
|
168 |
return output_path
|
169 |
|
170 |
+
def process_input(description):
|
171 |
+
"""Process the input and generate both proposal and slides"""
|
172 |
+
# Check if input is too short
|
173 |
+
if len(description.strip()) < 10:
|
174 |
+
return "Please provide a more detailed project description (at least 10 characters).", None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
175 |
|
176 |
# Generate the proposal
|
177 |
+
proposal = generate_proposal(description)
|
|
|
178 |
|
179 |
# Create the slides
|
180 |
+
ppt_path = create_slides(proposal)
|
|
|
|
|
|
|
|
|
|
|
|
|
181 |
|
182 |
+
return proposal, ppt_path
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
183 |
|
184 |
+
# Create Gradio interface
|
185 |
def create_interface():
|
186 |
+
with gr.Blocks(title="Professional Project Proposal Generator") as app:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
187 |
gr.Markdown("# Professional Project Proposal Generator")
|
188 |
+
gr.Markdown("Enter a project description to generate a comprehensive proposal and presentation slides using Meta Llama 3.3.")
|
189 |
|
190 |
with gr.Row():
|
191 |
+
with gr.Column(scale=1):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
192 |
description_input = gr.Textbox(
|
193 |
label="Project Description",
|
194 |
+
placeholder="Describe your project in detail...",
|
195 |
lines=10
|
196 |
)
|
197 |
+
generate_button = gr.Button("Generate Proposal", variant="primary")
|
198 |
|
199 |
+
# Examples
|
200 |
+
examples = gr.Examples(
|
201 |
+
examples=[
|
202 |
+
"Develop a cloud-based SaaS platform for performance evaluation in educational institutions and corporate environments.",
|
203 |
+
"Create a mobile application for sustainable waste management and recycling in urban communities.",
|
204 |
+
"Design and implement a smart agriculture system using IoT sensors for small-scale farms."
|
205 |
+
],
|
206 |
+
inputs=description_input
|
207 |
+
)
|
208 |
+
|
209 |
+
with gr.Column(scale=2):
|
210 |
+
proposal_output = gr.Textbox(label="Generated Proposal", lines=20)
|
211 |
+
slides_output = gr.File(label="PowerPoint Slides")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
212 |
|
213 |
generate_button.click(
|
214 |
process_input,
|
215 |
+
inputs=description_input,
|
216 |
+
outputs=[proposal_output, slides_output]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
217 |
)
|
218 |
+
|
219 |
+
return app
|
|
|
|
|
|
|
|
|
|
|
|
|
220 |
|
221 |
+
# Main script code
|
222 |
if __name__ == "__main__":
|
|
|
|
|
223 |
print("Starting Gradio interface...")
|
224 |
app = create_interface()
|
225 |
app.launch(share=True)
|