Skip to content

Commit a49b513

Browse files
authored
Merge pull request #18 from oasisprotocol/mz/forms
Init create flow forms
2 parents 047ae49 + 452b8b5 commit a49b513

File tree

7 files changed

+381
-7
lines changed

7 files changed

+381
-7
lines changed

src/pages/CreateApp/AgentStep.tsx

Lines changed: 107 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,75 @@
1-
import { type FC } from 'react';
1+
import { type FC, useEffect } from 'react';
22
import { CreateLayout } from './CreateLayout';
33
import { CreateFormHeader } from './CreateFormHeader';
44
import { CreateFormNavigation } from './CreateFormNavigation';
5+
import { z } from 'zod';
6+
import { zodResolver } from '@hookform/resolvers/zod';
7+
import { useForm, useWatch } from 'react-hook-form';
8+
import { InputFormField } from './InputFormField';
9+
import { SelectFormField } from './SelectFormField';
10+
11+
const formSchema = z.object({
12+
modelProvider: z.string().min(1, {
13+
message: 'Model provider is required.',
14+
}),
15+
model: z.string().min(1, {
16+
message: 'Model is required.',
17+
}),
18+
apiKey: z.string().min(1, {
19+
message: 'API key is required.',
20+
}),
21+
prompt: z.string().min(1, {
22+
message: 'Prompt is required.',
23+
}),
24+
});
525

626
type AgentStepProps = {
727
handleNext: () => void;
828
handleBack: () => void;
929
};
1030

