kamaleswar Mohanta commited on
Commit
3f4e8a3
·
1 Parent(s): 3e1b7d1

Add initial implementation of SDLC workflow tests and related mock classes

Browse files

- Introduced test cases for various stages of the SDLC process including planning, design, development, testing, deployment, and completion.
- Implemented mock classes to simulate session states and interactions within the workflow.
- Added utility functions for managing session history and feedback processing.
- Included error handling for potential import issues related to the SdlcNode class.

.gitignore CHANGED
@@ -6,4 +6,5 @@ venv/
6
  outputlog.txt
7
  app.log
8
  src\langgraphagenticai\logging\logs\
9
-
 
 
6
  outputlog.txt
7
  app.log
8
  src\langgraphagenticai\logging\logs\
9
+ test_sdlc_workflow.py
10
+ .pytest_cache/
__pycache__/test_sdlc_workflow.cpython-312-pytest-8.3.5.pyc ADDED
Binary file (16 kB). View file
 
__pycache__/test_sdlc_workflow.cpython-312.pyc ADDED
Binary file (9.45 kB). View file
 
src/langgraphagenticai/nodes/__pycache__/sdlc_node.cpython-312.pyc CHANGED
Binary files a/src/langgraphagenticai/nodes/__pycache__/sdlc_node.cpython-312.pyc and b/src/langgraphagenticai/nodes/__pycache__/sdlc_node.cpython-312.pyc differ
 
src/langgraphagenticai/nodes/sdlc_node.py CHANGED
@@ -193,31 +193,30 @@ class SdlcNode:
193
  Process user feedback and update state with decision.
194
  Only { "current_stage": ["accept"] } ends the process.
195
  """
196
- logger.info(f"--- Entering process_feedback ---")
197
- logger.info(f"Input state: {state.to_dict()}")
 
198
 
199
  current_stage = state.current_stage
200
- feedback = state.get_last_feedback_for_stage(current_stage)
 
 
 
201
 
202
- state.add_feedback(current_stage, str(feedback))
203
 
204
  logger.info(f"Processing feedback for stage: {current_stage}")
205
- logger.info(f"Feedback received: {feedback}")
206
 
207
- if feedback and feedback.strip().lower() == "accept":
208
  logger.info(f"Feedback for stage '{current_stage}' is ACCEPT. Ending flow.")
209
  state.feedback_decision = "accept"
210
  else:
211
- logger.info(f"Feedback for stage '{current_stage}' is not accept. Looping back. Feedback is: {feedback}")
212
  state.feedback_decision = "reject"
213
-
214
- return_value = {
215
- "feedback_decision": state.feedback_decision,
216
- "feedback": feedback
217
- }
218
- logger.info(f"Returning from process_feedback: {return_value}")
219
- logger.info(f"--- Exiting process_feedback ---")
220
- return return_value
221
 
222
  @log_entry_exit
223
  def feedback_route(self, state: State) -> str:
@@ -239,6 +238,8 @@ class SdlcNode:
239
 
240
  state.clear_feedback_decision()
241
  st.session_state["feedback_decision"] = None
 
 
242
  return "accept"
243
  else:
244
  logger.info("Feedback rejected. Routing back for revision.")
 
193
  Process user feedback and update state with decision.
194
  Only { "current_stage": ["accept"] } ends the process.
195
  """
196
+
197
+ # logger.info(f"Input state: {state.to_dict()}")
198
+ logger.info(f"Feedback: {state.feedback}")
199
 
200
  current_stage = state.current_stage
201
+ feedback_for_stage = state.get_last_feedback_for_stage(current_stage)
202
+
203
+ if feedback_for_stage is None:
204
+ logger.warning(f"No feedback found for stage {current_stage}. Available feedback: {state.feedback}")
205
 
206
+ state.add_feedback(current_stage, str(feedback_for_stage))
207
 
208
  logger.info(f"Processing feedback for stage: {current_stage}")
209
+ logger.info(f"Feedback received: {feedback_for_stage}")
210
 
211
+ if feedback_for_stage and feedback_for_stage.strip().lower() == "accept":
212
  logger.info(f"Feedback for stage '{current_stage}' is ACCEPT. Ending flow.")
213
  state.feedback_decision = "accept"
214
  else:
215
+ logger.info(f"Feedback for stage '{current_stage}' is not accept. Looping back. Feedback is: {feedback_for_stage}")
216
  state.feedback_decision = "reject"
217
+ logger.info(f"Feedback: {state.feedback}")
218
+
219
+ return {"feedback_decision": state.feedback_decision}
 
 
 
 
 
220
 
221
  @log_entry_exit
222
  def feedback_route(self, state: State) -> str:
 
238
 
239
  state.clear_feedback_decision()
240
  st.session_state["feedback_decision"] = None
241
+ logger.info(f"Feedback: {state.feedback}")
242
+ logger.info(f"Feedback decision: {state.feedback_decision}")
243
  return "accept"
244
  else:
245
  logger.info("Feedback rejected. Routing back for revision.")
src/langgraphagenticai/ui/streamlitui/__pycache__/display_result_sdlc.cpython-312.pyc CHANGED
Binary files a/src/langgraphagenticai/ui/streamlitui/__pycache__/display_result_sdlc.cpython-312.pyc and b/src/langgraphagenticai/ui/streamlitui/__pycache__/display_result_sdlc.cpython-312.pyc differ
 
src/langgraphagenticai/ui/streamlitui/__pycache__/loadui.cpython-312.pyc CHANGED
Binary files a/src/langgraphagenticai/ui/streamlitui/__pycache__/loadui.cpython-312.pyc and b/src/langgraphagenticai/ui/streamlitui/__pycache__/loadui.cpython-312.pyc differ
 
src/langgraphagenticai/ui/streamlitui/display_result_sdlc.py CHANGED
@@ -12,10 +12,10 @@ from src.langgraphagenticai.logging.logging_utils import logger, log_entry_exit
12
  import os
13
  from langgraph.graph import END
14
  from langgraph.types import Command
15
- from src.langgraphagenticai.state.state import SDLCState
16
  from src.langgraphagenticai.ui.uiconfigfile import Config
17
 
18
- exclude_keys = ["api_key", "OPENAI_API_KEY","GOOGLE_API_KEY","TAVILY_API_KEY","GROQ_API_KEY","state"]
19
  safe_state = {k: v for k, v in st.session_state.items() if k not in exclude_keys}
20
 
21
  # --- Pydantic Model for Feedback ---
@@ -41,21 +41,29 @@ class DisplaySdlcResult:
41
  "project_objectives": "",
42
  "requirements_generated": False,
43
  "user_stories_generated_flag": False,
 
 
 
 
44
  "generated_requirements": None,
45
  "generated_user_stories": None,
46
  "generated_design_documents": None,
47
  "generated_development_artifact": None,
48
  "generated_testing_artifact": None,
49
  "generated_deployment_artifact": None,
50
- "design_documents_generated_flag": False,
51
- "development_artifact_generated_flag": False,
52
- "testing_artifact_generated_flag": False,
53
- "deployment_artifact_generated_flag1 ": False,
54
- "feedback": None,
55
- "needs_resume_after_feedback": False,
56
  "user_stories_approved": False,
57
- "planning_stage_running": False,
 
 
 
 
58
  "feedback_pending": False,
 
 
 
 
 
 
59
  }
60
  for key, value in defaults.items():
61
  if key not in st.session_state:
@@ -68,18 +76,29 @@ class DisplaySdlcResult:
68
  st.write(safe_state)
69
 
70
  # Prevent concurrent runs
71
- if st.session_state.get("planning_stage_running"):
 
 
 
 
 
 
 
72
  st.warning("Workflow is already running. Please wait.")
73
- logger.warning("Workflow blocked due to planning_stage_running=True")
74
  return
75
 
76
  # Handle resumption
77
  if st.session_state.get("needs_resume_after_feedback"):
78
  logger.info("Detected need to resume graph after feedback.")
79
- st.session_state["planning_stage_running"] = True
80
- self._resume_sdlc_graph()
81
- st.session_state["planning_stage_running"] = False
82
- return
 
 
 
 
83
 
84
  # Display UI
85
  phases = ["Planning", "Design", "Development", "Testing", "Deployment"]
