Skip to content

Commit 728649e

Browse files
committed
prototype components
1 parent 4d7fa5d commit 728649e

File tree

8 files changed

+833
-0
lines changed

8 files changed

+833
-0
lines changed
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import { Plan, PlanWithSteps, Step } from '../models';
2+
3+
// Base API URL - update this to point to your backend API
4+
const API_BASE_URL = process.env.REACT_APP_API_BASE_URL || 'http://localhost:5000/api';
5+
6+
/**
7+
* Service for interacting with Plans and Steps
8+
*/
9+
export const PlanService = {
10+
/**
11+
* Get a plan by ID
12+
* @param sessionId Session identifier
13+
* @param planId Plan identifier
14+
* @returns Promise with the plan
15+
*/
16+
async getPlan(sessionId: string, planId: string): Promise<Plan> {
17+
const response = await fetch(`${API_BASE_URL}/sessions/${sessionId}/plans/${planId}`);
18+
if (!response.ok) {
19+
throw new Error(`Failed to get plan: ${response.statusText}`);
20+
}
21+
return await response.json() as Plan;
22+
},
23+
24+
/**
25+
* Get a plan with its steps
26+
* @param sessionId Session identifier
27+
* @param planId Plan identifier
28+
* @returns Promise with the plan and its steps
29+
*/
30+
async getPlanWithSteps(sessionId: string, planId: string): Promise<PlanWithSteps> {
31+
const response = await fetch(`${API_BASE_URL}/sessions/${sessionId}/plans/${planId}/with-steps`);
32+
if (!response.ok) {
33+
throw new Error(`Failed to get plan with steps: ${response.statusText}`);
34+
}
35+
return await response.json() as PlanWithSteps;
36+
},
37+
38+
/**
39+
* Get all plans for a session
40+
* @param sessionId Session identifier
41+
* @returns Promise with an array of plans
42+
*/
43+
async getPlans(sessionId: string): Promise<Plan[]> {
44+
const response = await fetch(`${API_BASE_URL}/sessions/${sessionId}/plans`);
45+
if (!response.ok) {
46+
throw new Error(`Failed to get plans: ${response.statusText}`);
47+
}
48+
return await response.json() as Plan[];
49+
},
50+
51+
/**
52+
* Create a new plan
53+
* @param plan Plan to create
54+
* @returns Promise with the created plan
55+
*/
56+
async createPlan(plan: Omit<Plan, 'id' | 'timestamp'>): Promise<Plan> {
57+
const response = await fetch(`${API_BASE_URL}/sessions/${plan.session_id}/plans`, {
58+
method: 'POST',
59+
headers: {
60+
'Content-Type': 'application/json',
61+
},
62+
body: JSON.stringify(plan),
63+
});
64+
if (!response.ok) {
65+
throw new Error(`Failed to create plan: ${response.statusText}`);
66+
}
67+
return await response.json() as Plan;
68+
},
69+
70+
/**
71+
* Get a step by ID
72+
* @param sessionId Session identifier
73+
* @param planId Plan identifier
74+
* @param stepId Step identifier
75+
* @returns Promise with the step
76+
*/
77+
async getStep(sessionId: string, planId: string, stepId: string): Promise<Step> {
78+
const response = await fetch(`${API_BASE_URL}/sessions/${sessionId}/plans/${planId}/steps/${stepId}`);
79+
if (!response.ok) {
80+
throw new Error(`Failed to get step: ${response.statusText}`);
81+
}
82+
return await response.json() as Step;
83+
},
84+
85+
/**
86+
* Get all steps for a plan
87+
* @param sessionId Session identifier
88+
* @param planId Plan identifier
89+
* @returns Promise with an array of steps
90+
*/
91+
async getSteps(sessionId: string, planId: string): Promise<Step[]> {
92+
const response = await fetch(`${API_BASE_URL}/sessions/${sessionId}/plans/${planId}/steps`);
93+
if (!response.ok) {
94+
throw new Error(`Failed to get steps: ${response.statusText}`);
95+
}
96+
return await response.json() as Step[];
97+
},
98+
99+
/**
100+
* Update step status and provide feedback
101+
* @param sessionId Session identifier
102+
* @param planId Plan identifier
103+
* @param stepId Step identifier
104+
* @param update Update data
105+
* @returns Promise with the updated step
106+
*/
107+
async updateStep(
108+
sessionId: string,
109+
planId: string,
110+
stepId: string,
111+
update: {
112+
status?: StepStatus,
113+
human_feedback?: string,
114+
updated_action?: string,
115+
}
116+
): Promise<Step> {
117+
const response = await fetch(`${API_BASE_URL}/sessions/${sessionId}/plans/${planId}/steps/${stepId}`, {
118+
method: 'PATCH',
119+
headers: {
120+
'Content-Type': 'application/json',
121+
},
122+
body: JSON.stringify(update),
123+
});
124+
if (!response.ok) {
125+
throw new Error(`Failed to update step: ${response.statusText}`);
126+
}
127+
return await response.json() as Step;
128+
}
129+
};
130+
131+
export default PlanService;
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import React from 'react';
2+
import { PlanStatus } from '../models';
3+
import usePlan from '../hooks/usePlan';
4+
import StepCard from './StepCard';
5+
6+
interface PlanViewProps {
7+
sessionId: string;
8+
planId: string;
9+
}
10+
11+
/**
12+
* Component to display a plan and its steps
13+
*/
14+
const PlanView: React.FC<PlanViewProps> = ({ sessionId, planId }) => {
15+
const {
16+
plan,
17+
loading,
18+
error,
19+
updateStepFeedback,
20+
getStepsAwaitingFeedback,
21+
isPlanComplete
22+
} = usePlan(sessionId, planId);
23+
24+
if (loading) {
25+
return (
26+
<div className="flex justify-center items-center h-64">
27+
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500"></div>
28+
</div>
29+
);
30+
}
31+
32+
if (error) {
33+
return (
34+
<div className="bg-red-100 border-l-4 border-red-500 text-red-700 p-4 rounded">
35+
<p className="font-bold">Error</p>
36+
<p>{error.message}</p>
37+
</div>
38+
);
39+
}
40+
41+
if (!plan) {
42+
return (
43+
<div className="bg-yellow-100 border-l-4 border-yellow-500 text-yellow-700 p-4 rounded">
44+
<p>No plan found with ID: {planId}</p>
45+
</div>
46+
);
47+
}
48+
49+
const handleApproveStep = (stepId: string, feedback?: string) => {
50+
updateStepFeedback(stepId, true, feedback);
51+
};
52+
53+
const handleRejectStep = (stepId: string, feedback: string, updatedAction?: string) => {
54+
updateStepFeedback(stepId, false, feedback, updatedAction);
55+
};
56+
57+
const getPlanStatusBadge = (status: PlanStatus) => {
58+
switch (status) {
59+
case PlanStatus.IN_PROGRESS:
60+
return <span className="px-2 py-1 bg-blue-100 text-blue-800 rounded-full text-xs font-medium">In Progress</span>;
61+
case PlanStatus.COMPLETED:
62+
return <span className="px-2 py-1 bg-green-100 text-green-800 rounded-full text-xs font-medium">Completed</span>;
63+
case PlanStatus.FAILED:
64+
return <span className="px-2 py-1 bg-red-100 text-red-800 rounded-full text-xs font-medium">Failed</span>;
65+
default:
66+
return null;
67+
}
68+
};
69+
70+
const stepsAwaitingFeedback = getStepsAwaitingFeedback();
71+
72+
return (
73+
<div className="max-w-4xl mx-auto p-4">
74+
<div className="mb-8">
75+
<div className="flex justify-between items-center mb-4">
76+
<h1 className="text-2xl font-bold text-gray-900">Plan</h1>
77+
{getPlanStatusBadge(plan.overall_status)}
78+
</div>
79+
80+
<div className="bg-white rounded-lg shadow-sm p-6 mb-6">
81+
<h2 className="text-xl font-medium text-gray-900 mb-2">Goal</h2>
82+
<p className="text-gray-700 whitespace-pre-line mb-4">{plan.initial_goal}</p>
83+
84+
{plan.summary && (
85+
<>
86+
<h2 className="text-xl font-medium text-gray-900 mb-2">Summary</h2>
87+
<p className="text-gray-700 whitespace-pre-line mb-4">{plan.summary}</p>
88+
</>
89+
)}
90+
91+
{plan.human_clarification_request && (
92+
<>
93+
<h2 className="text-xl font-medium text-gray-900 mb-2">Clarification Request</h2>
94+
<p className="text-gray-700 whitespace-pre-line mb-4">{plan.human_clarification_request}</p>
95+
</>
96+
)}
97+
98+
{plan.human_clarification_response && (
99+
<>
100+
<h2 className="text-xl font-medium text-gray-900 mb-2">Your Clarification</h2>
101+
<p className="text-gray-700 whitespace-pre-line">{plan.human_clarification_response}</p>
102+
</>
103+
)}
104+
</div>
105+
106+
<div className="mb-6">
107+
<div className="flex justify-between items-center mb-4">
108+
<h2 className="text-xl font-medium text-gray-900">Steps</h2>
109+
<div className="text-sm text-gray-500">
110+
{plan.completed} of {plan.total_steps} completed
111+
</div>
112+
</div>
113+
114+
{stepsAwaitingFeedback.length > 0 && (
115+
<div className="bg-yellow-50 border-l-4 border-yellow-400 p-4 mb-6">
116+
<div className="flex">
117+
<div className="flex-shrink-0">
118+
<svg className="h-5 w-5 text-yellow-400" viewBox="0 0 20 20" fill="currentColor">
119+
<path fillRule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clipRule="evenodd" />
120+
</svg>
121+
</div>
122+
<div className="ml-3">
123+
<p className="text-sm text-yellow-700">
124+
{stepsAwaitingFeedback.length} {stepsAwaitingFeedback.length === 1 ? 'step' : 'steps'} awaiting your feedback
125+
</p>
126+
</div>
127+
</div>
128+
</div>
129+
)}
130+
131+
<div className="space-y-4">
132+
{plan.steps.map(step => (
133+
<StepCard
134+
key={step.id}
135+
step={step}
136+
onApprove={handleApproveStep}
137+
onReject={handleRejectStep}
138+
/>
139+
))}
140+
</div>
141+
</div>
142+
</div>
143+
</div>
144+
);
145+
};
146+
147+
export default PlanView;

0 commit comments

Comments
 (0)