Skip to content

Commit

Permalink
update OpenAI demo app
Browse files Browse the repository at this point in the history
  • Loading branch information
vincanger committed Feb 26, 2024
1 parent 99fd761 commit 29996a2
Show file tree
Hide file tree
Showing 5 changed files with 269 additions and 247 deletions.
2 changes: 1 addition & 1 deletion app/main.wasp
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ app SaaSTemplate {
("headlessui", "^0.0.0"),
("@faker-js/faker", "8.3.1"),
("@google-analytics/data", "4.1.0"),
("openai", "^4.24.1"),
("openai", "^4.28.0"),
("prettier", "3.1.1"),
("prettier-plugin-tailwindcss", "0.5.11"),
("zod", "3.22.4"),
Expand Down
259 changes: 149 additions & 110 deletions app/src/client/app/DemoAppPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,21 @@ import getAllTasksByUser from '@wasp/queries/getAllTasksByUser';
import { Task } from '@wasp/entities';
import { CgSpinner } from 'react-icons/cg';
import { TiDelete } from 'react-icons/ti';
import { type GeneratedSchedule } from '../../shared/types';
import { MainTask, Subtask } from '@wasp/shared/types';

export default function DemoAppPage() {
return (
<div className='py-10 lg:mt-10'>
<div className='mx-auto max-w-7xl px-6 lg:px-8'>
<div className='mx-auto max-w-4xl text-center'>
<h2 className='mt-2 text-4xl font-bold tracking-tight text-gray-900 sm:text-5xl dark:text-white'>
<span className='text-yellow-500'>AI</span> Day Scheduler
<span className='text-yellow-500'>AI</span> Day Scheduler
</h2>
</div>
<p className='mx-auto mt-6 max-w-2xl text-center text-lg leading-8 text-gray-600 dark:text-white'>
This example app uses OpenAI's chat completions with function calling to return a structured JSON object. Try it out, enter your day's tasks, and let AI do the rest!
This example app uses OpenAI's chat completions with function calling to return a structured JSON object. Try
it out, enter your day's tasks, and let AI do the rest!
</p>
{/* begin AI-powered Todo List */}
<div className='my-8 border rounded-3xl border-gray-900/10 dark:border-gray-100/10'>
Expand All @@ -36,9 +39,7 @@ export default function DemoAppPage() {
type TodoProps = Pick<Task, 'id' | 'isDone' | 'description' | 'time'>;

function Todo({ id, isDone, description, time }: TodoProps) {
const handleCheckboxChange = async (
e: React.ChangeEvent<HTMLInputElement>
) => {
const handleCheckboxChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
await updateTask({
id,
isDone: e.currentTarget.checked,
Expand Down Expand Up @@ -66,13 +67,7 @@ function Todo({ id, isDone, description, time }: TodoProps) {
checked={isDone}
onChange={handleCheckboxChange}
/>
<span
className={`text-slate-600 ${
isDone ? 'line-through text-slate-500' : ''
}`}
>
{description}
</span>
<span className={`text-slate-600 ${isDone ? 'line-through text-slate-500' : ''}`}>{description}</span>
</div>
<div className='flex items-center gap-2'>
<input
Expand All @@ -86,13 +81,7 @@ function Todo({ id, isDone, description, time }: TodoProps) {
value={time}
onChange={handleTimeChange}
/>
<span
className={`italic text-slate-600 text-xs ${
isDone ? 'text-slate-500' : ''
}`}
>
hrs
</span>
<span className={`italic text-slate-600 text-xs ${isDone ? 'text-slate-500' : ''}`}>hrs</span>
</div>
</div>
<div className='flex items-center justify-end w-15'>
Expand All @@ -104,22 +93,75 @@ function Todo({ id, isDone, description, time }: TodoProps) {
);
}

function NewTaskForm({
handleCreateTask,
}: {
handleCreateTask: typeof createTask;
}) {
function NewTaskForm({ handleCreateTask }: { handleCreateTask: typeof createTask }) {
const [description, setDescription] = useState<string>('');
const [todaysHours, setTodaysHours] = useState<string>('8');
const [response, setResponse] = useState<any>(null);
const [response, setResponse] = useState<GeneratedSchedule | null>({
mainTasks: [
{
name: 'Respond to emails',
priority: 'high',
},
{
name: 'Learn WASP',
priority: 'low',
},
{
name: 'Read a book',
priority: 'medium',
},
],
subtasks: [
{
description: 'Read introduction and chapter 1',
time: 0.5,
mainTaskName: 'Read a book',
},
{
description: 'Read chapter 2 and take notes',
time: 0.3,
mainTaskName: 'Read a book',
},
{
description: 'Read chapter 3 and summarize key points',
time: 0.2,
mainTaskName: 'Read a book',
},
{
description: 'Check and respond to important emails',
time: 1,
mainTaskName: 'Respond to emails',
},
{
description: 'Organize and prioritize remaining emails',
time: 0.5,
mainTaskName: 'Respond to emails',
},
{
description: 'Draft responses to urgent emails',
time: 0.5,
mainTaskName: 'Respond to emails',
},
{
description: 'Watch tutorial video on WASP',
time: 0.5,
mainTaskName: 'Learn WASP',
},
{
description: 'Complete online quiz on the basics of WASP',
time: 1.5,
mainTaskName: 'Learn WASP',
},
{
description: 'Review quiz answers and clarify doubts',
time: 1,
mainTaskName: 'Learn WASP',
},
],
});
const [isPlanGenerating, setIsPlanGenerating] = useState<boolean>(false);

const { data: tasks, isLoading: isTasksLoading } =
useQuery(getAllTasksByUser);

useEffect(() => {
console.log('response', response);
}, [response]);
const { data: tasks, isLoading: isTasksLoading } = useQuery(getAllTasksByUser);

const handleSubmit = async () => {
try {
Expand All @@ -137,8 +179,7 @@ function NewTaskForm({
hours: todaysHours,
});
if (response) {
console.log('response', response);
setResponse(JSON.parse(response));
setResponse(response);
}
} catch (err: any) {
window.alert('Error: ' + (err.message || 'Something went wrong'));
Expand Down Expand Up @@ -179,20 +220,11 @@ function NewTaskForm({
{tasks!! && tasks.length > 0 ? (
<div className='space-y-4'>
{tasks.map((task: Task) => (
<Todo
key={task.id}
id={task.id}
isDone={task.isDone}
description={task.description}
time={task.time}
/>
<Todo key={task.id} id={task.id} isDone={task.isDone} description={task.description} time={task.time} />
))}
<div className='flex flex-col gap-3'>
<div className='flex items-center justify-between gap-3'>
<label
htmlFor='time'
className='text-sm text-gray-600 dark:text-gray-300 text-nowrap font-semibold'
>
<label htmlFor='time' className='text-sm text-gray-600 dark:text-gray-300 text-nowrap font-semibold'>
How many hours will you work today?
</label>
<input
Expand Down Expand Up @@ -231,87 +263,98 @@ function NewTaskForm({

{!!response && (
<div className='flex flex-col'>
<h3 className='text-lg font-semibold text-gray-900 dark:text-white'>
Today's Schedule
</h3>
<h3 className='text-lg font-semibold text-gray-900 dark:text-white'>Today's Schedule</h3>

<TaskTable schedule={response.schedule} />
<TaskTable schedule={response} />
</div>
)}
</div>
);
}

function TaskTable({ schedule }: { schedule: any[] }) {
function TaskTable({ schedule }: { schedule: GeneratedSchedule }) {
return (
<div className='flex flex-col gap-6 py-6'>
{schedule.map((task: any) => (
<table
key={task.name}
className='table-auto w-full border-separate border border-spacing-2 rounded-md border-slate-200 shadow-sm'
>
<thead>
<tr>
<th
className={`flex items-center justify-between gap-5 py-4 px-3 text-slate-800 border rounded-md border-slate-200 ${
task.priority === 'high'
? 'bg-red-50'
: task.priority === 'low'
? 'bg-green-50'
: 'bg-yellow-50'
}`}
>
<span>{task.name}</span>
<span className='opacity-70 text-xs font-medium italic'>
{' '}
{task.priority} priority
</span>
</th>
</tr>
</thead>
<tbody className=''>
{task.subtasks.map((subtask: { description: any; time: any }) => (
<tr>
<td
className={`flex items-center justify-between py-2 px-3 text-slate-600 border rounded-md border-purple-100 bg-purple-50`}
>
<Subtask
description={subtask.description}
time={subtask.time}
/>
</td>
</tr>
))}
<table className='table-auto w-full border-separate border border-spacing-2 rounded-md border-slate-200 shadow-sm'>
{!!schedule.mainTasks ? (
schedule.mainTasks
.map((mainTask) => <MainTask key={mainTask.name} mainTask={mainTask} subtasks={schedule.subtasks} />)
.sort((a, b) => {
const priorityOrder = ['low', 'medium', 'high'];
if (a.props.mainTask.priority && b.props.mainTask.priority) {
return (
priorityOrder.indexOf(b.props.mainTask.priority) - priorityOrder.indexOf(a.props.mainTask.priority)
);
} else {
return 0;
}
})
) : (
<div className='text-slate-600 text-center'>OpenAI didn't return any Main Tasks. Try again.</div>
)}
</table>

{task.breaks.map((breakItem: { description: any; time: any }) => (
<tr key={breakItem.description}>
<td
className={`flex items-center justify-between py-2 px-3 text-slate-600 border rounded-md border-purple-100 bg-purple-50`}
>
<Subtask
description={breakItem.description}
time={breakItem.time}
/>
</td>
</tr>
))}
</tbody>
</table>
))}
{/* ))} */}
</div>
);
}

function MainTask({ mainTask, subtasks }: { mainTask: MainTask; subtasks: Subtask[] }) {
return (
<>
<thead>
<tr>
<th
className={`flex items-center justify-between gap-5 py-4 px-3 text-slate-800 border rounded-md border-slate-200 bg-opacity-70 ${
mainTask.priority === 'high'
? 'bg-red-100'
: mainTask.priority === 'low'
? 'bg-green-100'
: 'bg-yellow-100'
}`}
>
<span>{mainTask.name}</span>
<span className='opacity-70 text-xs font-medium italic'> {mainTask.priority} priority</span>
</th>
</tr>
</thead>
{!!subtasks ? (
subtasks.map((subtask) => {
if (subtask.mainTaskName === mainTask.name) {
return (
<tbody key={subtask.description}>
<tr>
<td
className={`flex items-center justify-between gap-4 py-2 px-3 text-slate-600 border rounded-md border-purple-100 bg-opacity-60 ${
mainTask.priority === 'high'
? 'bg-red-50'
: mainTask.priority === 'low'
? 'bg-green-50'
: 'bg-yellow-50'
}`}
>
<Subtask description={subtask.description} time={subtask.time} />
</td>
</tr>
</tbody>
);
}
})
) : (
<div className='text-slate-600 text-center'>OpenAI didn't return any Subtasks. Try again.</div>
)}
</>
);
}

function Subtask({ description, time }: { description: string; time: number }) {
const [isDone, setIsDone] = useState<boolean>(false);

const convertHrsToMinutes = (time: number) => {
if (time === 0) return 0;
const hours = Math.floor(time);
const minutes = Math.round((time - hours) * 60);
return `${hours > 0 ? hours + 'hr' : ''} ${
minutes > 0 ? minutes + 'min' : ''
}`;
return `${hours > 0 ? hours + 'hr' : ''} ${minutes > 0 ? minutes + 'min' : ''}`;
};

const minutes = useMemo(() => convertHrsToMinutes(time), [time]);
Expand All @@ -325,17 +368,13 @@ function Subtask({ description, time }: { description: string; time: number }) {
onChange={(e) => setIsDone(e.currentTarget.checked)}
/>
<span
className={`text-slate-600 ${
className={`leading-tight justify-self-start w-full text-slate-600 ${
isDone ? 'line-through text-slate-500 opacity-50' : ''
}`}
>
{description}
</span>
<span
className={`text-slate-600 ${
isDone ? 'line-through text-slate-500 opacity-50' : ''
}`}
>
<span className={`text-slate-600 text-right ${isDone ? 'line-through text-slate-500 opacity-50' : ''}`}>
{minutes}
</span>
</>
Expand Down

0 comments on commit 29996a2

Please sign in to comment.