Skip to content

Commit fd8830e

Browse files
create plan page
1 parent 098b449 commit fd8830e

File tree

15 files changed

+1129
-15
lines changed

15 files changed

+1129
-15
lines changed

src/backend/app_kernel.py

Lines changed: 186 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import json
44
import logging
55
import os
6+
import re
67
import uuid
78
from typing import Dict, List, Optional
89

@@ -27,6 +28,7 @@
2728
from models.messages_kernel import (
2829
AgentMessage,
2930
AgentType,
31+
GeneratePlanRequest,
3032
HumanClarification,
3133
HumanFeedback,
3234
InputTask,
@@ -186,14 +188,22 @@ async def input_task_endpoint(input_task: InputTask, request: Request):
186188
track_event_if_configured(
187189
"RAI failed",
188190
{
189-
"status": "Plan not created",
191+
"status": "Plan not created - RAI validation failed",
190192
"description": input_task.description,
191193
"session_id": input_task.session_id,
192194
},
193195
)
194196

195197
return {
196-
"status": "Plan not created",
198+
"status": "RAI_VALIDATION_FAILED",
199+
"message": "Content Safety Check Failed",
200+
"detail": "Your request contains content that doesn't meet our safety guidelines. Please modify your request to ensure it's appropriate and try again.",
201+
"suggestions": [
202+
"Remove any potentially harmful, inappropriate, or unsafe content",
203+
"Use more professional and constructive language",
204+
"Focus on legitimate business or educational objectives",
205+
"Ensure your request complies with content policies"
206+
]
197207
}
198208
authenticated_user = get_authenticated_user_details(request_headers=request.headers)
199209
user_id = authenticated_user["user_principal_id"]
@@ -345,7 +355,7 @@ async def create_plan_endpoint(input_task: InputTask, request: Request):
345355
description: Error message
346356
"""
347357
# Perform RAI check on the description
348-
if not await rai_success(input_task.description):
358+
if not await rai_success(input_task.description, False):
349359
track_event_if_configured(
350360
"RAI failed",
351361
{
@@ -356,7 +366,18 @@ async def create_plan_endpoint(input_task: InputTask, request: Request):
356366
)
357367
raise HTTPException(
358368
status_code=400,
359-
detail="Task description failed safety validation. Please revise your request."
369+
detail={
370+
"error_type": "RAI_VALIDATION_FAILED",
371+
"message": "Content Safety Check Failed",
372+
"description": "Your request contains content that doesn't meet our safety guidelines. Please modify your request to ensure it's appropriate and try again.",
373+
"suggestions": [
374+
"Remove any potentially harmful, inappropriate, or unsafe content",
375+
"Use more professional and constructive language",
376+
"Focus on legitimate business or educational objectives",
377+
"Ensure your request complies with content policies"
378+
],
379+
"user_action": "Please revise your request and try again"
380+
}
360381
)
361382

362383
# Get authenticated user
@@ -420,6 +441,151 @@ async def create_plan_endpoint(input_task: InputTask, request: Request):
420441
raise HTTPException(status_code=400, detail=f"Error creating plan: {e}")
421442

422443

444+
@app.post("/api/generate_plan")
445+
async def generate_plan_endpoint(generate_plan_request: GeneratePlanRequest, request: Request):
446+
"""
447+
Generate plan steps for an existing plan using the planner agent.
448+
449+
---
450+
tags:
451+
- Plans
452+
parameters:
453+
- name: user_principal_id
454+
in: header
455+
type: string
456+
required: true
457+
description: User ID extracted from the authentication header
458+
- name: body
459+
in: body
460+
required: true
461+
schema:
462+
type: object
463+
properties:
464+
plan_id:
465+
type: string
466+
description: The ID of the existing plan to generate steps for
467+
responses:
468+
200:
469+
description: Plan generation completed successfully
470+
schema:
471+
type: object
472+
properties:
473+
status:
474+
type: string
475+
description: Success message
476+
plan_id:
477+
type: string
478+
description: The ID of the plan that was generated
479+
steps_created:
480+
type: integer
481+
description: Number of steps created
482+
400:
483+
description: Invalid request or processing error
484+
schema:
485+
type: object
486+
properties:
487+
detail:
488+
type: string
489+
description: Error message
490+
404:
491+
description: Plan not found
492+
schema:
493+
type: object
494+
properties:
495+
detail:
496+
type: string
497+
description: Error message
498+
"""
499+
# Get authenticated user
500+
authenticated_user = get_authenticated_user_details(request_headers=request.headers)
501+
user_id = authenticated_user["user_principal_id"]
502+
503+
if not user_id:
504+
track_event_if_configured(
505+
"UserIdNotFound", {"status_code": 400, "detail": "no user"}
506+
)
507+
raise HTTPException(status_code=400, detail="no user")
508+
509+
try:
510+
# Initialize memory store
511+
kernel, memory_store = await initialize_runtime_and_context("", user_id)
512+
513+
# Get the existing plan
514+
plan = await memory_store.get_plan_by_plan_id(plan_id=generate_plan_request.plan_id)
515+
if not plan:
516+
track_event_if_configured(
517+
"GeneratePlanNotFound",
518+
{"status_code": 404, "detail": "Plan not found", "plan_id": generate_plan_request.plan_id}
519+
)
520+
raise HTTPException(status_code=404, detail="Plan not found")
521+
522+
# Create the agents for this session
523+
client = None
524+
try:
525+
client = config.get_ai_project_client()
526+
except Exception as client_exc:
527+
logging.error(f"Error creating AIProjectClient: {client_exc}")
528+
529+
agents = await AgentFactory.create_all_agents(
530+
session_id=plan.session_id,
531+
user_id=user_id,
532+
memory_store=memory_store,
533+
client=client,
534+
)
535+
536+
# Get the group chat manager to process the plan
537+
group_chat_manager = agents[AgentType.GROUP_CHAT_MANAGER.value]
538+
539+
# Create an InputTask from the plan's initial goal
540+
input_task = InputTask(
541+
session_id=plan.session_id,
542+
description=plan.initial_goal
543+
)
544+
545+
# Use the group chat manager to generate the plan steps
546+
await group_chat_manager.handle_input_task(input_task)
547+
548+
# Get the updated plan with steps
549+
updated_plan = await memory_store.get_plan_by_plan_id(plan_id=generate_plan_request.plan_id)
550+
steps = await memory_store.get_steps_by_plan(plan_id=generate_plan_request.plan_id)
551+
552+
# Log successful plan generation
553+
track_event_if_configured(
554+
"PlanGenerated",
555+
{
556+
"status": f"Plan generation completed for plan ID: {generate_plan_request.plan_id}",
557+
"plan_id": generate_plan_request.plan_id,
558+
"session_id": plan.session_id,
559+
"steps_created": len(steps),
560+
},
561+
)
562+
563+
if client:
564+
try:
565+
client.close()
566+
except Exception as e:
567+
logging.error(f"Error closing AIProjectClient: {e}")
568+
569+
return {
570+
"status": "Plan generation completed successfully",
571+
"plan_id": generate_plan_request.plan_id,
572+
"steps_created": len(steps),
573+
}
574+
575+
except HTTPException:
576+
# Re-raise HTTP exceptions
577+
raise
578+
except Exception as e:
579+
track_event_if_configured(
580+
"GeneratePlanError",
581+
{
582+
"plan_id": generate_plan_request.plan_id,
583+
"error": str(e),
584+
},
585+
)
586+
raise HTTPException(status_code=400, detail=f"Error generating plan: {e}")
587+
588+
423589
@app.post("/api/human_feedback")
424590
async def human_feedback_endpoint(human_feedback: HumanFeedback, request: Request):
425591
"""
@@ -588,12 +754,26 @@ async def human_clarification_endpoint(
588754
track_event_if_configured(
589755
"RAI failed",
590756
{
591-
"status": "Clarification is not received",
757+
"status": "Clarification rejected - RAI validation failed",
592758
"description": human_clarification.human_clarification,
593759
"session_id": human_clarification.session_id,
594760
},
595761
)
596-
raise HTTPException(status_code=400, detail="Invalida Clarification")
762+
raise HTTPException(
763+
status_code=400,
764+
detail={
765+
"error_type": "RAI_VALIDATION_FAILED",
766+
"message": "Clarification Safety Check Failed",
767+
"description": "Your clarification contains content that doesn't meet our safety guidelines. Please provide a more appropriate clarification.",
768+
"suggestions": [
769+
"Use clear and professional language",
770+
"Avoid potentially harmful or inappropriate content",
771+
"Focus on providing constructive feedback or clarification",
772+
"Ensure your message complies with content policies"
773+
],
774+
"user_action": "Please revise your clarification and try again"
775+
}
776+
)
597777

598778
authenticated_user = get_authenticated_user_details(request_headers=request.headers)
599779
user_id = authenticated_user["user_principal_id"]

src/backend/models/messages_kernel.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,12 @@ class UserLanguage(KernelBaseModel):
310310
language: str
311311

312312

313+
class GeneratePlanRequest(KernelBaseModel):
314+
"""Message representing a request to generate a plan from an existing plan ID."""
315+
316+
plan_id: str
317+
318+
313319
class ApprovalRequest(KernelBaseModel):
314320
"""Message sent to HumanAgent to request approval for a step."""
315321

src/frontend/src/App.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import React from 'react';
22
import './App.css';
33
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
4-
import { HomePage, PlanPage } from './pages';
4+
import { HomePage, PlanPage, PlanCreatePage } from './pages';
55

66
function App() {
77
return (
88
<Router>
99
<Routes>
1010
<Route path="/" element={<HomePage />} />
11+
<Route path="/plan/:planId/create" element={<PlanCreatePage />} />
1112
<Route path="/plan/:planId" element={<PlanPage />} />
1213
<Route path="*" element={<Navigate to="/" replace />} />
1314
</Routes>

src/frontend/src/components/content/HomeInput.tsx

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import "./../../styles/HomeInput.css";
1414
import { HomeInputProps, quickTasks, QuickTask } from "../../models/homeInput";
1515
import { TaskService } from "../../services/TaskService";
1616
import { NewTaskService } from "../../services/NewTaskService";
17+
import { RAIErrorCard, RAIErrorData } from "../errors";
1718

1819
import ChatInput from "@/coral/modules/ChatInput";
1920
import InlineToaster, { useInlineToaster } from "../toast/InlineToaster";
@@ -26,6 +27,7 @@ const HomeInput: React.FC<HomeInputProps> = ({
2627
}) => {
2728
const [submitting, setSubmitting] = useState(false);
2829
const [input, setInput] = useState("");
30+
const [raiError, setRAIError] = useState<RAIErrorData | null>(null);
2931

3032
const textareaRef = useRef<HTMLTextAreaElement>(null);
3133
const navigate = useNavigate();
@@ -40,6 +42,7 @@ const HomeInput: React.FC<HomeInputProps> = ({
4042

4143
const resetTextarea = () => {
4244
setInput("");
45+
setRAIError(null); // Clear any RAI errors
4346
if (textareaRef.current) {
4447
textareaRef.current.style.height = "auto";
4548
textareaRef.current.focus();
@@ -54,6 +57,7 @@ const HomeInput: React.FC<HomeInputProps> = ({
5457
const handleSubmit = async () => {
5558
if (input.trim()) {
5659
setSubmitting(true);
60+
setRAIError(null); // Clear any previous RAI errors
5761
let id = showToast("Creating a plan", "progress");
5862

5963
try {
@@ -67,17 +71,40 @@ const HomeInput: React.FC<HomeInputProps> = ({
6771
if (response.plan_id && response.plan_id !== null) {
6872
showToast("Plan created!", "success");
6973
dismissToast(id);
70-
navigate(`/plan/${response.plan_id}`);
74+
navigate(`/plan/${response.plan_id}/create`);
7175
} else {
7276
showToast("Failed to create plan", "error");
7377
dismissToast(id);
7478
}
7579
} catch (error: any) {
7680
dismissToast(id);
77-
// Show more specific error message if available
78-
const errorMessage = error instanceof Error ? error.message : "Something went wrong";
79-
showToast(errorMessage, "error");
80-
showToast(JSON.parse(error?.message)?.detail, "error");
81+
82+
// Check if this is an RAI validation error
83+
let errorDetail = null;
84+
try {
85+
// Try to parse the error detail if it's a string
86+
if (typeof error?.response?.data?.detail === 'string') {
87+
errorDetail = JSON.parse(error.response.data.detail);
88+
} else {
89+
errorDetail = error?.response?.data?.detail;
90+
}
91+
} catch (parseError) {
92+
// If parsing fails, use the original error
93+
errorDetail = error?.response?.data?.detail;
94+
}
95+
96+
// Handle RAI validation errors with better UX
97+
if (errorDetail?.error_type === 'RAI_VALIDATION_FAILED') {
98+
setRAIError(errorDetail);
99+
} else {
100+
// Handle other errors with toast messages
101+
const errorMessage = errorDetail?.description ||
102+
errorDetail?.message ||
103+
error?.response?.data?.message ||
104+
error?.message ||
105+
"Something went wrong";
106+
showToast(errorMessage, "error");
107+
}
81108

82109
} finally {
83110
setInput("");
@@ -88,6 +115,7 @@ const HomeInput: React.FC<HomeInputProps> = ({
88115

89116
const handleQuickTaskClick = (task: QuickTask) => {
90117
setInput(task.description);
118+
setRAIError(null); // Clear any RAI errors when selecting a quick task
91119
if (textareaRef.current) {
92120
textareaRef.current.focus();
93121
}
@@ -109,6 +137,20 @@ const HomeInput: React.FC<HomeInputProps> = ({
109137
<Title2>How can I help?</Title2>
110138
</div>
111139

140+
{/* Show RAI error if present */}
141+
{raiError && (
142+
<RAIErrorCard
143+
error={raiError}
144+
onRetry={() => {
145+
setRAIError(null);
146+
if (textareaRef.current) {
147+
textareaRef.current.focus();
148+
}
149+
}}
150+
onDismiss={() => setRAIError(null)}
151+
/>
152+
)}
153+
112154
<ChatInput
113155
ref={textareaRef} // forwarding
114156
value={input}

0 commit comments

Comments
 (0)