@@ -91,21 +110,93 @@ class DisplaySdlcResult:
91
  self._display_planning_phase()
92
  self._display_planning_artifacts()
93
  if st.session_state.get("user_stories_approved"):
94
- st.session_state["user_stories"]= st.session_state["generated_user_stories"]
95
- st.session_state["user_stories_approved"] = True
96
  st.session_state["sdlc_stage"] = "design"
 
 
 
97
  st.success("✅ SDLC Planning Phase Completed Successfully!")
98
- st.markdown("You can restart the workflow or review the artifacts.")
 
 
 
 
 
 
 
 
99
 
100
  with tabs[1]:
101
  if not st.session_state.get("design_documents_generated_flag"):
102
  self._display_design_phase()
103
  self._display_design_artifacts()
104
- with tabs[2]: self._display_development_phase(); self._display_development_artifacts()
105
- with tabs[3]: self._display_testing_phase(); self._display_testing_artifacts()
106
- with tabs[4]: self._display_deployment_phase(); self._display_deployment_artifacts()
107
-
108
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
 
110
  st.markdown("---")
111
  if st.button("Restart SDLC Workflow", key="restart_button"):
@@ -133,14 +224,10 @@ class DisplaySdlcResult:
133
  def _display_planning_artifacts(self):
134
  """Displays generated planning artifacts and the feedback form."""
135
  st.subheader("Planning Artifacts")
136
- requirements_exist = st.session_state.get("requirements_generated",False)
137
- stories_exist = st.session_state.get("user_stories_generated_flag",False)
138
  user_stories_approved = st.session_state.get("user_stories_approved", False)
139
 
140
- if "feedback" not in st.session_state or st.session_state["feedback"] is None:
141
- st.session_state["feedback"] = {}
142
- logger.info("Initialized empty feedback dictionary in session state.")
143
-
144
  if requirements_exist:
145
  with st.expander("Generated Requirements", expanded=False):
146
  st.markdown(st.session_state["generated_requirements"])
@@ -185,16 +272,15 @@ class DisplaySdlcResult:
185
  is_approved = (selected_decision == "Approve")
186
  logger.info(f"Feedback submitted: {'Approved' if is_approved else 'Rejected with comments.'}")
187
  current_stage = "planning"
188
- st.session_state["feedback"] = {
189
- current_stage: ["accept"] if is_approved else [comments.strip()]
190
- }
191
  logger.info("Feedback stored: %s", st.session_state["feedback"])
192
  st.session_state["needs_resume_after_feedback"] = True
193
  if not is_approved:
194
  st.session_state["user_stories_generated_flag"] = False
195
  st.session_state["feedback_pending"] = False
196
- st.write(f"Feedback submitted: {'Approved' if is_approved else 'Rejected with comments.'}{st.session_state["feedback"]}")
197
- st.write(f"Feedback processing completed. Graph needs resuming: {st.session_state['needs_resume_after_feedback']}")
198
  st.write("Session state after feedback: %s", st.session_state)
199
  st.rerun()
200
  else:
@@ -205,33 +291,29 @@ class DisplaySdlcResult:
205
  elif not requirements_exist and not stories_exist and not user_stories_approved:
206
  st.info("No artifacts generated yet in the Planning phase.")
207
 
208
-
209
-
210
  @log_entry_exit
211
  def _display_design_phase(self):
212
  """Displays the design phase details."""
213
  st.header("Design Phase")
214
- # st.markdown(st.session_state["design_documents"])
215
-
216
-
217
  @log_entry_exit
218
  def _display_design_artifacts(self):
219
- """Displays design artifacts."""
220
  st.subheader("Design Artifacts")
221
- logger.info(f"Current design documents in session state: %s", st.session_state.get("generated_design_documents", None))
222
- design_artifacts_exist = st.session_state.get("generated_design_documents",False)
223
  design_documents_approved = st.session_state.get("design_documents_approved", False)
224
-
225
  if design_artifacts_exist:
226
- expander_design_label = "Approved Design Artifacts" if design_documents_approved else "Generated Design Artifacts"
227
- with st.expander(expander_design_label, expanded=False):
228
  design_documents = st.session_state.get("generated_design_documents", "Error: Design documents not generated.")
229
  st.markdown(design_documents)
230
  if st.button("Save Design Documents", key="save_design_documents"):
231
  self._save_artifact(design_documents, "design_documents.txt")
232
-
233
  if not design_documents_approved:
234
- st.markdown("### Feedback on Design Artifacts")
235
  decision_options = ("Approve", "Reject")
236
  selected_decision = st.radio(
237
  "Review Decision:",
@@ -245,7 +327,7 @@ class DisplaySdlcResult:
245
  placeholder="Include more details on the design",
246
  key="design_documents_feedback_comments"
247
  )
248
- submit_disabled = st.session_state.get("feedback_pending", False) or st.session_state.get("planning_stage_running", False)
249
  if st.button("Submit Review", key="submit_review_button_design", disabled=submit_disabled):
250
  if st.session_state.get("feedback_pending"):
251
  st.warning("Feedback is already being processed. Please wait.")
@@ -258,91 +340,229 @@ class DisplaySdlcResult:
258
  is_approved = (selected_decision == "Approve")
259
  logger.info(f"Feedback submitted: {'Approved' if is_approved else 'Rejected with comments.'}")
260
  current_stage = "design"
261
- st.session_state["feedback"] = {
262
- current_stage: ["accept"] if is_approved else [comments.strip()]
263
- }
264
  logger.info("Feedback stored: %s", st.session_state["feedback"])
265
  st.session_state["needs_resume_after_feedback"] = True
266
  if not is_approved:
267
  st.session_state["design_documents_generated_flag"] = False
268
  st.session_state["feedback_pending"] = False
269
- st.write(f"Feedback submitted: {'Approved' if is_approved else 'Rejected with comments.'}{st.session_state["feedback"]}")
270
- st.write(f"Feedback processing completed. Graph needs resuming: {st.session_state['needs_resume_after_feedback']}")
271
  st.write("Session state after feedback: %s", st.session_state)
272
  st.rerun()
273
  else:
274
- st.success("✅ User stories approved. Planning phase completed.")
275
- st.markdown("The feedback form is disabled as the workflow is complete.")
276
-
277
-
278
-
279
 
280
  @log_entry_exit
281
  def _display_development_phase(self):
282
- # st.header("Development Phase")
283
- # st.info("Development phase details will be displayed here in future implementations.")
284
- pass
285
 
286
  @log_entry_exit
287
  def _display_development_artifacts(self):
288
- # st.subheader("Development Artifacts")
289
- # st.info("Development artifacts (e.g., code snippets, build logs) will be displayed here.")
290
- pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
291
 
292
  @log_entry_exit
293
  def _display_testing_phase(self):
294
- # st.header("Testing Phase")
295
- # st.info("Testing phase details will be displayed here in future implementations.")
296
- pass
297
 
298
  @log_entry_exit
299
  def _display_testing_artifacts(self):
300
- # st.subheader("Testing Artifacts")
301
- # st.info("Testing artifacts (e.g., test cases, bug reports) will be displayed here.")
302
- pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
303
 
304
  @log_entry_exit
305
  def _display_deployment_phase(self):
306
- # st.header("Deployment Phase")
307
- # st.info("Deployment phase details will be displayed here in future implementations.")
308
- pass
309
 
310
  @log_entry_exit
311
  def _display_deployment_artifacts(self):
312
- # st.subheader("Deployment Artifacts")
313
- # st.info("Deployment artifacts (e.g., deployment plans, monitoring dashboards) will be displayed here.")
314
- pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
315
 
316
  @log_entry_exit
317
  def _collect_project_requirements(self):
318
  """Displays the form to collect initial project details."""
