|
1 | | -import { |
2 | | - aiFormSchemaResolver, |
3 | | - AiSettingsFormSchema, |
4 | | -} from '@/app/features/ai/lib/ai-form-utils'; |
5 | 1 | import { ConnectionSelect } from '@/app/features/builder/step-settings/block-settings/connection-select'; |
6 | 2 | import { |
7 | 3 | BlockMetadataModel, |
8 | 4 | BlockMetadataModelSummary, |
9 | 5 | } from '@openops/blocks-framework'; |
10 | | -import { |
11 | | - Button, |
12 | | - Form, |
13 | | - FormField, |
14 | | - FormItem, |
15 | | - Label, |
16 | | - Switch, |
17 | | -} from '@openops/components/ui'; |
18 | | -import { AiConfig } from '@openops/shared'; |
| 6 | +import { Form } from '@openops/components/ui'; |
| 7 | +import { removeConnectionBrackets } from '@openops/shared'; |
19 | 8 | import equal from 'fast-deep-equal'; |
20 | | -import { t } from 'i18next'; |
21 | | -import { CircleCheck } from 'lucide-react'; |
22 | | -import React, { useEffect, useMemo, useState } from 'react'; |
| 9 | +import debounce from 'lodash.debounce'; |
| 10 | +import React, { useEffect, useMemo, useRef, useState } from 'react'; |
23 | 11 | import { useForm } from 'react-hook-form'; |
24 | 12 |
|
25 | 13 | type AiSettingsFormProps = { |
26 | 14 | block: BlockMetadataModelSummary | BlockMetadataModel; |
27 | | - savedSettings?: AiConfig; |
28 | | - onSave: (settings: AiSettingsFormSchema) => void; |
29 | | - isSaving: boolean; |
| 15 | + providerKey: string; |
| 16 | + initialConnection?: string; |
| 17 | + onSave: (connectionName: string) => void; |
| 18 | + displayName?: string; |
| 19 | + disabled?: boolean; |
30 | 20 | }; |
31 | 21 |
|
32 | | -export const EMPTY_FORM_VALUE: AiSettingsFormSchema = { |
33 | | - enabled: false, |
| 22 | +type LocalForm = { connection: string }; |
| 23 | + |
| 24 | +export const EMPTY_FORM_VALUE: LocalForm = { |
34 | 25 | connection: '', |
35 | 26 | }; |
36 | 27 |
|
37 | 28 | const AiSettingsForm = ({ |
38 | 29 | block, |
39 | | - savedSettings, |
| 30 | + providerKey, |
| 31 | + initialConnection, |
40 | 32 | onSave, |
41 | | - isSaving, |
| 33 | + displayName, |
| 34 | + disabled = false, |
42 | 35 | }: AiSettingsFormProps) => { |
43 | | - const form = useForm<AiSettingsFormSchema>({ |
44 | | - resolver: aiFormSchemaResolver, |
| 36 | + const form = useForm<LocalForm>({ |
45 | 37 | defaultValues: EMPTY_FORM_VALUE, |
46 | 38 | mode: 'onChange', |
47 | 39 | }); |
48 | 40 | const [initialFormValue, setInitialFormValue] = |
49 | | - useState<AiSettingsFormSchema>(EMPTY_FORM_VALUE); |
| 41 | + useState<LocalForm>(EMPTY_FORM_VALUE); |
50 | 42 |
|
51 | 43 | useEffect(() => { |
52 | | - const formValue: AiSettingsFormSchema = { |
53 | | - enabled: savedSettings?.enabled ?? false, |
54 | | - connection: savedSettings?.connection ?? '', |
| 44 | + const formValue: LocalForm = { |
| 45 | + connection: initialConnection ?? '', |
55 | 46 | }; |
56 | 47 | setInitialFormValue(formValue); |
57 | 48 | form.reset(formValue); |
58 | | - }, [savedSettings, form]); |
| 49 | + }, [initialConnection, form]); |
59 | 50 |
|
60 | 51 | const currentFormValue = form.watch(); |
| 52 | + const watchedConnection = form.watch('connection'); |
61 | 53 |
|
62 | 54 | const isFormUnchanged = useMemo(() => { |
63 | 55 | return equal(currentFormValue, initialFormValue); |
64 | 56 | }, [currentFormValue, initialFormValue]); |
65 | 57 |
|
66 | | - const isValidConnection = useMemo(() => { |
67 | | - const omit = (obj?: AiSettingsFormSchema) => { |
68 | | - const { enabled, ...rest } = obj ?? EMPTY_FORM_VALUE; |
69 | | - return rest; |
70 | | - }; |
71 | | - |
72 | | - return equal(omit(currentFormValue), omit(initialFormValue)); |
73 | | - }, [currentFormValue, initialFormValue]); |
74 | | - |
75 | | - const resetForm = () => { |
76 | | - form.reset(); |
77 | | - }; |
| 58 | + const debouncedSave = useMemo( |
| 59 | + () => |
| 60 | + debounce((connection: string, unchanged: boolean) => { |
| 61 | + if (!unchanged) { |
| 62 | + onSave(connection); |
| 63 | + setInitialFormValue({ connection }); |
| 64 | + } |
| 65 | + }, 300), |
| 66 | + [onSave], |
| 67 | + ); |
78 | 68 |
|
79 | | - const onSaveClick = () => { |
80 | | - const formValue = form.getValues(); |
81 | | - onSave({ |
82 | | - ...savedSettings, |
83 | | - ...formValue, |
84 | | - }); |
85 | | - }; |
| 69 | + useEffect(() => { |
| 70 | + debouncedSave(watchedConnection ?? '', isFormUnchanged); |
| 71 | + }, [debouncedSave, watchedConnection, isFormUnchanged]); |
86 | 72 |
|
87 | | - const descriptionText = t( |
88 | | - 'Enables OpenOps Assistant and other AI-powered features such as the CLI command generation.', |
89 | | - ); |
| 73 | + useEffect(() => { |
| 74 | + return () => { |
| 75 | + debouncedSave.cancel(); |
| 76 | + }; |
| 77 | + }, [debouncedSave]); |
90 | 78 |
|
91 | 79 | return ( |
92 | 80 | <Form {...form}> |
93 | | - <form className="flex-1 flex flex-col gap-4"> |
94 | | - <FormField |
95 | | - control={form.control} |
96 | | - name="enabled" |
97 | | - render={({ field }) => ( |
98 | | - <FormItem className="flex flex-col"> |
99 | | - <div className="flex items-center gap-[6px]"> |
100 | | - <Switch |
101 | | - id="enabled" |
102 | | - checked={field.value} |
103 | | - onCheckedChange={field.onChange} |
104 | | - /> |
105 | | - <Label |
106 | | - className="text-lg font-bold leading-6" |
107 | | - htmlFor="enabled" |
108 | | - > |
109 | | - {t('Enable OpenOps AI')} |
110 | | - </Label> |
111 | | - </div> |
112 | | - <p className="mt-8 text-base font-normal leading-6 text-primary-900"> |
113 | | - {descriptionText} |
114 | | - </p> |
115 | | - </FormItem> |
116 | | - )} |
| 81 | + <form className="max-w-[516px]"> |
| 82 | + <ConnectionSelect |
| 83 | + disabled={disabled} |
| 84 | + allowDynamicValues={false} |
| 85 | + block={block} |
| 86 | + providerKey={providerKey} |
| 87 | + name={'connection'} |
| 88 | + displayName={displayName} |
117 | 89 | /> |
118 | | - <div className="max-w-[516px]"> |
119 | | - <ConnectionSelect |
120 | | - disabled={!currentFormValue.enabled} |
121 | | - allowDynamicValues={false} |
122 | | - block={block} |
123 | | - providerKey={'AI'} |
124 | | - name={'connection'} |
125 | | - /> |
126 | | - </div> |
127 | | - |
128 | | - <div className="flex items-center justify-between max-w-[516px]"> |
129 | | - <div className="flex gap-2"> |
130 | | - <Button |
131 | | - variant="outline" |
132 | | - type="button" |
133 | | - onClick={resetForm} |
134 | | - disabled={isSaving || isFormUnchanged} |
135 | | - > |
136 | | - {t('Cancel')} |
137 | | - </Button> |
138 | | - <Button |
139 | | - className="w-[95px]" |
140 | | - type="button" |
141 | | - disabled={!form.formState.isValid || isFormUnchanged} |
142 | | - onClick={onSaveClick} |
143 | | - loading={isSaving} |
144 | | - > |
145 | | - {t('Save')} |
146 | | - </Button> |
147 | | - </div> |
148 | | - {savedSettings?.id && isValidConnection && ( |
149 | | - <div className="flex items-center gap-2"> |
150 | | - <CircleCheck size={24} className="text-success-300" /> |
151 | | - <span>{t('Valid Connection')}</span> |
152 | | - </div> |
153 | | - )} |
154 | | - </div> |
155 | 90 | </form> |
156 | 91 | </Form> |
157 | 92 | ); |
|
0 commit comments