Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -8,13 +8,12 @@ from pptx.util import Pt, Inches
|
|
8 |
from pptx.enum.text import PP_ALIGN
|
9 |
from pptx.dml.color import RGBColor
|
10 |
from reportlab.lib.pagesizes import letter
|
11 |
-
from reportlab.
|
12 |
-
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
13 |
-
from reportlab.lib import colors
|
14 |
from reportlab.lib.units import inch
|
15 |
-
from reportlab.lib
|
16 |
from dotenv import load_dotenv
|
17 |
import re
|
|
|
18 |
|
19 |
# Load environment variables
|
20 |
load_dotenv()
|
@@ -39,68 +38,216 @@ def generate_proposal(description, project_type=None):
|
|
39 |
"""Generate a proposal from a description using OpenRouter API"""
|
40 |
|
41 |
# Create a better prompt with example formatting and detailed instructions
|
42 |
-
system_prompt = """You are a professional
|
43 |
|
44 |
-
Your
|
45 |
|
46 |
Make sure to:
|
47 |
1. Include proper formatting with section headings in bold
|
48 |
-
2.
|
49 |
-
3.
|
50 |
-
4. Provide concrete, specific details rather than generic statements
|
51 |
-
5. Maintain a professional tone throughout
|
52 |
-
6.
|
|
|
|
|
53 |
|
54 |
-
The
|
55 |
"""
|
56 |
|
57 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
58 |
|
59 |
-
**
|
60 |
-
A
|
|
|
|
|
|
|
61 |
|
62 |
-
**
|
63 |
-
|
64 |
|
65 |
-
**
|
66 |
-
Clearly separated primary goals (broader aims) and specific objectives (measurable targets). Use bullet points for clarity, with each objective being SMART (Specific, Measurable, Achievable, Relevant, Time-bound).
|
67 |
|
68 |
-
**
|
69 |
-
|
|
|
|
|
70 |
|
71 |
-
**
|
72 |
-
|
|
|
|
|
73 |
|
74 |
-
**
|
75 |
-
|
|
|
|
|
76 |
|
77 |
-
**
|
78 |
-
|
|
|
|
|
79 |
|
80 |
-
**
|
81 |
-
|
|
|
|
|
82 |
|
83 |
-
**
|
84 |
-
|
|
|
|
|
85 |
|
86 |
-
**
|
87 |
-
|
|
|
|
|
88 |
|
89 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
90 |
project_specific_prompt = ""
|
91 |
if project_type == "saas_performance":
|
92 |
-
project_specific_prompt = """
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
-
|
98 |
-
-
|
99 |
-
-
|
100 |
-
-
|
101 |
-
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
102 |
|
103 |
-
prompt = f"""Create a detailed
|
104 |
|
105 |
{example_format}
|
106 |
|
@@ -108,7 +255,8 @@ Project Description: {description}
|
|
108 |
|
109 |
{project_specific_prompt}
|
110 |
|
111 |
-
Create a complete, professionally formatted
|
|
|
112 |
|
113 |
try:
|
114 |
completion = client.chat.completions.create(
|
@@ -127,7 +275,7 @@ Create a complete, professionally formatted project proposal that could be prese
|
|
127 |
"content": prompt
|
128 |
}
|
129 |
],
|
130 |
-
temperature=0.
|
131 |
max_tokens=4500
|
132 |
)
|
133 |
|
@@ -140,18 +288,23 @@ Create a complete, professionally formatted project proposal that could be prese
|
|
140 |
|
141 |
def extract_title(proposal):
|
142 |
"""Extract the title from the proposal"""
|
143 |
-
# Try to match the exact format first
|
144 |
title_match = re.search(r"\*\*Project Proposal: (.*?)\*\*", proposal)
|
145 |
if title_match:
|
146 |
return title_match.group(1)
|
147 |
|
|
|
|
|
|
|
|
|
|
|
148 |
# Fallback pattern for more flexibility
|
149 |
-
title_match = re.search(r"\*\*\s*(?:Project Proposal|Proposal|Title):\s*(.*?)\s*\*\*", proposal, re.IGNORECASE)
|
150 |
if title_match:
|
151 |
return title_match.group(1)
|
152 |
|
153 |
# If no title found, use a generic one
|
154 |
-
return "
|
155 |
|
156 |
def create_pdf(proposal, output_path="proposal.pdf"):
|
157 |
"""Create a PDF document from the proposal"""
|
@@ -241,78 +394,106 @@ def create_slides(proposal):
|
|
241 |
|
242 |
# Add title slide
|
243 |
title_slide = prs.slides.add_slide(title_slide_layout)
|
244 |
-
title_slide.shapes.title.text =
|
245 |
subtitle = title_slide.placeholders[1]
|
246 |
-
subtitle.text = "
|
247 |
|
248 |
-
#
|
249 |
-
sections = [
|
250 |
-
|
251 |
-
|
252 |
-
"Goals and Objectives",
|
253 |
-
"Methodology and Approach",
|
254 |
-
"Timeline",
|
255 |
-
"Budget Considerations",
|
256 |
-
"Expected Outcomes",
|
257 |
-
"Team and Resources",
|
258 |
-
"Risk Assessment",
|
259 |
-
"Conclusion"
|
260 |
-
]
|
261 |
|
262 |
-
#
|
263 |
-
|
264 |
-
proposal, re.DOTALL)
|
265 |
|
266 |
-
for
|
267 |
-
|
268 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
269 |
|
270 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
271 |
section_slide = prs.slides.add_slide(section_title_layout)
|
272 |
section_slide.shapes.title.text = section_title
|
273 |
|
274 |
-
#
|
275 |
-
paragraphs = section_content.split('\n\n')
|
276 |
-
|
277 |
-
# Process each paragraph
|
278 |
current_slide = None
|
279 |
text_frame = None
|
280 |
-
|
281 |
|
282 |
-
for
|
283 |
-
para = para.strip()
|
284 |
-
if not para:
|
285 |
-
continue
|
286 |
-
|
287 |
# Start a new slide if needed
|
288 |
-
if current_slide is None or
|
289 |
current_slide = prs.slides.add_slide(content_layout)
|
290 |
current_slide.shapes.title.text = section_title
|
291 |
text_frame = current_slide.placeholders[1].text_frame
|
292 |
-
|
293 |
|
294 |
-
|
295 |
-
|
296 |
-
|
297 |
-
|
298 |
-
|
299 |
-
|
300 |
-
|
301 |
-
|
302 |
-
|
303 |
-
|
304 |
-
|
305 |
-
|
306 |
-
|
307 |
-
bullet_p.text = item
|
308 |
-
bullet_p.level = 1
|
309 |
-
else:
|
310 |
-
p.text = para
|
311 |
|
312 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
313 |
|
314 |
# Save the presentation
|
315 |
-
output_path = "
|
316 |
prs.save(output_path)
|
317 |
return output_path
|
318 |
|
@@ -326,90 +507,133 @@ def create_slides(proposal):
|
|
326 |
subtitle = slide.placeholders[1]
|
327 |
subtitle.text = f"An error occurred: {str(e)}\nPlease try again with a different project description."
|
328 |
|
329 |
-
output_path = "
|
330 |
prs.save(output_path)
|
331 |
except:
|
332 |
# If even the error presentation fails, return a default path
|
333 |
pass
|
334 |
return output_path
|
335 |
|
336 |
-
def
|
337 |
-
"""
|
338 |
try:
|
339 |
-
|
340 |
-
|
341 |
-
|
|
|
342 |
|
343 |
-
#
|
344 |
-
|
345 |
-
|
346 |
-
type_value = "saas_performance"
|
347 |
|
348 |
-
#
|
349 |
-
|
|
|
350 |
|
351 |
-
#
|
352 |
-
|
353 |
-
|
354 |
-
|
355 |
-
|
356 |
-
|
357 |
-
|
358 |
-
|
359 |
-
|
360 |
-
|
361 |
-
|
362 |
-
|
363 |
-
|
364 |
-
|
365 |
-
|
366 |
-
|
367 |
-
|
368 |
-
|
369 |
-
lines = proposal.split('\n')
|
370 |
-
for line in lines:
|
371 |
-
# Check for page break
|
372 |
-
if y < 50:
|
373 |
-
c.showPage()
|
374 |
-
y = height - 50
|
375 |
|
376 |
-
|
377 |
-
|
378 |
-
|
379 |
-
|
380 |
-
|
381 |
-
|
382 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
383 |
|
384 |
-
# Draw the
|
385 |
-
|
386 |
-
|
|
|
|
|
387 |
y -= line_height
|
388 |
-
|
389 |
-
|
390 |
-
|
391 |
-
|
392 |
-
|
393 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
394 |
|
395 |
-
#
|
396 |
-
|
397 |
-
|
398 |
-
except Exception as ppt_error:
|
399 |
-
print(f"Error creating slides: {ppt_error}")
|
400 |
-
ppt_path = None
|
401 |
|
402 |
-
|
|
|
403 |
except Exception as e:
|
404 |
-
|
405 |
-
|
406 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
407 |
|
408 |
# Create Gradio interface
|
409 |
def create_interface():
|
410 |
-
with gr.Blocks(title="
|
411 |
-
gr.Markdown("#
|
412 |
-
gr.Markdown("Generate comprehensive, professionally formatted
|
413 |
|
414 |
with gr.Row():
|
415 |
with gr.Column(scale=1):
|
@@ -422,10 +646,10 @@ def create_interface():
|
|
422 |
project_type = gr.Dropdown(
|
423 |
label="Project Type",
|
424 |
choices=["General Project", "SaaS Performance Platform"],
|
425 |
-
value="
|
426 |
)
|
427 |
|
428 |
-
generate_button = gr.Button("Generate
|
429 |
|
430 |
# Examples
|
431 |
examples = gr.Examples(
|
@@ -440,8 +664,8 @@ def create_interface():
|
|
440 |
with gr.Column(scale=2):
|
441 |
output_tabs = gr.Tabs()
|
442 |
with output_tabs:
|
443 |
-
with gr.TabItem("
|
444 |
-
proposal_output = gr.Textbox(label="Generated
|
445 |
with gr.TabItem("Documents"):
|
446 |
with gr.Row():
|
447 |
pdf_output = gr.File(label="PDF Document")
|
|
|
8 |
from pptx.enum.text import PP_ALIGN
|
9 |
from pptx.dml.color import RGBColor
|
10 |
from reportlab.lib.pagesizes import letter
|
11 |
+
from reportlab.pdfgen import canvas
|
|
|
|
|
12 |
from reportlab.lib.units import inch
|
13 |
+
from reportlab.lib import colors
|
14 |
from dotenv import load_dotenv
|
15 |
import re
|
16 |
+
import textwrap
|
17 |
|
18 |
# Load environment variables
|
19 |
load_dotenv()
|
|
|
38 |
"""Generate a proposal from a description using OpenRouter API"""
|
39 |
|
40 |
# Create a better prompt with example formatting and detailed instructions
|
41 |
+
system_prompt = """You are a professional technical documentation writer with expertise in creating detailed, well-structured requirements and proposal documents.
|
42 |
|
43 |
+
Your documents are comprehensive, highly detailed, and follow a clear structure with proper formatting. Each section should include substantial content with specific details, bullet points, and professional technical language.
|
44 |
|
45 |
Make sure to:
|
46 |
1. Include proper formatting with section headings in bold
|
47 |
+
2. Use bullet points (● symbol) for listing features and requirements
|
48 |
+
3. Organize content into logical sections and subsections
|
49 |
+
4. Provide concrete, specific technical details rather than generic statements
|
50 |
+
5. Maintain a professional, technical tone throughout
|
51 |
+
6. DO NOT include timelines, budgets, or pricing information
|
52 |
+
7. Format the document to look like a formal technical requirements document
|
53 |
+
8. Focus on functional requirements, technical specifications, and feature details
|
54 |
|
55 |
+
The document must be highly detailed, professionally formatted, and ready for presentation to technical stakeholders.
|
56 |
"""
|
57 |
|
58 |
+
# For SaaS Performance Platform, use a specific template based on the provided document
|
59 |
+
if project_type == "saas_performance":
|
60 |
+
example_format = """**[Title based on description]**
|
61 |
+
|
62 |
+
**Introduction:**
|
63 |
+
A comprehensive explanation of the project goals, purpose, and overview in 2-3 paragraphs. Describe the key benefits and intended outcomes without mentioning timelines or budgets.
|
64 |
+
|
65 |
+
**Functional Requirements:**
|
66 |
+
|
67 |
+
**User Authentication and Role Management:**
|
68 |
+
● [Feature point 1]
|
69 |
+
● [Feature point 2]
|
70 |
+
● [Feature point 3]
|
71 |
+
|
72 |
+
**Dashboard and Analytics:**
|
73 |
+
● [Feature point 1]
|
74 |
+
● [Feature point 2]
|
75 |
+
● [Feature point 3]
|
76 |
+
|
77 |
+
**Evaluation Framework Builder:**
|
78 |
+
● [Feature point 1]
|
79 |
+
● [Feature point 2]
|
80 |
+
● [Feature point 3]
|
81 |
+
|
82 |
+
**Data Input and Rating Engine:**
|
83 |
+
● [Feature point 1]
|
84 |
+
● [Feature point 2]
|
85 |
+
● [Feature point 3]
|
86 |
+
|
87 |
+
**Progress Tracking Module:**
|
88 |
+
● [Feature point 1]
|
89 |
+
● [Feature point 2]
|
90 |
+
● [Feature point 3]
|
91 |
+
|
92 |
+
**Reporting and Exporting:**
|
93 |
+
● [Feature point 1]
|
94 |
+
● [Feature point 2]
|
95 |
+
● [Feature point 3]
|
96 |
+
|
97 |
+
**Notifications:**
|
98 |
+
● [Feature point 1]
|
99 |
+
|
100 |
+
**Permission and Access Management:**
|
101 |
+
● [Feature point 1]
|
102 |
+
● [Feature point 2]
|
103 |
+
|
104 |
+
**Audit Log and Activity Tracking:**
|
105 |
+
● [Feature point 1]
|
106 |
+
● [Feature point 2]
|
107 |
+
|
108 |
+
**Modules:**
|
109 |
+
● [Module 1]
|
110 |
+
● [Module 2]
|
111 |
+
● [Module 3]
|
112 |
+
● [Module 4]
|
113 |
+
● [Module 5]
|
114 |
+
● [Module 6]
|
115 |
+
● [Module 7]
|
116 |
+
● [Module 8]
|
117 |
+
|
118 |
+
**User Roles and Permissions:**
|
119 |
+
|
120 |
+
**[Role Type 1]:**
|
121 |
+
● [Permission 1]
|
122 |
+
● [Permission 2]
|
123 |
+
|
124 |
+
**[Role Type 2]:**
|
125 |
+
● [Permission 1]
|
126 |
+
● [Permission 2]
|
127 |
+
● [Permission 3]
|
128 |
+
|
129 |
+
**[Role Type 3]:**
|
130 |
+
● [Permission 1]
|
131 |
+
● [Permission 2]
|
132 |
+
|
133 |
+
**[Role Type 4]:**
|
134 |
+
● [Permission 1]
|
135 |
+
● [Permission 2]
|
136 |
+
● [Permission 3]
|
137 |
+
|
138 |
+
**Technology Stack:**
|
139 |
+
● [Technology Category 1]: [Specific technologies]
|
140 |
+
● [Technology Category 2]: [Specific technologies]
|
141 |
+
● [Technology Category 3]: [Specific technologies]
|
142 |
+
● [Technology Category 4]: [Specific technologies]
|
143 |
+
● [Technology Category 5]: [Specific technologies]
|
144 |
+
● [Technology Category 6]: [Specific technologies]
|
145 |
+
● [Technology Category 7]: [Specific technologies]
|
146 |
+
● [Technology Category 8]: [Specific technologies]
|
147 |
+
● [Technology Category 9]: [Specific technologies]
|
148 |
+
● [Technology Category 10]: [Specific technologies]
|
149 |
|
150 |
+
**Conclusion:**
|
151 |
+
A final summary of the proposed system and its benefits in 1-2 paragraphs. Emphasize how the solution will meet the needs described without mentioning timelines or costs."""
|
152 |
+
else:
|
153 |
+
# General format for other project types
|
154 |
+
example_format = """**[Title based on description]**
|
155 |
|
156 |
+
**Introduction:**
|
157 |
+
A comprehensive explanation of the project goals, purpose, and overview in 2-3 paragraphs. Describe the key benefits and intended outcomes without mentioning timelines or budgets.
|
158 |
|
159 |
+
**Functional Requirements:**
|
|
|
160 |
|
161 |
+
**[Key Feature Area 1]:**
|
162 |
+
● [Feature point 1]
|
163 |
+
● [Feature point 2]
|
164 |
+
● [Feature point 3]
|
165 |
|
166 |
+
**[Key Feature Area 2]:**
|
167 |
+
● [Feature point 1]
|
168 |
+
● [Feature point 2]
|
169 |
+
● [Feature point 3]
|
170 |
|
171 |
+
**[Key Feature Area 3]:**
|
172 |
+
● [Feature point 1]
|
173 |
+
● [Feature point 2]
|
174 |
+
● [Feature point 3]
|
175 |
|
176 |
+
**[Key Feature Area 4]:**
|
177 |
+
● [Feature point 1]
|
178 |
+
● [Feature point 2]
|
179 |
+
● [Feature point 3]
|
180 |
|
181 |
+
**System Architecture:**
|
182 |
+
● [Architecture Component 1]
|
183 |
+
● [Architecture Component 2]
|
184 |
+
● [Architecture Component 3]
|
185 |
|
186 |
+
**Technical Specifications:**
|
187 |
+
● [Technical Requirement 1]
|
188 |
+
● [Technical Requirement 2]
|
189 |
+
● [Technical Requirement 3]
|
190 |
|
191 |
+
**Security Requirements:**
|
192 |
+
● [Security Feature 1]
|
193 |
+
● [Security Feature 2]
|
194 |
+
● [Security Feature 3]
|
195 |
|
196 |
+
**Integration Requirements:**
|
197 |
+
● [Integration Point 1]
|
198 |
+
● [Integration Point 2]
|
199 |
+
● [Integration Point 3]
|
200 |
+
|
201 |
+
**User Types and Permissions:**
|
202 |
+
|
203 |
+
**[User Type 1]:**
|
204 |
+
● [Permission 1]
|
205 |
+
● [Permission 2]
|
206 |
+
|
207 |
+
**[User Type 2]:**
|
208 |
+
● [Permission 1]
|
209 |
+
● [Permission 2]
|
210 |
+
|
211 |
+
**[User Type 3]:**
|
212 |
+
● [Permission 1]
|
213 |
+
● [Permission 2]
|
214 |
+
|
215 |
+
**Technology Stack:**
|
216 |
+
● [Technology Area 1]: [Specific technologies]
|
217 |
+
● [Technology Area 2]: [Specific technologies]
|
218 |
+
● [Technology Area 3]: [Specific technologies]
|
219 |
+
● [Technology Area 4]: [Specific technologies]
|
220 |
+
|
221 |
+
**Conclusion:**
|
222 |
+
A final summary of the proposed system and its benefits in 1-2 paragraphs. Emphasize how the solution will meet the needs described without mentioning timelines or costs."""
|
223 |
+
|
224 |
+
# Create a project type specific instruction
|
225 |
project_specific_prompt = ""
|
226 |
if project_type == "saas_performance":
|
227 |
+
project_specific_prompt = """
|
228 |
+
Format the document to precisely match the example provided. For this SaaS Performance Evaluation Platform:
|
229 |
+
|
230 |
+
1. DO NOT include any sections about budget, timeline, team structure, or pricing
|
231 |
+
2. Structure the document exactly like the provided example with these specific sections:
|
232 |
+
- Introduction
|
233 |
+
- Functional Requirements (with all subsections as shown)
|
234 |
+
- User Roles and Permissions (with all user types)
|
235 |
+
- Technology Stack
|
236 |
+
- Conclusion
|
237 |
+
3. Use bullet points with the ● symbol for all feature lists
|
238 |
+
4. Include specific technical details about:
|
239 |
+
- Multi-tenant architecture with role-based access control
|
240 |
+
- User authentication and authorization
|
241 |
+
- Dashboard and analytics features with visualization options
|
242 |
+
- Evaluation framework capabilities
|
243 |
+
- Data input methods and rating systems
|
244 |
+
- Reporting and exporting functions
|
245 |
+
- Specific technology recommendations
|
246 |
+
5. Maintain a formal, technical tone throughout the document
|
247 |
+
6. Format all section headings in bold with a colon, like: "**Section Name:**"
|
248 |
+
"""
|
249 |
|
250 |
+
prompt = f"""Create a detailed technical requirements document based on the following description. Format it exactly like the example format provided, with bold section headings and proper structure.
|
251 |
|
252 |
{example_format}
|
253 |
|
|
|
255 |
|
256 |
{project_specific_prompt}
|
257 |
|
258 |
+
Create a complete, professionally formatted technical requirements document that resembles a formal functional specification. DO NOT include any sections about budget, timeline, team structure, or pricing. Focus entirely on functional and technical requirements.
|
259 |
+
"""
|
260 |
|
261 |
try:
|
262 |
completion = client.chat.completions.create(
|
|
|
275 |
"content": prompt
|
276 |
}
|
277 |
],
|
278 |
+
temperature=0.4, # Lower temperature for more consistent, formal results
|
279 |
max_tokens=4500
|
280 |
)
|
281 |
|
|
|
288 |
|
289 |
def extract_title(proposal):
|
290 |
"""Extract the title from the proposal"""
|
291 |
+
# Try to match the exact format first - standard format
|
292 |
title_match = re.search(r"\*\*Project Proposal: (.*?)\*\*", proposal)
|
293 |
if title_match:
|
294 |
return title_match.group(1)
|
295 |
|
296 |
+
# Try to match the requirements document format - just the title
|
297 |
+
title_match = re.search(r"\*\*(.*?)\*\*", proposal)
|
298 |
+
if title_match:
|
299 |
+
return title_match.group(1)
|
300 |
+
|
301 |
# Fallback pattern for more flexibility
|
302 |
+
title_match = re.search(r"\*\*\s*(?:Project Proposal|Proposal|Title|Requirements Document):\s*(.*?)\s*\*\*", proposal, re.IGNORECASE)
|
303 |
if title_match:
|
304 |
return title_match.group(1)
|
305 |
|
306 |
# If no title found, use a generic one
|
307 |
+
return "Requirements Document"
|
308 |
|
309 |
def create_pdf(proposal, output_path="proposal.pdf"):
|
310 |
"""Create a PDF document from the proposal"""
|
|
|
394 |
|
395 |
# Add title slide
|
396 |
title_slide = prs.slides.add_slide(title_slide_layout)
|
397 |
+
title_slide.shapes.title.text = title
|
398 |
subtitle = title_slide.placeholders[1]
|
399 |
+
subtitle.text = "Requirements Document"
|
400 |
|
401 |
+
# Parse the document into sections
|
402 |
+
sections = []
|
403 |
+
current_section = None
|
404 |
+
current_content = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
405 |
|
406 |
+
# Split into paragraphs
|
407 |
+
paragraphs = proposal.split('\n\n')
|
|
|
408 |
|
409 |
+
for para in paragraphs:
|
410 |
+
para = para.strip()
|
411 |
+
if not para:
|
412 |
+
continue
|
413 |
+
|
414 |
+
# Check if it's a main section header
|
415 |
+
if para.startswith('**') and para.endswith('**'):
|
416 |
+
# Save previous section if exists
|
417 |
+
if current_section and current_content:
|
418 |
+
sections.append((current_section, current_content))
|
419 |
+
|
420 |
+
# Start new section
|
421 |
+
current_section = para.replace('**', '')
|
422 |
+
current_content = []
|
423 |
|
424 |
+
# Check if it's a subsection header
|
425 |
+
elif para.startswith('**') and '**:' in para:
|
426 |
+
parts = para.split('**:', 1)
|
427 |
+
if len(parts) == 2:
|
428 |
+
# Add as subsection with special formatting
|
429 |
+
header = parts[0].replace('**', '') + ':'
|
430 |
+
content = parts[1].strip()
|
431 |
+
current_content.append(("subsection", header, content if content else []))
|
432 |
+
|
433 |
+
# Regular content or bullet points
|
434 |
+
else:
|
435 |
+
current_content.append(("content", para))
|
436 |
+
|
437 |
+
# Add the last section
|
438 |
+
if current_section and current_content:
|
439 |
+
sections.append((current_section, current_content))
|
440 |
+
|
441 |
+
# Create slides for each section
|
442 |
+
for section_title, contents in sections:
|
443 |
+
# Skip the first section if it's the title
|
444 |
+
if section_title.lower() == title.lower():
|
445 |
+
continue
|
446 |
+
|
447 |
+
# Section title slide
|
448 |
section_slide = prs.slides.add_slide(section_title_layout)
|
449 |
section_slide.shapes.title.text = section_title
|
450 |
|
451 |
+
# Content slides
|
|
|
|
|
|
|
452 |
current_slide = None
|
453 |
text_frame = None
|
454 |
+
items_on_slide = 0
|
455 |
|
456 |
+
for content_type, *content_data in contents:
|
|
|
|
|
|
|
|
|
457 |
# Start a new slide if needed
|
458 |
+
if current_slide is None or items_on_slide >= 6:
|
459 |
current_slide = prs.slides.add_slide(content_layout)
|
460 |
current_slide.shapes.title.text = section_title
|
461 |
text_frame = current_slide.placeholders[1].text_frame
|
462 |
+
items_on_slide = 0
|
463 |
|
464 |
+
if content_type == "subsection":
|
465 |
+
# Add subsection header
|
466 |
+
subsection_header, subsection_content = content_data
|
467 |
+
p = text_frame.add_paragraph()
|
468 |
+
p.text = subsection_header
|
469 |
+
p.font.bold = True
|
470 |
+
items_on_slide += 1
|
471 |
+
|
472 |
+
# Add subsection content if any
|
473 |
+
if subsection_content and isinstance(subsection_content, str):
|
474 |
+
p = text_frame.add_paragraph()
|
475 |
+
p.text = subsection_content
|
476 |
+
items_on_slide += 1
|
|
|
|
|
|
|
|
|
477 |
|
478 |
+
elif content_type == "content":
|
479 |
+
para_text = content_data[0]
|
480 |
+
|
481 |
+
# Check if it's a bullet list
|
482 |
+
if para_text.startswith('●'):
|
483 |
+
lines = para_text.split('\n')
|
484 |
+
for line in lines:
|
485 |
+
if line.strip():
|
486 |
+
p = text_frame.add_paragraph()
|
487 |
+
p.text = line.strip().replace('●', '').strip()
|
488 |
+
p.level = 1
|
489 |
+
items_on_slide += 1
|
490 |
+
else:
|
491 |
+
p = text_frame.add_paragraph()
|
492 |
+
p.text = para_text
|
493 |
+
items_on_slide += 1
|
494 |
|
495 |
# Save the presentation
|
496 |
+
output_path = "requirements_slides.pptx"
|
497 |
prs.save(output_path)
|
498 |
return output_path
|
499 |
|
|
|
507 |
subtitle = slide.placeholders[1]
|
508 |
subtitle.text = f"An error occurred: {str(e)}\nPlease try again with a different project description."
|
509 |
|
510 |
+
output_path = "requirements_slides.pptx"
|
511 |
prs.save(output_path)
|
512 |
except:
|
513 |
# If even the error presentation fails, return a default path
|
514 |
pass
|
515 |
return output_path
|
516 |
|
517 |
+
def create_pdf(proposal, output_path="requirements_document.pdf"):
|
518 |
+
"""Create a PDF document from the proposal"""
|
519 |
try:
|
520 |
+
from reportlab.lib.pagesizes import letter
|
521 |
+
from reportlab.pdfgen import canvas
|
522 |
+
from reportlab.lib.units import inch
|
523 |
+
from reportlab.lib import colors
|
524 |
|
525 |
+
# Basic PDF generation using canvas directly
|
526 |
+
c = canvas.Canvas(output_path, pagesize=letter)
|
527 |
+
width, height = letter
|
|
|
528 |
|
529 |
+
# Add a header with company info (similar to the example)
|
530 |
+
c.setFont("Helvetica", 10)
|
531 |
+
c.drawString(1*inch, height-0.5*inch, "Generated Requirements Document")
|
532 |
|
533 |
+
# Title
|
534 |
+
title = extract_title(proposal)
|
535 |
+
c.setFont("Helvetica-Bold", 14)
|
536 |
+
c.drawCentredString(width/2, height-1.2*inch, title)
|
537 |
+
|
538 |
+
# Simple text rendering
|
539 |
+
c.setFont("Helvetica", 10)
|
540 |
+
y = height - 1.5*inch
|
541 |
+
line_height = 14
|
542 |
+
|
543 |
+
# Process by paragraphs to handle sections better
|
544 |
+
paragraphs = proposal.split('\n\n')
|
545 |
+
|
546 |
+
for paragraph in paragraphs:
|
547 |
+
paragraph = paragraph.strip()
|
548 |
+
if not paragraph:
|
549 |
+
continue
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
550 |
|
551 |
+
# Check for page break
|
552 |
+
if y < 1*inch:
|
553 |
+
c.showPage()
|
554 |
+
# Add header to new page
|
555 |
+
c.setFont("Helvetica", 10)
|
556 |
+
c.drawString(1*inch, height-0.5*inch, "Generated Requirements Document")
|
557 |
+
y = height - 1*inch
|
558 |
+
|
559 |
+
# Check if it's a section header (starts with **)
|
560 |
+
if paragraph.startswith('**') and paragraph.endswith('**'):
|
561 |
+
c.setFont("Helvetica-Bold", 12)
|
562 |
+
# Remove ** markers
|
563 |
+
paragraph = paragraph.replace('**', '')
|
564 |
+
c.drawString(1*inch, y, paragraph)
|
565 |
+
y -= line_height * 1.5
|
566 |
+
# Check if it's a subsection header (starts with ** but has more text)
|
567 |
+
elif paragraph.startswith('**') and '**:' in paragraph:
|
568 |
+
parts = paragraph.split('**:', 1)
|
569 |
+
if len(parts) == 2:
|
570 |
+
header = parts[0].replace('**', '') + ':'
|
571 |
+
c.setFont("Helvetica-Bold", 11)
|
572 |
+
c.drawString(1*inch, y, header)
|
573 |
+
y -= line_height * 1.2
|
574 |
+
|
575 |
+
# If there's content after the header
|
576 |
+
content = parts[1].strip()
|
577 |
+
if content:
|
578 |
+
c.setFont("Helvetica", 10)
|
579 |
+
# Need to wrap this text
|
580 |
+
text_object = c.beginText(1.2*inch, y)
|
581 |
+
for line in textwrap.wrap(content, width=70):
|
582 |
+
text_object.textLine(line)
|
583 |
+
y -= line_height
|
584 |
+
c.drawText(text_object)
|
585 |
+
y -= line_height * 0.5
|
586 |
+
# Check if it's a bullet point
|
587 |
+
elif paragraph.startswith('●'):
|
588 |
+
c.setFont("Helvetica", 10)
|
589 |
+
# Draw the bullet
|
590 |
+
c.drawString(1.2*inch, y, '●')
|
591 |
|
592 |
+
# Draw the text after the bullet with indentation
|
593 |
+
text = paragraph[1:].strip()
|
594 |
+
text_object = c.beginText(1.5*inch, y)
|
595 |
+
for line in textwrap.wrap(text, width=60):
|
596 |
+
text_object.textLine(line)
|
597 |
y -= line_height
|
598 |
+
c.drawText(text_object)
|
599 |
+
y -= line_height * 0.3
|
600 |
+
else:
|
601 |
+
# Regular paragraph
|
602 |
+
c.setFont("Helvetica", 10)
|
603 |
+
# Need to wrap this text
|
604 |
+
text_object = c.beginText(1*inch, y)
|
605 |
+
for line in textwrap.wrap(paragraph, width=70):
|
606 |
+
text_object.textLine(line)
|
607 |
+
y -= line_height
|
608 |
+
c.drawText(text_object)
|
609 |
+
y -= line_height * 0.5
|
610 |
|
611 |
+
# Add a footer with page number
|
612 |
+
c.setFont("Helvetica", 8)
|
613 |
+
c.drawCentredString(width/2, 0.5*inch, "Page 1")
|
|
|
|
|
|
|
614 |
|
615 |
+
c.save()
|
616 |
+
return output_path
|
617 |
except Exception as e:
|
618 |
+
print(f"Error creating PDF: {e}")
|
619 |
+
# Create an even simpler emergency PDF
|
620 |
+
try:
|
621 |
+
c = canvas.Canvas(output_path, pagesize=letter)
|
622 |
+
width, height = letter
|
623 |
+
c.setFont("Helvetica-Bold", 14)
|
624 |
+
c.drawString(1*inch, height-1*inch, "Requirements Document")
|
625 |
+
c.setFont("Helvetica", 10)
|
626 |
+
c.drawString(1*inch, height-1.5*inch, "Error formatting document. Please see text version.")
|
627 |
+
c.save()
|
628 |
+
except Exception as inner_e:
|
629 |
+
print(f"Error creating emergency PDF: {inner_e}")
|
630 |
+
return output_path
|
631 |
|
632 |
# Create Gradio interface
|
633 |
def create_interface():
|
634 |
+
with gr.Blocks(title="Requirements Document Generator") as app:
|
635 |
+
gr.Markdown("# Technical Requirements Document Generator")
|
636 |
+
gr.Markdown("Generate comprehensive, professionally formatted technical requirements documents with PDF and PowerPoint exports.")
|
637 |
|
638 |
with gr.Row():
|
639 |
with gr.Column(scale=1):
|
|
|
646 |
project_type = gr.Dropdown(
|
647 |
label="Project Type",
|
648 |
choices=["General Project", "SaaS Performance Platform"],
|
649 |
+
value="SaaS Performance Platform"
|
650 |
)
|
651 |
|
652 |
+
generate_button = gr.Button("Generate Requirements Document", variant="primary")
|
653 |
|
654 |
# Examples
|
655 |
examples = gr.Examples(
|
|
|
664 |
with gr.Column(scale=2):
|
665 |
output_tabs = gr.Tabs()
|
666 |
with output_tabs:
|
667 |
+
with gr.TabItem("Document Text"):
|
668 |
+
proposal_output = gr.Textbox(label="Generated Document", lines=25)
|
669 |
with gr.TabItem("Documents"):
|
670 |
with gr.Row():
|
671 |
pdf_output = gr.File(label="PDF Document")
|