319
- # DefaultProjectName = "The Book Nook"
320
- # DefaultDescription = """Develop a user-friendly mobile application for "The Book Nook," a local bookstore in Bangalore,
321
- # to allow customers to browse their inventory, place orders online, and learn about upcoming events."""
322
- # DefaultGoals = """ Increase sales and revenue for The Book Nook.
323
- # Enhance customer engagement and loyalty.
324
- # Modernize The Book Nook's presence and reach a wider audience in Bangalore. """
325
- # DefaultScope = """ Inclusions:
326
- # Developing a mobile application compatible with Android and iOS.
327
- # Features: Browsing book catalog with search and filtering, viewing book details (description, author, price, availability), creating user accounts, adding books to a shopping cart, secure online payment integration, order history, push notifications for new arrivals and events, information about store hours and location.
328
- # Integration with the bookstore's existing inventory management system.
329
- # Basic user support documentation.
330
- # Exclusions:
331
- # Developing a separate tablet application.
332
- # Implementing a loyalty points program (will be considered in a future phase).
333
- # Integrating with social media platforms for direct purchasing.
334
- # Providing real-time inventory updates beyond a daily sync.
335
- # Developing advanced analytics dashboards for the bookstore owner in this phase."""
336
- # DefaultObjectives = """Increase online sales by 15% within the first six months of the app launch (Measurable, Achievable, Relevant, Time-bound).
337
- # Achieve an average user rating of 4.5 stars or higher on both app stores within three months of launch (Measurable, Achievable, Relevant, Time-bound).
338
- # Acquire 500 new registered app users within the first month of launch (Measurable, Achievable, Relevant, Time-bound).
339
- # Successfully integrate the app with the existing inventory system with no data loss by the end of the development phase (Measurable, Achievable, Relevant, Time-bound). """
340
- DefaultProjectName="demo_project"
341
- DefaultDescription="demo_description"
342
- DefaultGoals="demo_goals"
343
- DefaultScope="demo_scope"
344
- DefaultObjectives="demo_objectives"
345
-
346
 
347
  st.subheader("Define Project Details")
348
  with st.form("sdlc_requirements_form"):
@@ -362,20 +582,22 @@ class DisplaySdlcResult:
362
  st.session_state["sdlc_stage"] = "planning"
363
  logger.info("Initial project details submitted. Running graph for the first time.")
364
  st.session_state["planning_stage_running"] = True
365
- self._run_sdlc_graph_initial()
366
- st.session_state["planning_stage_running"] = False
 
 
367
  st.rerun()
368
 
369
  @log_entry_exit
370
- def _run_sdlc_graph_initial(self):
371
- """Runs the SDLC graph for the very first time with initial project details."""
372
  session_id = self.config.get("configurable", {}).get("session_id")
373
  if not session_id:
374
  logger.error("Session ID not found in config! Cannot start graph.")
375
  st.error("Critical error: Session ID missing. Please restart.")
376
  return
377
 
378
- input_data = {"session_id": session_id}
379
  requirements = None
380
  user_stories = None
381
  design_documents = None
@@ -384,45 +606,29 @@ class DisplaySdlcResult:
384
  deployment_artifact = None
385
  logger.info(f"Running SDLC graph initially with input_data: {input_data}...")
386
 
387
- stage = st.session_state.get("sdlc_stage", "planning")
388
- artifact_description = ""
389
- if stage == "planning":
390
- artifact_description = "requirements and user stories"
391
- elif stage == "design":
392
- artifact_description = "design documents"
393
- elif stage == "development":
394
- artifact_description = "development artifact"
395
- elif stage == "testing":
396
- artifact_description = "testing artifact"
397
- elif stage == "deployment":
398
- artifact_description = "deployment artifact"
399
 
400
  with st.spinner(f"Generating initial {artifact_description}..."):
401
  try:
402
- # Using stream_mode="values" might be too verbose if not needed for accumulation here.
403
- # Default stream_mode="updates" (or explicit) might be cleaner if just reacting to node outputs.
404
- # However, if the goal is to capture the state *at interruption*, getting the state from the checkpointer
405
- # after the loop is more reliable.
406
- for event_dict in self.graph.stream(input_data, self.config): # event_dict is the whole event dictionary
407
  logger.info(f"Initial Run Event: {event_dict}")
408
-
409
  if "__interrupt__" in event_dict:
410
  logger.info("Graph interrupted as expected after generating artifacts.")
411
- # Artifacts should have been populated from previous node output events.
412
- # The state is saved in the checkpointer.
413
  break
414
-
415
  for node_name, node_output_dict in event_dict.items():
416
- if node_name in ["__checkpoint__", "__interrupt__"]: # Skip meta keys
417
  continue
418
  if node_output_dict is None:
419
  continue
420
-
421
- # Ensure node_output_dict is a dictionary before trying to access keys
422
  if not isinstance(node_output_dict, dict):
423
- logger.warning(f"Node '{node_name}' output is not a dict: {node_output_dict}. Skipping artifact extraction for this item.")
424
  continue
425
-
426
  logger.info(f"Processing output from node '{node_name}'.")
427
  if "generated_requirements" in node_output_dict:
428
  requirements = node_output_dict["generated_requirements"]
@@ -453,7 +659,7 @@ class DisplaySdlcResult:
453
  st.session_state["design_documents_generated_flag"] = design_documents is not None
454
  st.session_state["development_artifact_generated_flag"] = development_artifact is not None
455
  st.session_state["testing_artifact_generated_flag"] = testing_artifact is not None
456
- st.session_state["deployment_artifact_generated_flag1 "] = deployment_artifact is not None
457
  logger.info("Initial graph run finished (interrupted). Artifacts stored in session state.")
458
 
459
  @log_entry_exit
@@ -463,91 +669,63 @@ class DisplaySdlcResult:
463
  feedback_data = st.session_state.get("feedback")
464
  logger.info(f"Feedback data from session state: %s", feedback_data)
465
 
466
- # Ensure config has thread_id
467
  if "configurable" not in self.config or "thread_id" not in self.config["configurable"]:
468
  logger.error("Thread ID missing in main config. Cannot resume.")
469
  st.error("Cannot resume workflow: Session thread ID is missing in configuration.")
470
  st.session_state["needs_resume_after_feedback"] = False
471
- st.session_state["planning_stage_running"] = False
472
  return
473
- thread_id = self.config["configurable"]["thread_id"] # Get thread_id from main config
474
-
475
- update_payload = {} # Dictionary for state updates
476
 
 
477
  try:
478
  if feedback_data:
479
  update_payload['feedback'] = feedback_data
480
- current_stage_value = st.session_state.get("sdlc_stage", "planning") # TODO: Make this more robust if interrupts happen elsewhere
481
-
482
  if isinstance(feedback_data, dict) and current_stage_value in feedback_data:
483
- last_feedback = feedback_data[current_stage_value][-1].strip().lower() if feedback_data[current_stage_value] else ""
 
484
  update_payload['feedback_decision'] = "accept" if last_feedback == "accept" else "reject"
485
  else:
486
- update_payload['feedback_decision'] = "reject" # Default safely
487
  logger.info(f"Update payload prepared: {update_payload}")
488
  else:
489
- # If no feedback data, we probably shouldn't be resuming this way
490
  logger.warning("Resume triggered but no feedback data found in session state.")
491
- # Decide how to handle this: maybe default to reject or raise error?
492
- update_payload['feedback_decision'] = "reject" # Defaulting to reject if no feedback
493
- logger.info(f"Update payload prepared (no feedback data): {update_payload}")
494
-
495
- # We can also add/update 'last_updated' timestamp if desired
496
  update_payload['last_updated'] = datetime.now().isoformat()
497
-
498
  except Exception as e:
499
  logger.error(f"Error preparing update payload for thread_id {thread_id}: {e}", exc_info=True)
500
  st.error(f"Error preparing resume data: {e}. Please restart.")
501
  st.session_state["needs_resume_after_feedback"] = False
502
- st.session_state["planning_stage_running"] = False
503
  return
504
 
505
  if update_payload:
506
- logger.info(f"Attempting to update graph state for thread_id {thread_id} with final payload: {update_payload}")
507
  try:
508
- logger.info(f"Updating graph state for thread_id {thread_id} with payload: {update_payload}")
509
- self.graph.update_state(
510
- config=self.config, # Pass the main config containing thread_id
511
- values=update_payload # Pass only the fields to update
512
- )
513
  logger.info(f"[OK] Updated graph state for thread_id {thread_id} with payload: {update_payload}")
514
  except Exception as e:
515
  logger.error(f"Error calling graph.update_state for thread_id {thread_id}: {e}", exc_info=True)