1131
export const AgentStep: FC<AgentStepProps> = ({ handleNext, handleBack }) => {
32+
const form = useForm<z.infer<typeof formSchema>>({
33+
resolver: zodResolver(formSchema),
34+
defaultValues: {
35+
modelProvider: '',
36+
model: '',
37+
apiKey: '',
38+
prompt: '',
39+
},
40+
});
41+
42+
const modelProvider = useWatch({
43+
control: form.control,
44+
name: 'modelProvider',
45+
});
46+
47+
useEffect(() => {
48+
if (modelProvider === 'openai') {
49+
form.setValue('model', 'gpt-4o');
50+
} else if (modelProvider === 'anthropic') {
51+
form.setValue('model', 'sonnet-3.7');
52+
} else {
53+
form.setValue('model', '');
54+
}
55+
}, [modelProvider, form]);
56+
57+
function onSubmit(values: z.infer<typeof formSchema>) {
58+
console.log('Agent values:', values);
59+
handleNext();
60+
}
61+
62+
const getModelOptions = () => {
63+
if (modelProvider === 'openai') {
64+
return [{ value: 'gpt-4o', label: 'ChatGPT 4o' }];
65+
} else if (modelProvider === 'anthropic') {
66+
return [{ value: 'sonnet-3.7', label: 'Sonnet-3.7' }];
67+
}
68+
return [];
69+
};
70+
71+
const formHasErrors = !form.formState.isValid;
72+
1273
return (
1374
<CreateLayout
1475
currentStep={2}
@@ -24,7 +85,51 @@ export const AgentStep: FC<AgentStepProps> = ({ handleNext, handleBack }) => {
2485
title="Agent Specific Stuff"
2586
description="At varius sit sit netus at integer vitae posuere id. Nulla imperdiet vestibulum amet ultrices egestas. Bibendum sed integer ac eget."
2687
/>
27-
<CreateFormNavigation handleNext={handleNext} handleBack={handleBack} />
88+
89+
<form
90+
onSubmit={form.handleSubmit(onSubmit)}
91+
className="space-y-6 mb-6 w-full"
92+
>
93+
<SelectFormField
94+
control={form.control}
95+
name="modelProvider"
96+
label="Model Provider"
97+
placeholder="Select a model provider"
98+
options={[
99+
{ value: 'openai', label: 'OpenAI' },
100+
{ value: 'anthropic', label: 'Anthropic' },
101+
]}
102+
/>
103+
104+
<SelectFormField
105+
control={form.control}
106+
name="model"
107+
label="Select Model"
108+
placeholder="Select a model"
109+
options={getModelOptions()}
110+
/>
111+
112+
<InputFormField
113+
control={form.control}
114+
name="apiKey"
115+
label="Model Provider API Key"
116+
placeholder="Paste or type key here"
117+
/>
118+
119+
<InputFormField
120+
control={form.control}
121+
name="prompt"
122+
label="Prompt"
123+
placeholder="Instructions for the agent on how to act, behave..."
124+
type="textarea"
125+
/>
126+
127+
<CreateFormNavigation
128+
handleNext={form.handleSubmit(onSubmit)}
129+
handleBack={handleBack}
130+
disabled={formHasErrors || !form.formState.isDirty}
131+
/>
132+
</form>
28133
</CreateLayout>
29134
);
30135
};

src/pages/CreateApp/BuildStep.tsx

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,41 @@ import { type FC } from 'react';
22
import { CreateLayout } from './CreateLayout';
33
import { CreateFormHeader } from './CreateFormHeader';
44
import { CreateFormNavigation } from './CreateFormNavigation';
5+
import { z } from 'zod';
6+
import { zodResolver } from '@hookform/resolvers/zod';
7+
import { useForm } from 'react-hook-form';
8+
import { InputFormField } from './InputFormField';
9+
10+
const formSchema = z.object({
11+
artifacts: z.string().min(1, {
12+
message: 'Artifacts are required.',
13+
}),
14+
provider: z.string().min(1, {
15+
message: 'Provider is required.',
16+
}),
17+
});
518

619
type AgentStepProps = {
720
handleNext: () => void;
821
handleBack: () => void;
922
};
1023

1124
export const BuildStep: FC<AgentStepProps> = ({ handleNext, handleBack }) => {
25+
const form = useForm<z.infer<typeof formSchema>>({
26+
resolver: zodResolver(formSchema),
27+
defaultValues: {
28+
artifacts: 'oasis boot 0.5.0, ROFL container 0.5.1',
29+
provider: 'OPF',
30+
},
31+
});
32+
33+
const onSubmit = (values: z.infer<typeof formSchema>) => {
34+
console.log('Metadata values:', values);
35+
handleNext();
36+
};
37+
38+
const formHasErrors = !form.formState.isValid;
39+
1240
return (
1341
<CreateLayout
1442
currentStep={3}
@@ -24,7 +52,31 @@ export const BuildStep: FC<AgentStepProps> = ({ handleNext, handleBack }) => {
2452
title="Build and Deploy"
2553
description="At varius sit sit netus at integer vitae posuere id. Nulla imperdiet vestibulum amet ultrices egestas. Bibendum sed integer ac eget."
2654
/>
27-
<CreateFormNavigation handleNext={handleNext} handleBack={handleBack} />
55+
56+
<form
57+
onSubmit={form.handleSubmit(onSubmit)}
58+
className="space-y-6 mb-6 w-full"
59+
>
60+
<InputFormField
61+
control={form.control}
62+
name="artifacts"
63+
label="Base Artifacts"
64+
placeholder="oasis boot 0.5.0, ROFL container 0.5.1"
65+
/>
66+
67+
<InputFormField
68+
control={form.control}
69+
name="provider"
70+
label="Provider"
71+
placeholder="OPF"
72+
/>
73+
74+
<CreateFormNavigation
75+
handleNext={handleNext}
76+
handleBack={handleBack}
77+
disabled={formHasErrors}
78+
/>
79+
</form>
2880
</CreateLayout>
2981
);
3082
};

src/pages/CreateApp/CreateFormHeader.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export const CreateFormHeader: FC<CreateFormHeaderProps> = ({
1010
description,
1111
}) => {
1212
return (
13-
<div className="mb-8">
13+
<div>
1414
<h1 className="text-2xl font-white font-bold mb-2">{title}</h1>
1515
<p className="text-muted-foreground text-md max-w-md">{description}</p>
1616
</div>

src/pages/CreateApp/CreateFormNavigation.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,22 @@ import { Button } from '@oasisprotocol/ui-library/src/components/ui/button';
44
type CreateFormNavigationProps = {
55
handleNext: () => void;
66
handleBack?: () => void;
7+
disabled?: boolean;
78
};
89

910
export const CreateFormNavigation: FC<CreateFormNavigationProps> = ({
1011
handleNext,
1112
handleBack,
13+
disabled = false,
1214
}) => {
1315
return (
14-
<div className="flex gap-4 w-full">
16+
<div className="flex gap-4 w-full pt-4">
1517
{handleBack && (
1618
<Button className="flex-1" variant="secondary" onClick={handleBack}>
1719
Back
1820
</Button>
1921
)}
20-
<Button className="flex-1" onClick={handleNext}>
22+
<Button className="flex-1" onClick={handleNext} disabled={disabled}>
2123
Continue
2224
</Button>
2325
</div>
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { type ReactNode } from 'react';
2+
import {
3+
Controller,
4+
type Control,
5+
type FieldValues,
6+
type Path,
7+
} from 'react-hook-form';
8+
import { Input } from '@oasisprotocol/ui-library/src/components/ui/input';
9+
import { Textarea } from '@oasisprotocol/ui-library/src/components/ui/textarea';
10+
import { Label } from '@oasisprotocol/ui-library/src/components/ui/label';
11+
12+
type InputFormFieldProps<T extends FieldValues> = {
13+
control: Control<T>;
14+
name: Path<T>;
15+
label: string;
16+
placeholder?: string;
17+
type?: 'input' | 'textarea';
18+
};
19+
20+
export const InputFormField = <T extends FieldValues>({
21+
control,
22+
name,
23+
label,
24+
placeholder,
25+
type = 'input',
26+
}: InputFormFieldProps<T>): ReactNode => {
27+
return (
28+
<div className="grid gap-2">
29+
<Label htmlFor={name}>{label}</Label>
30+
<Controller
31+
control={control}
32+
name={name}
33+
render={({ field, fieldState }) => (
34+
<>
35+
{type === 'input' ? (
36+
<Input
37+
id={name}
38+
placeholder={placeholder}
39+
{...field}
40+
aria-invalid={!!fieldState.error}
41+
/>
42+
) : (
43+
<Textarea
44+
id={name}
45+
placeholder={placeholder}
46+
{...field}
47+
aria-invalid={!!fieldState.error}
48+
/>
49+
)}
50+
{fieldState.error && (
51+
<div className="text-destructive text-sm">
52+
{fieldState.error.message}
53+
</div>
54+
)}
55+
</>
56+
)}
57+
/>
58+
</div>
59+
);
60+
};

0 commit comments

Comments
 (0)