diff --git a/apps/web-remix/app/api/pipeline/pipeline.contracts.ts b/apps/web-remix/app/api/pipeline/pipeline.contracts.ts index da378facf..a6f51aa1b 100644 --- a/apps/web-remix/app/api/pipeline/pipeline.contracts.ts +++ b/apps/web-remix/app/api/pipeline/pipeline.contracts.ts @@ -7,14 +7,38 @@ import { ExtendedBlockConfig, UpdateBlockConfig, } from '~/api/blockType/blockType.contracts'; -import type { IInterfaceConfigFormProperty } from '~/components/pages/pipelines/pipeline.types'; +import type { + IInterfaceConfigFormOutputProperty, + IInterfaceConfigFormProperty, + IInterfaceConfigProperty, +} from '~/components/pages/pipelines/pipeline.types'; import { PaginationMeta } from '~/components/pagination/pagination.types'; -export const InterfaceConfigFormProperty = z.object({ +export const InterfaceConfigProperty = z.object({ name: z.string(), type: z.string(), }); -export const InterfaceConfigForm = z.object({ + +export const InterfaceConfigFormProperty = InterfaceConfigProperty.extend({ + label: z.string().optional().default(''), + description: z.string().optional().default(''), + required: z + .union([z.boolean(), z.string().transform((v) => v === 'on')]) + .optional() + .default(false), +}); + +export const InterfaceConfigFormOutputProperty = + InterfaceConfigFormProperty.omit({ required: true }); + +export const CommonInterfaceConfigForm = z.object({ + public: z + .union([z.boolean(), z.string().transform((v) => v === 'on')]) + .optional() + .default(false), +}); + +export const InterfaceFormConfigForm = CommonInterfaceConfigForm.extend({ inputs: z .union([ z @@ -32,56 +56,64 @@ export const InterfaceConfigForm = z.object({ .transform( (value) => JSON.parse(value) as IInterfaceConfigFormProperty[], ), - z.array(InterfaceConfigFormProperty), + z.array(InterfaceConfigFormOutputProperty), ]) .default([]), - public: z - .union([z.boolean(), z.string().transform((v) => v === 'on')]) - .optional() - .default(false), }); -export const InterfaceWebchatConfigForm = InterfaceConfigForm.extend({ +export const InterfaceWebchatConfigForm = CommonInterfaceConfigForm.extend({ description: z .string() .optional() .default('Hello. How can I help you today?'), suggested_messages: z.array(z.string()).optional().default([]), + inputs: z + .union([ + z + .string() + .transform((value) => JSON.parse(value) as IInterfaceConfigProperty[]), + z.array(InterfaceConfigProperty), + ]) + .default([]), + outputs: z + .union([ + z + .string() + .transform((value) => JSON.parse(value) as IInterfaceConfigProperty[]), + z.array(InterfaceConfigProperty), + ]) + .default([]), audio_inputs: z .union([ z .string() - .transform( - (value) => JSON.parse(value) as IInterfaceConfigFormProperty[], - ), - z.array(InterfaceConfigFormProperty), + .transform((value) => JSON.parse(value) as IInterfaceConfigProperty[]), + z.array(InterfaceConfigProperty), ]) .default([]), audio_outputs: z .union([ z .string() - .transform( - (value) => JSON.parse(value) as IInterfaceConfigFormProperty[], - ), - z.array(InterfaceConfigFormProperty), + .transform((value) => JSON.parse(value) as IInterfaceConfigProperty[]), + z.array(InterfaceConfigProperty), ]) .default([]), }); export const InterfaceConfig = z.object({ webchat: InterfaceWebchatConfigForm.optional().default({ - inputs: [] as IInterfaceConfigFormProperty[], - outputs: [] as IInterfaceConfigFormProperty[], - audio_inputs: [] as IInterfaceConfigFormProperty[], - audio_outputs: [] as IInterfaceConfigFormProperty[], + inputs: [] as IInterfaceConfigProperty[], + outputs: [] as IInterfaceConfigProperty[], + audio_inputs: [] as IInterfaceConfigProperty[], + audio_outputs: [] as IInterfaceConfigProperty[], description: 'Hello. How can I help you today?', suggested_messages: [] as string[], public: false, }), - form: InterfaceConfigForm.optional().default({ + form: InterfaceFormConfigForm.optional().default({ inputs: [] as IInterfaceConfigFormProperty[], - outputs: [] as IInterfaceConfigFormProperty[], + outputs: [] as IInterfaceConfigFormOutputProperty[], public: false, }), }); @@ -93,34 +125,34 @@ export const SafeInterfaceConfig = z ? c : { webchat: { - inputs: [] as IInterfaceConfigFormProperty[], - outputs: [] as IInterfaceConfigFormProperty[], - audio_outputs: [] as IInterfaceConfigFormProperty[], - audio_inputs: [] as IInterfaceConfigFormProperty[], + inputs: [] as IInterfaceConfigProperty[], + outputs: [] as IInterfaceConfigProperty[], + audio_outputs: [] as IInterfaceConfigProperty[], + audio_inputs: [] as IInterfaceConfigProperty[], description: 'Hello. How can I help you today?', suggested_messages: [] as string[], public: false, }, form: { inputs: [] as IInterfaceConfigFormProperty[], - outputs: [] as IInterfaceConfigFormProperty[], + outputs: [] as IInterfaceConfigFormOutputProperty[], public: false, }, }, ) .default({ webchat: { - inputs: [] as IInterfaceConfigFormProperty[], - outputs: [] as IInterfaceConfigFormProperty[], - audio_outputs: [] as IInterfaceConfigFormProperty[], - audio_inputs: [] as IInterfaceConfigFormProperty[], + inputs: [] as IInterfaceConfigProperty[], + outputs: [] as IInterfaceConfigProperty[], + audio_outputs: [] as IInterfaceConfigProperty[], + audio_inputs: [] as IInterfaceConfigProperty[], description: 'Hello. How can I help you today?', suggested_messages: [] as string[], public: false, }, form: { inputs: [] as IInterfaceConfigFormProperty[], - outputs: [] as IInterfaceConfigFormProperty[], + outputs: [] as IInterfaceConfigFormOutputProperty[], public: false, }, }); @@ -352,5 +384,3 @@ export const PipelineRunLogsResponse = z.object({ export type IPipelineRunLogsResponse = z.TypeOf; export type IPipelineRunLog = z.TypeOf; - - diff --git a/apps/web-remix/app/components/form/fields/select.field.tsx b/apps/web-remix/app/components/form/fields/select.field.tsx index 21bcf6eaf..58202c079 100644 --- a/apps/web-remix/app/components/form/fields/select.field.tsx +++ b/apps/web-remix/app/components/form/fields/select.field.tsx @@ -40,7 +40,7 @@ export const SelectField = ({ } {...getInputProps()} /> - {label} + {label ? {label} : null} & { validationBehavior?: Partial; - ref?: React.RefObject; + ref?: React.RefObject | null; }) => { const { name, getInputProps, error } = useFieldContext({ validationBehavior, @@ -58,7 +58,7 @@ export function ResettableTextInputField({ size, ...props }: Partial & { label: string }) { - const inputRef = useRef(null); + const inputRef = useRef(null); const { name } = useFieldContext({ validationBehavior: { initial: 'onChange', diff --git a/apps/web-remix/app/components/form/inputs/select/select.input.css b/apps/web-remix/app/components/form/inputs/select/select.input.css index 9cc4433d3..b0a6d2116 100644 --- a/apps/web-remix/app/components/form/inputs/select/select.input.css +++ b/apps/web-remix/app/components/form/inputs/select/select.input.css @@ -11,7 +11,7 @@ } .rc-select .rc-select-selector .rc-select-selection-search-input { - @apply !bg-white !shadow-none !ring-0 !text-foreground !text-sm !h-[40px] !outline-none; + @apply !bg-white !shadow-none !ring-0 !text-foreground !text-sm !h-[36px] !outline-none; } .rc-select .rc-select-selection-placeholder { diff --git a/apps/web-remix/app/components/form/inputs/select/select.input.tsx b/apps/web-remix/app/components/form/inputs/select/select.input.tsx index dba29ecde..35afca622 100644 --- a/apps/web-remix/app/components/form/inputs/select/select.input.tsx +++ b/apps/web-remix/app/components/form/inputs/select/select.input.tsx @@ -11,7 +11,7 @@ export const SelectInput: React.FC = ({ ...props }) => { return ( +
} > {() => } diff --git a/apps/web-remix/app/components/pages/interfaces/form/formInterface.form.tsx b/apps/web-remix/app/components/pages/interfaces/form/formInterface.form.tsx index 307b22fb8..1b697ba12 100644 --- a/apps/web-remix/app/components/pages/interfaces/form/formInterface.form.tsx +++ b/apps/web-remix/app/components/pages/interfaces/form/formInterface.form.tsx @@ -9,11 +9,14 @@ type PropertyValue = | null | undefined; +// todo refactor this form after bum to rmv + type State = { formValues: { [key: string]: PropertyValue; }; formErrors: Record; + isSubmitting?: boolean; }; @@ -89,8 +92,10 @@ export const FormContext = React.createContext<{ export function useForm({ defaultValues, onSubmit, + requiredFields, }: { defaultValues?: State['formValues']; + requiredFields?: string[]; onSubmit?: ( data: State['formValues'], e: React.FormEvent, @@ -104,11 +109,14 @@ export function useForm({ const validate = () => { const errors: Record = {}; - Object.entries(state.formValues).forEach(([key, value]) => { - if (!value) { - errors[key] = ['This field is required']; - } - }); + + if (requiredFields) { + requiredFields.forEach((field) => { + if (!state.formValues[field]) { + errors[field] = ['This field is required']; + } + }); + } dispatch({ type: 'SET_ERRORS', payload: errors }); diff --git a/apps/web-remix/app/components/pages/interfaces/form/formInterface.reducer.tsx b/apps/web-remix/app/components/pages/interfaces/form/formInterface.reducer.tsx index e325c7852..67097ae21 100644 --- a/apps/web-remix/app/components/pages/interfaces/form/formInterface.reducer.tsx +++ b/apps/web-remix/app/components/pages/interfaces/form/formInterface.reducer.tsx @@ -77,6 +77,16 @@ export function formInterfaceReducer( case 'DONE': return { ...state, + outputs: Object.keys(state.outputs).reduce( + (acc, key) => { + acc[key] = { + ...state.outputs[key], + isCompleted: true, + }; + return acc; + }, + {} as Record, + ), isWaitingForOutputs: false, }; diff --git a/apps/web-remix/app/components/pages/pipelines/build/FloatingInterfaces/FloatingChat.tsx b/apps/web-remix/app/components/pages/pipelines/build/FloatingInterfaces/FloatingChat.tsx index 6aa8498a6..ba71a11be 100644 --- a/apps/web-remix/app/components/pages/pipelines/build/FloatingInterfaces/FloatingChat.tsx +++ b/apps/web-remix/app/components/pages/pipelines/build/FloatingInterfaces/FloatingChat.tsx @@ -3,14 +3,14 @@ import { MessageCircle } from 'lucide-react'; import { IconButton, IconButtonProps } from '~/components/iconButton'; import { BasicLink } from '~/components/link/BasicLink'; -import { IInterfaceConfigForm } from '~/components/pages/pipelines/pipeline.types'; +import { IInterfaceWebchatConfigForm } from '~/components/pages/pipelines/pipeline.types'; import { useOrganizationId } from '~/hooks/useOrganizationId'; import { usePipelineId } from '~/hooks/usePipelineId'; import { cn } from '~/utils/cn'; import { routes } from '~/utils/routes.utils'; export interface FloatingChatProps { - config: IInterfaceConfigForm; + config: IInterfaceWebchatConfigForm; chatUrl: string; } diff --git a/apps/web-remix/app/components/pages/pipelines/build/editBlock/page.tsx b/apps/web-remix/app/components/pages/pipelines/build/editBlock/page.tsx index 6f3ab604b..f5bc8139a 100644 --- a/apps/web-remix/app/components/pages/pipelines/build/editBlock/page.tsx +++ b/apps/web-remix/app/components/pages/pipelines/build/editBlock/page.tsx @@ -13,10 +13,10 @@ import type { IBlockConfig, IConfigConnection, IInterfaceConfig, + IInterfaceConfigProperty, INode, IPipeline, } from '~/components/pages/pipelines/pipeline.types'; -import { IInterfaceConfigFormProperty } from '~/components/pages/pipelines/pipeline.types'; import { getEdges, getNodes, @@ -211,8 +211,8 @@ function validateInterfaceConfigs( } function validateInterfaceProperty(updated: IExtendedBlockConfig) { - return (property: IInterfaceConfigFormProperty) => { - const updatedProperty: IInterfaceConfigFormProperty = { ...property }; + return (property: IInterfaceConfigProperty) => { + const updatedProperty: IInterfaceConfigProperty = { ...property }; if (property.name === updated.oldName) { updatedProperty.name = updated.name; diff --git a/apps/web-remix/app/components/pages/pipelines/interface/form/InterfaceConfigForm.tsx b/apps/web-remix/app/components/pages/pipelines/interface/form/InterfaceConfigForm.tsx index 7866110c1..1988910a0 100644 --- a/apps/web-remix/app/components/pages/pipelines/interface/form/InterfaceConfigForm.tsx +++ b/apps/web-remix/app/components/pages/pipelines/interface/form/InterfaceConfigForm.tsx @@ -1,18 +1,36 @@ -import React, { useMemo } from 'react'; +import React, { useMemo, useState } from 'react'; import { withZod } from '@remix-validated-form/with-zod'; -import { ValidatedForm } from 'remix-validated-form'; +import startCase from 'lodash.startcase'; +import { Trash } from 'lucide-react'; +import { + useControlField, + useFieldArray, + ValidatedForm, +} from 'remix-validated-form'; -import { InterfaceConfig } from '~/api/pipeline/pipeline.contracts'; +import { + InterfaceConfig, + InterfaceConfigFormProperty, +} from '~/api/pipeline/pipeline.contracts'; import { CheckboxInputField } from '~/components/form/fields/checkbox.field'; -import { Field } from '~/components/form/fields/field.context'; -import { SelectField } from '~/components/form/fields/select.field'; +import { Field, HiddenField } from '~/components/form/fields/field.context'; +import { TextInputField } from '~/components/form/fields/text.field'; +import { SelectInput } from '~/components/form/inputs/select/select.input'; import { SubmitButton } from '~/components/form/submit'; +import { IconButton } from '~/components/iconButton'; import { toSelectOption } from '~/components/pages/pipelines/interface/interface.utils'; import type { + IBlockConfig, IInterfaceConfig, + IInterfaceConfigFormOutputProperty, + IInterfaceConfigFormProperty, + IInterfaceConfigProperty, IPipeline, } from '~/components/pages/pipelines/pipeline.types'; +import { errorToast } from '~/components/toasts/errorToast'; +import { Button } from '~/components/ui/button'; import { Label } from '~/components/ui/label'; +import { cn } from '~/utils/cn'; interface InterfaceConfigFormProps { pipeline: IPipeline; @@ -37,19 +55,16 @@ export const InterfaceConfigForm: React.FC = ({ e: React.FormEvent, ) => { e.preventDefault(); + const inputs = data.form.inputs.map((input) => { - const parsed = JSON.parse(input as unknown as string); return { - name: parsed.name, - type: parsed.type, - }; + ...input, + } as IInterfaceConfigFormProperty; }); const outputs = data.form.outputs.map((output) => { - const parsed = JSON.parse(output as unknown as string); return { - name: parsed.name, - type: parsed.type, - }; + ...output, + } as IInterfaceConfigFormOutputProperty; }); const body = { @@ -60,6 +75,7 @@ export const InterfaceConfigForm: React.FC = ({ public: data.form.public, }, }; + onSubmit(body); }; @@ -70,22 +86,17 @@ export const InterfaceConfigForm: React.FC = ({ noValidate onSubmit={handleOnSubmit} > -
- - - +
+
+ - - - + +
+ +
+ + +