516
  st.error(f"Error updating workflow state: {e}. Please restart.")
517
  st.session_state["needs_resume_after_feedback"] = False
518
- st.session_state["planning_stage_running"] = False
519
  return
520
- else:
521
- logger.warning("No update payload generated, skipping state update.")
522
 
523
- st.session_state["needs_resume_after_feedback"] = False
524
- st.session_state["planning_stage_running"] = False
525
- return
526
-
527
-
528
- # --- Resume Graph Execution using Command(resume=True) ---
529
  with st.spinner("Processing feedback and continuing workflow..."):
530
  try:
531
  final_state = {}
532
- # Revert to using Command(resume=True)
533
  logger.info("Attempting graph stream with Command(resume=True)")
534
  for event in self.graph.stream(Command(resume=True), config=self.config, stream_mode="values"):
535
  logger.debug(f"Resume Stream Event: {event}")
536
-
537
  node = event.get("log", {}).get("actions", [{}])[0].get("node")
538
  state = event
539
-
540
  if not isinstance(state, dict):
541
  logger.warning(f"Received non-dict state in stream: {type(state)}. Skipping.")
542
  continue
543
-
544
- # Log entry into graph nodes if structure allows
545
- if node: logger.info(f"Executing node: {node}")
546
-
547
- logger.info(f"Node '{node}' generated state update.") # Log less verbosely
548
- final_state.update(state) # Aggregate the latest state view
549
-
550
- # Update session state based on the received state from the graph execution
551
  if "generated_requirements" in state and state["generated_requirements"] is not None:
552
  st.session_state["generated_requirements"] = state["generated_requirements"]
553
  st.session_state["requirements_generated"] = True
@@ -557,9 +735,6 @@ class DisplaySdlcResult:
557
  if "design_documents" in state and state["design_documents"] is not None:
558
  st.session_state["generated_design_documents"] = state["design_documents"]
559
  st.session_state["design_documents_generated_flag"] = True
560
- elif "generated_design_documents" in state and state["generated_design_documents"] is not None:
561
- st.session_state["generated_design_documents"] = state["generated_design_documents"]
562
- st.session_state["design_documents_generated_flag"] = True
563
  if "development_artifact" in state and state["development_artifact"] is not None:
564
  st.session_state["generated_development_artifact"] = state["development_artifact"]
565
  st.session_state["development_artifact_generated_flag"] = True
@@ -568,17 +743,12 @@ class DisplaySdlcResult:
568
  st.session_state["testing_artifact_generated_flag"] = True
569
  if "deployment_artifact" in state and state["deployment_artifact"] is not None:
570
  st.session_state["generated_deployment_artifact"] = state["deployment_artifact"]
571
- st.session_state["deployment_artifact_generated_flag1 "] = True
572
- if "feedback_decision" in state:
573
- logger.info(f"Feedback decision processed by graph node: {state['feedback_decision']}")
574
-
575
  except Exception as e:
576
- logger.error(f"Error during graph stream after update_state/resume: {e}", exc_info=True)
577
  st.error(f"An error occurred during workflow resumption: {e}")
578
- st.session_state["planning_stage_running"] = False
579
- return # Stop execution here
580
 
581
- # --- Post-Stream State Update ---
582
  final_feedback_decision = final_state.get("feedback_decision")
583
  logger.info(f"Final feedback decision after stream: {final_feedback_decision}")
584
 
@@ -586,44 +756,41 @@ class DisplaySdlcResult:
586
  if st.session_state["sdlc_stage"] == "planning":
587
  st.session_state["user_stories_approved"] = True
588
  st.session_state["sdlc_stage"] = "design"
589
- st.session_state["feedback_decision"] = None
590
- st.session_state["feedback"] = None
591
- logger.info("User stories approved. Proceeding to next phase.")
592
  elif st.session_state["sdlc_stage"] == "design":
593
  st.session_state["design_documents_approved"] = True
594
- logger.info("Design documents approved. Proceeding to next phase.")
 
595
  elif st.session_state["sdlc_stage"] == "development":
596
  st.session_state["development_artifact_approved"] = True
597
- logger.info("Development artifact approved. Proceeding to next phase.")
 
598
  elif st.session_state["sdlc_stage"] == "testing":
599
  st.session_state["testing_artifact_approved"] = True
600
- logger.info("Testing artifact approved. Proceeding to next phase.")
 
601
  elif st.session_state["sdlc_stage"] == "deployment":
602
  st.session_state["deployment_artifact_approved"] = True
603
- logger.info("Deployment artifact approved. Proceeding to next phase.")
 
604
  else:
605
- logger.error("Unknown SDLC stage. Cannot proceed.")
606
  st.error("Unknown SDLC stage. Cannot proceed.")
607
- st.session_state["user_stories_approved"] = False
608
- st.session_state["user_stories_generated_flag"] = False
609
- st.session_state["feedback_pending"] = False
610
  st.session_state["needs_resume_after_feedback"] = False
611
- st.session_state["planning_stage_running"] = False
612
  return
613
-
614
-
615
  else:
616
- # st.session_state["user_stories_generated_flag"] = final_state.get("user_stories") is not None
617
- st.session_state["user_stories_approved"] = False
618
- logger.info(f"Graph looped back or did not complete. Final feedback decision: {final_feedback_decision}")
619
 
620
  st.session_state["needs_resume_after_feedback"] = False
621
- st.session_state["planning_stage_running"] = False
622
- logger.info("Graph resumption stream processing completed. Graph completed flag: %s", st.session_state.get('user_stories_approved', False))
623
-
 
 
 
 
624
  st.rerun()
625
 
626
-
627
  @log_entry_exit
628
  def _save_artifact(self, content: str, filename: str):
629
  """Provides a download link for the given artifact content."""
@@ -635,3 +802,30 @@ class DisplaySdlcResult:
635
  except Exception as e:
636
  logger.error(f"Error creating download link for {filename}: {e}")
637
  st.error(f"Could not generate download link for {filename}.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  import os
13
  from langgraph.graph import END
14
  from langgraph.types import Command
15
+ from src.langgraphagenticai.state.state import SDLCStages, SDLCState
16
  from src.langgraphagenticai.ui.uiconfigfile import Config
17
 
18
+ exclude_keys = ["api_key", "OPENAI_API_KEY", "GOOGLE_API_KEY", "TAVILY_API_KEY", "GROQ_API_KEY", "state"]
19
  safe_state = {k: v for k, v in st.session_state.items() if k not in exclude_keys}
20
 
21
  # --- Pydantic Model for Feedback ---
 
41
  "project_objectives": "",
42
  "requirements_generated": False,
43
  "user_stories_generated_flag": False,
44
+ "design_documents_generated_flag": False,
45
+ "development_artifact_generated_flag": False,
46
+ "testing_artifact_generated_flag": False,
47
+ "deployment_artifact_generated_flag": False,
48
  "generated_requirements": None,
49
  "generated_user_stories": None,
50
  "generated_design_documents": None,
51
  "generated_development_artifact": None,
52
  "generated_testing_artifact": None,
53
  "generated_deployment_artifact": None,
 
 
 
 
 
 
54
  "user_stories_approved": False,
55
+ "design_documents_approved": False,
56
+ "development_artifact_approved": False,
57
+ "testing_artifact_approved": False,
58
+ "deployment_artifact_approved": False,
59
+ "feedback": {},
60
  "feedback_pending": False,
61
+ "needs_resume_after_feedback": False,
62
+ "planning_stage_running": False,
63
+ "design_stage_running": False,
64
+ "development_stage_running": False,
65
+ "testing_stage_running": False,
66
+ "deployment_stage_running": False,
67
  }
68
  for key, value in defaults.items():
69
  if key not in st.session_state:
 
76
  st.write(safe_state)
77
 
78
  # Prevent concurrent runs
79
+ running_flags = [
80
+ st.session_state.get("planning_stage_running"),
81
+ st.session_state.get("design_stage_running"),
82
+ st.session_state.get("development_stage_running"),
83
+ st.session_state.get("testing_stage_running"),
84
+ st.session_state.get("deployment_stage_running"),
85
+ ]
86
+ if any(running_flags):
87
  st.warning("Workflow is already running. Please wait.")
