Skip to content

Commit ec48346

Browse files
Roman SnapkoCopilotalexandrudanpop
authored
Update connections page with new UI (#1745)
<!-- Ensure the title clearly reflects what was changed. Provide a clear and concise description of the changes made. The PR should only contain the changes related to the issue, and no other unrelated changes. --> Part of OPS-3164. <img width="1920" height="993" alt="image" src="https://github.com/user-attachments/assets/5378beb5-72ca-498c-9266-7e18fd5a71df" /> Available on UX <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * AI configuration status indicator displaying connection enable/disable state * **Refactor** * Simplified AI settings form with automatic save-on-change * Redesigned AI settings page with improved layout and organization * Integrated AWS and MCP tool configuration into unified settings interface <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Alexandru-Dan Pop <alexandrudanpop@gmail.com>
1 parent 66e8f64 commit ec48346

4 files changed

Lines changed: 188 additions & 410 deletions

File tree

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { t } from 'i18next';
2+
import { CircleCheck, CircleMinus } from 'lucide-react';
3+
4+
type AiConfigIndicatorProps = {
5+
enabled: boolean;
6+
};
7+
8+
const AiConfigIndicator = ({ enabled }: AiConfigIndicatorProps) => {
9+
return (
10+
<div className="flex items-center gap-[6px] text-primary-900 font-medium">
11+
{enabled ? (
12+
<>
13+
<CircleCheck className="text-success-300" size={24} />
14+
<span>{t('OpenOps AI is enabled')}</span>
15+
</>
16+
) : (
17+
<>
18+
<CircleMinus className="text-muted-foreground" size={24} />
19+
<span>
20+
{t('OpenOps AI is disabled - Configure a connection to enable it')}
21+
</span>
22+
</>
23+
)}
24+
</div>
25+
);
26+
};
27+
28+
export { AiConfigIndicator };

packages/react-ui/src/app/features/ai/ai-settings-form.tsx

Lines changed: 46 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -1,157 +1,91 @@
1-
import {
2-
aiFormSchemaResolver,
3-
AiSettingsFormSchema,
4-
} from '@/app/features/ai/lib/ai-form-utils';
51
import { ConnectionSelect } from '@/app/features/builder/step-settings/block-settings/connection-select';
62
import {
73
BlockMetadataModel,
84
BlockMetadataModelSummary,
95
} 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';
197
import equal from 'fast-deep-equal';
20-
import { t } from 'i18next';
21-
import { CircleCheck } from 'lucide-react';
8+
import debounce from 'lodash.debounce';
229
import React, { useEffect, useMemo, useState } from 'react';
2310
import { useForm } from 'react-hook-form';
2411

2512
type AiSettingsFormProps = {
2613
block: BlockMetadataModelSummary | BlockMetadataModel;
27-
savedSettings?: AiConfig;
28-
onSave: (settings: AiSettingsFormSchema) => void;
29-
isSaving: boolean;
14+
providerKey: string;
15+
initialConnection?: string;
16+
onSave: (connectionName: string) => void;
17+
displayName?: string;
18+
disabled?: boolean;
3019
};
3120

32-
export const EMPTY_FORM_VALUE: AiSettingsFormSchema = {
33-
enabled: false,
21+
type LocalForm = { connection: string };
22+
23+
export const EMPTY_FORM_VALUE: LocalForm = {
3424
connection: '',
3525
};
3626

3727
const AiSettingsForm = ({
3828
block,
39-
savedSettings,
29+
providerKey,
30+
initialConnection,
4031
onSave,
41-
isSaving,
32+
displayName,
33+
disabled = false,
4234
}: AiSettingsFormProps) => {
43-
const form = useForm<AiSettingsFormSchema>({
44-
resolver: aiFormSchemaResolver,
35+
const form = useForm<LocalForm>({
4536
defaultValues: EMPTY_FORM_VALUE,
4637
mode: 'onChange',
4738
});
4839
const [initialFormValue, setInitialFormValue] =
49-
useState<AiSettingsFormSchema>(EMPTY_FORM_VALUE);
40+
useState<LocalForm>(EMPTY_FORM_VALUE);
5041

5142
useEffect(() => {
52-
const formValue: AiSettingsFormSchema = {
53-
enabled: savedSettings?.enabled ?? false,
54-
connection: savedSettings?.connection ?? '',
43+
const formValue: LocalForm = {
44+
connection: initialConnection ?? '',
5545
};
5646
setInitialFormValue(formValue);
5747
form.reset(formValue);
58-
}, [savedSettings, form]);
48+
}, [initialConnection, form]);
5949

6050
const currentFormValue = form.watch();
51+
const watchedConnection = form.watch('connection');
6152

6253
const isFormUnchanged = useMemo(() => {
6354
return equal(currentFormValue, initialFormValue);
6455
}, [currentFormValue, initialFormValue]);
6556

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-
};
57+
const debouncedSave = useMemo(
58+
() =>
59+
debounce((connection: string, unchanged: boolean) => {
60+
if (!unchanged) {
61+
onSave(connection);
62+
setInitialFormValue({ connection });
63+
}
64+
}, 300),
65+
[onSave],
66+
);
7867

79-
const onSaveClick = () => {
80-
const formValue = form.getValues();
81-
onSave({
82-
...savedSettings,
83-
...formValue,
84-
});
85-
};
68+
useEffect(() => {
69+
debouncedSave(watchedConnection ?? '', isFormUnchanged);
70+
}, [debouncedSave, watchedConnection, isFormUnchanged]);
8671

87-
const descriptionText = t(
88-
'Enables OpenOps Assistant and other AI-powered features such as the CLI command generation.',
89-
);
72+
useEffect(() => {
73+
return () => {
74+
debouncedSave.cancel();
75+
};
76+
}, [debouncedSave]);
9077

9178
return (
9279
<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-
)}
80+
<form>
81+
<ConnectionSelect
82+
disabled={disabled}
83+
allowDynamicValues={false}
84+
block={block}
85+
providerKey={providerKey}
86+
name={'connection'}
87+
displayName={displayName}
11788
/>
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>
15589
</form>
15690
</Form>
15791
);

0 commit comments

Comments
 (0)