88
+ logger.warning("Workflow blocked due to active stage running.")
89
  return
90
 
91
  # Handle resumption
92
  if st.session_state.get("needs_resume_after_feedback"):
93
  logger.info("Detected need to resume graph after feedback.")
94
+ current_stage = st.session_state.get("sdlc_stage")
95
+ st.session_state[f"{current_stage}_stage_running"] = True
96
+ try:
97
+ self._resume_sdlc_graph()
98
+ finally:
99
+ st.session_state[f"{current_stage}_stage_running"] = False
100
+ st.rerun()
101
+ return
102
 
103
  # Display UI
104
  phases = ["Planning", "Design", "Development", "Testing", "Deployment"]
 
110
  self._display_planning_phase()
111
  self._display_planning_artifacts()
112
  if st.session_state.get("user_stories_approved"):
113
+ st.session_state["user_stories"] = st.session_state["generated_user_stories"]
 
114
  st.session_state["sdlc_stage"] = "design"
115
+ st.session_state["feedback_decision"] = None
116
+ st.session_state["needs_resume_after_feedback"] = False
117
+ st.session_state["feedback_pending"] = False
118
  st.success("✅ SDLC Planning Phase Completed Successfully!")
119
+ st.markdown("You can GO TO NEXT PHASE or review the artifacts.")
120
+ if st.session_state["sdlc_stage"] == "design" and not st.session_state.get("design_documents_generated_flag"):
121
+ st.info("Moving to Design Phase and generating artifacts...")
122
+ st.session_state["design_stage_running"] = True
123
+ try:
124
+ self._run_sdlc_graph_initial("design")
125
+ finally:
126
+ st.session_state["design_stage_running"] = False
127
+ st.rerun()
128
 
129
  with tabs[1]:
130
  if not st.session_state.get("design_documents_generated_flag"):
131
  self._display_design_phase()
132
  self._display_design_artifacts()
133
+ if st.session_state.get("design_documents_approved"):
134
+ st.session_state["sdlc_stage"] = "development"
135
+ st.session_state["feedback_decision"] = None
136
+ st.session_state["needs_resume_after_feedback"] = False
137
+ st.session_state["feedback_pending"] = False
138
+ st.success("✅ SDLC Design Phase Completed Successfully!")
139
+ st.markdown("You can GO TO NEXT PHASE or review the artifacts.")
140
+ if st.session_state["sdlc_stage"] == "development" and not st.session_state.get("development_artifact_generated_flag"):
141
+ st.info("Moving to Development Phase and generating artifacts...")
142
+ st.session_state["development_stage_running"] = True
143
+ try:
144
+ self._run_sdlc_graph_initial("development")
145
+ finally:
146
+ st.session_state["development_stage_running"] = False
147
+ st.rerun()
148
+
149
+ with tabs[2]:
150
+ if not st.session_state.get("development_artifact_generated_flag"):
151
+ self._display_development_phase()
152
+ self._display_development_artifacts()
153
+ if st.session_state.get("development_artifact_approved"):
154
+ st.session_state["sdlc_stage"] = "testing"
155
+ st.session_state["feedback_decision"] = None
156
+ st.session_state["needs_resume_after_feedback"] = False
157
+ st.session_state["feedback_pending"] = False
158
+ st.success("✅ SDLC Development Phase Completed Successfully!")
159
+ st.markdown("You can GO TO NEXT PHASE or review the artifacts.")
160
+ if st.session_state["sdlc_stage"] == "testing" and not st.session_state.get("testing_artifact_generated_flag"):
161
+ st.info("Moving to Testing Phase and generating artifacts...")
162
+ st.session_state["testing_stage_running"] = True
163
+ try:
164
+ self._run_sdlc_graph_initial("testing")
165
+ finally:
166
+ st.session_state["testing_stage_running"] = False
167
+ st.rerun()
168
+
169
+ with tabs[3]:
170
+ if not st.session_state.get("testing_artifact_generated_flag"):
171
+ self._display_testing_phase()
172
+ self._display_testing_artifacts()
173
+ if st.session_state.get("testing_artifact_approved"):
174
+ st.session_state["sdlc_stage"] = "deployment"
175
+ st.session_state["feedback_decision"] = None
176
+ st.session_state["needs_resume_after_feedback"] = False
177
+ st.session_state["feedback_pending"] = False
178
+ st.success("✅ SDLC Testing Phase Completed Successfully!")
179
+ st.markdown("You can GO TO NEXT PHASE or review the artifacts.")
180
+ if st.session_state["sdlc_stage"] == "deployment" and not st.session_state.get("deployment_artifact_generated_flag"):
181
+ st.info("Moving to Deployment Phase and generating artifacts...")
182
+ st.session_state["deployment_stage_running"] = True
183
+ try:
184
+ self._run_sdlc_graph_initial("deployment")
185
+ finally:
186
+ st.session_state["deployment_stage_running"] = False
187
+ st.rerun()
188
+
189
+ with tabs[4]:
190
+ if not st.session_state.get("deployment_artifact_generated_flag"):
191
+ self._display_deployment_phase()
192
+ self._display_deployment_artifacts()
193
+ if st.session_state.get("deployment_artifact_approved"):
194
+ st.session_state["sdlc_stage"] = "complete"
195
+ st.session_state["feedback_decision"] = None
196
+ st.session_state["needs_resume_after_feedback"] = False
197
+ st.session_state["feedback_pending"] = False
198
+ st.success("✅ SDLC Deployment Phase Completed Successfully!")
199
+ st.markdown("SDLC Workflow Completed!")
200
 
201
  st.markdown("---")
202
  if st.button("Restart SDLC Workflow", key="restart_button"):
 
224
  def _display_planning_artifacts(self):
225
  """Displays generated planning artifacts and the feedback form."""
226
  st.subheader("Planning Artifacts")
227
+ requirements_exist = st.session_state.get("requirements_generated", False)
228
+ stories_exist = st.session_state.get("user_stories_generated_flag", False)
229
  user_stories_approved = st.session_state.get("user_stories_approved", False)
230
 
 
 
 
 
231
  if requirements_exist:
232
  with st.expander("Generated Requirements", expanded=False):
233
  st.markdown(st.session_state["generated_requirements"])
 
272
  is_approved = (selected_decision == "Approve")
273
  logger.info(f"Feedback submitted: {'Approved' if is_approved else 'Rejected with comments.'}")
274
  current_stage = "planning"
275
+ if current_stage not in st.session_state["feedback"]:
276
+ st.session_state["feedback"][current_stage] = []
277
+ st.session_state["feedback"][current_stage] = ["accept" if is_approved else comments.strip()]
278
  logger.info("Feedback stored: %s", st.session_state["feedback"])
279
  st.session_state["needs_resume_after_feedback"] = True
280
  if not is_approved:
281
  st.session_state["user_stories_generated_flag"] = False
282
  st.session_state["feedback_pending"] = False
283
+ st.write(f"Feedback submitted: {'Approved' if is_approved else 'Rejected with comments.'}")
 
284
  st.write("Session state after feedback: %s", st.session_state)
285
  st.rerun()
286
  else:
 
291
  elif not requirements_exist and not stories_exist and not user_stories_approved:
292
  st.info("No artifacts generated yet in the Planning phase.")
293
 
 
 
294
  @log_entry_exit
295
  def _display_design_phase(self):
296
  """Displays the design phase details."""
297
  st.header("Design Phase")
298
+ st.info("Design documents are being generated based on approved user stories.")
299
+
 
300
  @log_entry_exit
301
  def _display_design_artifacts(self):
302
+ """Displays design artifacts and feedback form."""
303
  st.subheader("Design Artifacts")
304
+ design_artifacts_exist = st.session_state.get("generated_design_documents", False)
 
305
  design_documents_approved = st.session_state.get("design_documents_approved", False)
306
+
307
  if design_artifacts_exist:
308
+ expander_label = "Approved Design Documents" if design_documents_approved else "Generated Design Documents"
309
+ with st.expander(expander_label, expanded=False):
310
  design_documents = st.session_state.get("generated_design_documents", "Error: Design documents not generated.")
311
  st.markdown(design_documents)
312
  if st.button("Save Design Documents", key="save_design_documents"):
313
  self._save_artifact(design_documents, "design_documents.txt")
314
+
315
  if not design_documents_approved:
316
+ st.markdown("### Feedback on Design Documents")
317
  decision_options = ("Approve", "Reject")
318
  selected_decision = st.radio(
319
  "Review Decision:",
 
327
  placeholder="Include more details on the design",
328
  key="design_documents_feedback_comments"
329
  )
330
+ submit_disabled = st.session_state.get("feedback_pending", False) or st.session_state.get("design_stage_running", False)
331
  if st.button("Submit Review", key="submit_review_button_design", disabled=submit_disabled):
332
  if st.session_state.get("feedback_pending"):
333
  st.warning("Feedback is already being processed. Please wait.")
 
340
  is_approved = (selected_decision == "Approve")
341
  logger.info(f"Feedback submitted: {'Approved' if is_approved else 'Rejected with comments.'}")
342
  current_stage = "design"
343
+ if current_stage not in st.session_state["feedback"]:
344
+ st.session_state["feedback"][current_stage] = []
345
+ st.session_state["feedback"][current_stage] = ["accept" if is_approved else comments.strip()]
346
  logger.info("Feedback stored: %s", st.session_state["feedback"])
347
  st.session_state["needs_resume_after_feedback"] = True
348
  if not is_approved:
349
  st.session_state["design_documents_generated_flag"] = False
350
  st.session_state["feedback_pending"] = False
351
+ st.write(f"Feedback submitted: {'Approved' if is_approved else 'Rejected with comments.'}")
 
352
  st.write("Session state after feedback: %s", st.session_state)
353
  st.rerun()
354
  else:
355
+ st.success("✅ Design documents approved. Design phase completed.")
356
+ st.markdown("The feedback form is disabled as the Design Phase is complete.")
357
+ else:
358
+ st.info("Design documents will be displayed here after generation.")
 
359
 
360
  @log_entry_exit
361
  def _display_development_phase(self):
362
+ """Displays the development phase details."""
363
+ st.header("Development Phase")
364
+ st.info("Development artifacts are being generated based on approved design documents.")
365
 
366
  @log_entry_exit
367
  def _display_development_artifacts(self):
368
+ """Displays development artifacts and feedback form."""
369
+ st.subheader("Development Artifacts")
370
+ development_artifacts_exist = st.session_state.get("generated_development_artifact", False)
371
+ development_artifact_approved = st.session_state.get("development_artifact_approved", False)
372
+
373
+ if development_artifacts_exist:
374
+ expander_label = "Approved Development Artifacts" if development_artifact_approved else "Generated Development Artifacts"
375
+ with st.expander(expander_label, expanded=False):
376
+ development_artifact = st.session_state.get("generated_development_artifact", "Error: Development artifacts not generated.")
377
+ st.markdown(development_artifact)
378
+ if st.button("Save Development Artifacts", key="save_development_artifacts"):
379
+ self._save_artifact(development_artifact, "development_artifact.txt")
380
+
381
+ if not development_artifact_approved:
382
+ st.markdown("### Feedback on Development Artifacts")
383
+ decision_options = ("Approve", "Reject")
384
+ selected_decision = st.radio(
385
+ "Review Decision:",
386
+ decision_options,
387
+ key="development_artifact_feedback_decision",
388
+ horizontal=True,
389
+ index=0
390
+ )
391
+ comments = st.text_area(
392
+ "Comments (Required for Rejection):",
393
+ placeholder="Include details on code improvements",
394
+ key="development_artifact_feedback_comments"
395
+ )
396
+ submit_disabled = st.session_state.get("feedback_pending", False) or st.session_state.get("development_stage_running", False)
397
+ if st.button("Submit Review", key="submit_review_button_development", disabled=submit_disabled):
398
+ if st.session_state.get("feedback_pending"):
399
+ st.warning("Feedback is already being processed. Please wait.")
400
+ return
401
+ st.session_state["feedback_pending"] = True
402
+ if selected_decision == "Reject" and not comments.strip():
403
+ st.error("Comments are required when rejecting.")
404
+ st.session_state["feedback_pending"] = False
405
+ else:
406
+ is_approved = (selected_decision == "Approve")
407
+ logger.info(f"Feedback submitted: {'Approved' if is_approved else 'Rejected with comments.'}")
408
+ current_stage = "development"
409
+ if current_stage not in st.session_state["feedback"]:
410
+ st.session_state["feedback"][current_stage] = []
411
+ st.session_state["feedback"][current_stage] = ["accept" if is_approved else comments.strip()]
412
+ logger.info("Feedback stored: %s", st.session_state["feedback"])
413
+ st.session_state["needs_resume_after_feedback"] = True
414
+ if not is_approved:
415
+ st.session_state["development_artifact_generated_flag"] = False
416
+ st.session_state["feedback_pending"] = False
417
+ st.write(f"Feedback submitted: {'Approved' if is_approved else 'Rejected with comments.'}")
418
+ st.write("Session state after feedback: %s", st.session_state)
419
+ st.rerun()
420
+ else:
421
+ st.success("✅ Development artifacts approved. Development phase completed.")
422
+ st.markdown("The feedback form is disabled as the Development Phase is complete.")
423
+ else:
424
+ st.info("Development artifacts will be displayed here after generation.")
425
 
426
  @log_entry_exit
427
  def _display_testing_phase(self):
428
+ """Displays the testing phase details."""
429
+ st.header("Testing Phase")
430
+ st.info("Testing artifacts are being generated based on approved development artifacts.")
431
 
432
  @log_entry_exit
433
  def _display_testing_artifacts(self):
434
+ """Displays testing artifacts and feedback form."""
435
+ st.subheader("Testing Artifacts")
436
+ testing_artifacts_exist = st.session_state.get("generated_testing_artifact", False)
437
+ testing_artifact_approved = st.session_state.get("testing_artifact_approved", False)
438
+
439
+ if testing_artifacts_exist:
440
+ expander_label = "Approved Testing Artifacts" if testing_artifact_approved else "Generated Testing Artifacts"
441
+ with st.expander(expander_label, expanded=False):
442
+ testing_artifact = st.session_state.get("generated_testing_artifact", "Error: Testing artifacts not generated.")
443
+ st.markdown(testing_artifact)
444
+ if st.button("Save Testing Artifacts", key="save_testing_artifacts"):
445
+ self._save_artifact(testing_artifact, "testing_artifact.txt")
446
+
447
+ if not testing_artifact_approved:
448
+ st.markdown("### Feedback on Testing Artifacts")
449
+ decision_options = ("Approve", "Reject")
450
+ selected_decision = st.radio(
451
+ "Review Decision:",
452
+ decision_options,
453
+ key="testing_artifact_feedback_decision",
454
+ horizontal=True,
455
+ index=0
456
+ )
457
+ comments = st.text_area(
458
+ "Comments (Required for Rejection):",
459
+ placeholder="Include details on test case improvements",
460
+ key="testing_artifact_feedback_comments"
461
+ )
462
+ submit_disabled = st.session_state.get("feedback_pending", False) or st.session_state.get("testing_stage_running", False)
463
+ if st.button("Submit Review", key="submit_review_button_testing", disabled=submit_disabled):
464
+ if st.session_state.get("feedback_pending"):
465
+ st.warning("Feedback is already being processed. Please wait.")
466
+ return
467
+ st.session_state["feedback_pending"] = True
468
+ if selected_decision == "Reject" and not comments.strip():
469
+ st.error("Comments are required when rejecting.")
470
+ st.session_state["feedback_pending"] = False
471
+ else:
472
+ is_approved = (selected_decision == "Approve")
473
+ logger.info(f"Feedback submitted: {'Approved' if is_approved else 'Rejected with comments.'}")
474
+ current_stage = "testing"
475
+ if current_stage not in st.session_state["feedback"]:
476
+ st.session_state["feedback"][current_stage] = []
477
+ st.session_state["feedback"][current_stage] = ["accept" if is_approved else comments.strip()]
478
+ logger.info("Feedback stored: %s", st.session_state["feedback"])
479
+ st.session_state["needs_resume_after_feedback"] = True
480
+ if not is_approved:
481
+ st.session_state["testing_artifact_generated_flag"] = False
482
+ st.session_state["feedback_pending"] = False
483
+ st.write(f"Feedback submitted: {'Approved' if is_approved else 'Rejected with comments.'}")
484
+ st.write("Session state after feedback: %s", st.session_state)
485
+ st.rerun()
486
+ else:
487
+ st.success("✅ Testing artifacts approved. Testing phase completed.")
488
+ st.markdown("The feedback form is disabled as the Testing Phase is complete.")
489
+ else:
490
+ st.info("Testing artifacts will be displayed here after generation.")
491
 
492
  @log_entry_exit
493
  def _display_deployment_phase(self):
494
+ """Displays the deployment phase details."""
495
+ st.header("Deployment Phase")
496
+ st.info("Deployment artifacts are being generated based on approved testing artifacts.")
497
 
498
  @log_entry_exit
499
  def _display_deployment_artifacts(self):
500
+ """Displays deployment artifacts and feedback form."""
501
+ st.subheader("Deployment Artifacts")
502
+ deployment_artifacts_exist = st.session_state.get("generated_deployment_artifact", False)
503
+ deployment_artifact_approved = st.session_state.get("deployment_artifact_approved", False)
504
+
505
+ if deployment_artifacts_exist:
506
+ expander_label = "Approved Deployment Artifacts" if deployment_artifact_approved else "Generated Deployment Artifacts"
507
+ with st.expander(expander_label, expanded=False):
508
+ deployment_artifact = st.session_state.get("generated_deployment_artifact", "Error: Deployment artifacts not generated.")
509
+ st.markdown(deployment_artifact)
510
+ if st.button("Save Deployment Artifacts", key="save_deployment_artifacts"):
511
+ self._save_artifact(deployment_artifact, "deployment_artifact.txt")
512
+
513
+ if not deployment_artifact_approved:
514
+ st.markdown("### Feedback on Deployment Artifacts")
515
+ decision_options = ("Approve", "Reject")
516
+ selected_decision = st.radio(
517
+ "Review Decision:",
518
+ decision_options,
519
+ key="deployment_artifact_feedback_decision",
520
+ horizontal=True,
521
+ index=0
522
+ )
523
+ comments = st.text_area(
524
+ "Comments (Required for Rejection):",
525
+ placeholder="Include details on deployment improvements",
526
+ key="deployment_artifact_feedback_comments"
527
+ )
528
+ submit_disabled = st.session_state.get("feedback_pending", False) or st.session_state.get("deployment_stage_running", False)
529
+ if st.button("Submit Review", key="submit_review_button_deployment", disabled=submit_disabled):
530
+ if st.session_state.get("feedback_pending"):
531
+ st.warning("Feedback is already being processed. Please wait.")
532
+ return
533
+ st.session_state["feedback_pending"] = True
534
+ if selected_decision == "Reject" and not comments.strip():
535
+ st.error("Comments are required when rejecting.")
536
+ st.session_state["feedback_pending"] = False
537
+ else:
538
+ is_approved = (selected_decision == "Approve")
539
+ logger.info(f"Feedback submitted: {'Approved' if is_approved else 'Rejected with comments.'}")
540
+ current_stage = "deployment"
541
+ if current_stage not in st.session_state["feedback"]:
542
+ st.session_state["feedback"][current_stage] = []
543
+ st.session_state["feedback"][current_stage] = ["accept" if is_approved else comments.strip()]
544
+ logger.info("Feedback stored: %s", st.session_state["feedback"])
545
+ st.session_state["needs_resume_after_feedback"] = True
546
+ if not is_approved:
547
+ st.session_state["deployment_artifact_generated_flag"] = False
548
+ st.session_state["feedback_pending"] = False
549
+ st.write(f"Feedback submitted: {'Approved' if is_approved else 'Rejected with comments.'}")
550
+ st.write("Session state after feedback: %s", st.session_state)
551
+ st.rerun()
552
+ else:
553
+ st.success("✅ Deployment artifacts approved. Deployment phase completed.")
554
+ st.markdown("The feedback form is disabled as the Deployment Phase is complete.")
555
+ else:
556
+ st.info("Deployment artifacts will be displayed here after generation.")
557
 
558
  @log_entry_exit
559
  def _collect_project_requirements(self):
560
  """Displays the form to collect initial project details."""
561
+ DefaultProjectName = "demo_project"
562
+ DefaultDescription = "demo_description"
563
+ DefaultGoals = "demo_goals"
564
+ DefaultScope = "demo_scope"
565
+ DefaultObjectives = "demo_objectives"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
566
 
567
  st.subheader("Define Project Details")
568
  with st.form("sdlc_requirements_form"):
 
582
  st.session_state["sdlc_stage"] = "planning"
583
  logger.info("Initial project details submitted. Running graph for the first time.")
584
  st.session_state["planning_stage_running"] = True
585
+ try:
586
+ self._run_sdlc_graph_initial("planning")
587
+ finally:
588
+ st.session_state["planning_stage_running"] = False
589
  st.rerun()
590
 
591
  @log_entry_exit
592
+ def _run_sdlc_graph_initial(self, stage: str):
593
+ """Runs the SDLC graph for the first time with initial project details."""
594
  session_id = self.config.get("configurable", {}).get("session_id")
595
  if not session_id:
596
  logger.error("Session ID not found in config! Cannot start graph.")
597
  st.error("Critical error: Session ID missing. Please restart.")
598
  return
599
 
600
+ input_data = {"session_id": session_id, "sdlc_stage": stage}
601
  requirements = None
602
  user_stories = None
603
  design_documents = None
 
606
  deployment_artifact = None
607
  logger.info(f"Running SDLC graph initially with input_data: {input_data}...")
608
 
609
+ artifact_description = {
610
+ "planning": "requirements and user stories",
611
+ "design": "design documents",
612
+ "development": "development artifact",
613
+ "testing": "testing artifact",
614
+ "deployment": "deployment artifact"
615
+ }.get(stage, "artifacts")
 
 
 
 
 
616
 
617
  with st.spinner(f"Generating initial {artifact_description}..."):
618
  try:
619
+ for event_dict in self.graph.stream(input_data, self.config):
 
 
 
 
620
  logger.info(f"Initial Run Event: {event_dict}")
 
621
  if "__interrupt__" in event_dict:
622
  logger.info("Graph interrupted as expected after generating artifacts.")
 
 
623
  break
 
624
  for node_name, node_output_dict in event_dict.items():
625
+ if node_name in ["__checkpoint__", "__interrupt__"]:
626
  continue
627
  if node_output_dict is None:
628
  continue
 
 
629
  if not isinstance(node_output_dict, dict):
630
+ logger.warning(f"Node '{node_name}' output is not a dict: {node_output_dict}. Skipping artifact extraction.")
631
  continue
 
632
  logger.info(f"Processing output from node '{node_name}'.")
633
  if "generated_requirements" in node_output_dict:
634
  requirements = node_output_dict["generated_requirements"]
 
659
  st.session_state["design_documents_generated_flag"] = design_documents is not None
660
  st.session_state["development_artifact_generated_flag"] = development_artifact is not None
661
  st.session_state["testing_artifact_generated_flag"] = testing_artifact is not None
662
+ st.session_state["deployment_artifact_generated_flag"] = deployment_artifact is not None
663
  logger.info("Initial graph run finished (interrupted). Artifacts stored in session state.")
664
 
665
  @log_entry_exit
 
669
  feedback_data = st.session_state.get("feedback")
670
  logger.info(f"Feedback data from session state: %s", feedback_data)
671
 
 
672
  if "configurable" not in self.config or "thread_id" not in self.config["configurable"]:
673
  logger.error("Thread ID missing in main config. Cannot resume.")
674
  st.error("Cannot resume workflow: Session thread ID is missing in configuration.")
675
  st.session_state["needs_resume_after_feedback"] = False
 
676
  return
677
+ thread_id = self.config["configurable"]["thread_id"]
 
 
678
 
679
+ update_payload = {}
680
  try:
681
  if feedback_data:
682
  update_payload['feedback'] = feedback_data
683
+ current_stage_value = st.session_state.get("sdlc_stage")
684
+ update_payload['current_stage'] = SDLCStages(current_stage_value)
685
  if isinstance(feedback_data, dict) and current_stage_value in feedback_data:
686
+ stage_feedback = feedback_data.get(current_stage_value, [])
687
+ last_feedback = stage_feedback[-1].strip().lower() if stage_feedback else ""
688
  update_payload['feedback_decision'] = "accept" if last_feedback == "accept" else "reject"
689
  else:
690
+ update_payload['feedback_decision'] = "reject"
691
  logger.info(f"Update payload prepared: {update_payload}")
692
  else:
 
693
  logger.warning("Resume triggered but no feedback data found in session state.")
694
+ update_payload['feedback_decision'] = "reject"
695
+ update_payload['current_stage'] = SDLCStages(st.session_state.get("sdlc_stage", "planning"))
 
 
 
696
  update_payload['last_updated'] = datetime.now().isoformat()
 
697
  except Exception as e:
698
  logger.error(f"Error preparing update payload for thread_id {thread_id}: {e}", exc_info=True)
699
  st.error(f"Error preparing resume data: {e}. Please restart.")
700
  st.session_state["needs_resume_after_feedback"] = False
 
701
  return
702
 
703
  if update_payload:
704
+ logger.info(f"Updating graph state for thread_id {thread_id} with payload: {update_payload}")
705
  try:
706
+ self.graph.update_state(config=self.config, values=update_payload)
 
 
 
 
707
  logger.info(f"[OK] Updated graph state for thread_id {thread_id} with payload: {update_payload}")
708
  except Exception as e:
709
  logger.error(f"Error calling graph.update_state for thread_id {thread_id}: {e}", exc_info=True)
710
  st.error(f"Error updating workflow state: {e}. Please restart.")
711
  st.session_state["needs_resume_after_feedback"] = False
 
712
  return
 
 
713
 
 
 
 
 
 
 
714
  with st.spinner("Processing feedback and continuing workflow..."):
715
  try:
716
  final_state = {}
 
717
  logger.info("Attempting graph stream with Command(resume=True)")
718
  for event in self.graph.stream(Command(resume=True), config=self.config, stream_mode="values"):
719
  logger.debug(f"Resume Stream Event: {event}")
 
720
  node = event.get("log", {}).get("actions", [{}])[0].get("node")
721
  state = event
 
722
  if not isinstance(state, dict):
723
  logger.warning(f"Received non-dict state in stream: {type(state)}. Skipping.")
724
  continue
725
+ if node:
726
+ logger.info(f"Executing node: {node}")
727
+ logger.info(f"Node '{node}' generated state update.")
728
+ final_state.update(state)
 
 
 
 
729
  if "generated_requirements" in state and state["generated_requirements"] is not None:
730
  st.session_state["generated_requirements"] = state["generated_requirements"]
731
  st.session_state["requirements_generated"] = True
 
735
  if "design_documents" in state and state["design_documents"] is not None:
736
  st.session_state["generated_design_documents"] = state["design_documents"]
737
  st.session_state["design_documents_generated_flag"] = True
 
 
 
738
  if "development_artifact" in state and state["development_artifact"] is not None:
739
  st.session_state["generated_development_artifact"] = state["development_artifact"]
740
  st.session_state["development_artifact_generated_flag"] = True
 
743
  st.session_state["testing_artifact_generated_flag"] = True
744
  if "deployment_artifact" in state and state["deployment_artifact"] is not None:
745
  st.session_state["generated_deployment_artifact"] = state["deployment_artifact"]
746
+ st.session_state["deployment_artifact_generated_flag"] = True
 
 
 
747
  except Exception as e:
748
+ logger.error(f"Error during graph stream after resume: {e}", exc_info=True)
749
  st.error(f"An error occurred during workflow resumption: {e}")
750
+ return
 
751
 
 
752
  final_feedback_decision = final_state.get("feedback_decision")
753
  logger.info(f"Final feedback decision after stream: {final_feedback_decision}")
754
 
 
756
  if st.session_state["sdlc_stage"] == "planning":
757
  st.session_state["user_stories_approved"] = True
758
  st.session_state["sdlc_stage"] = "design"
759
+ logger.info("User stories approved. Proceeding to design phase.")
 
 
760
  elif st.session_state["sdlc_stage"] == "design":
761
  st.session_state["design_documents_approved"] = True
762
+ st.session_state["sdlc_stage"] = "development"
763
+ logger.info("Design documents approved. Proceeding to development phase.")
764
  elif st.session_state["sdlc_stage"] == "development":
765
  st.session_state["development_artifact_approved"] = True
766
+ st.session_state["sdlc_stage"] = "testing"
767
+ logger.info("Development artifact approved. Proceeding to testing phase.")
768
  elif st.session_state["sdlc_stage"] == "testing":
769
  st.session_state["testing_artifact_approved"] = True
770
+ st.session_state["sdlc_stage"] = "deployment"
771
+ logger.info("Testing artifact approved. Proceeding to deployment phase.")
772
  elif st.session_state["sdlc_stage"] == "deployment":
773
  st.session_state["deployment_artifact_approved"] = True
774
+ st.session_state["sdlc_stage"] = "complete"
775
+ logger.info("Deployment artifact approved. SDLC complete.")
776
  else:
777
+ logger.error("Unknown SDLC stage: %s", st.session_state["sdlc_stage"])
778
  st.error("Unknown SDLC stage. Cannot proceed.")
 
 
 
779
  st.session_state["needs_resume_after_feedback"] = False
 
780
  return
 
 
781
  else:
782
+ logger.info(f"Graph looped back. Final feedback decision: {final_feedback_decision}")
 
 
783
 
784
  st.session_state["needs_resume_after_feedback"] = False
785
+ logger.info("Graph resumption completed. Approved flags: %s", {
786
+ "planning": st.session_state.get("user_stories_approved"),
787
+ "design": st.session_state.get("design_documents_approved"),
788
+ "development": st.session_state.get("development_artifact_approved"),
789
+ "testing": st.session_state.get("testing_artifact_approved"),
790
+ "deployment": st.session_state.get("deployment_artifact_approved")
791
+ })
792
  st.rerun()
793
 
 
794
  @log_entry_exit
795
  def _save_artifact(self, content: str, filename: str):
796
  """Provides a download link for the given artifact content."""
 
802
  except Exception as e:
803
  logger.error(f"Error creating download link for {filename}: {e}")
804
  st.error(f"Could not generate download link for {filename}.")
805
+
806
+
807
+
808
+
809
+
810
+ # DefaultProjectName = "The Book Nook"
811
+ # DefaultDescription = """Develop a user-friendly mobile application for "The Book Nook," a local bookstore in Bangalore,
812
+ # to allow customers to browse their inventory, place orders online, and learn about upcoming events."""
813
+ # DefaultGoals = """ Increase sales and revenue for The Book Nook.
814
+ # Enhance customer engagement and loyalty.
815
+ # Modernize The Book Nook's presence and reach a wider audience in Bangalore. """
816
+ # DefaultScope = """ Inclusions:
817
+ # Developing a mobile application compatible with Android and iOS.
818
+ # Features: Browsing book catalog with search and filtering, viewing book details (description, author, price, availability), creating user accounts, adding books to a shopping cart, secure online payment integration, order history, push notifications for new arrivals and events, information about store hours and location.
819
+ # Integration with the bookstore's existing inventory management system.
820
+ # Basic user support documentation.
821
+ # Exclusions:
822
+ # Developing a separate tablet application.
823
+ # Implementing a loyalty points program (will be considered in a future phase).
824
+ # Integrating with social media platforms for direct purchasing.
825
+ # Providing real-time inventory updates beyond a daily sync.
826
+ # Developing advanced analytics dashboards for the bookstore owner in this phase."""
827
+ # DefaultObjectives = """Increase online sales by 15% within the first six months of the app launch (Measurable, Achievable, Relevant, Time-bound).
828
+ # Achieve an average user rating of 4.5 stars or higher on both app stores within three months of launch (Measurable, Achievable, Relevant, Time-bound).
829
+ # Acquire 500 new registered app users within the first month of launch (Measurable, Achievable, Relevant, Time-bound).
830
+ # Successfully integrate the app with the existing inventory system with no data loss by the end of the development phase (Measurable, Achievable, Relevant, Time-bound). """
831
+