diff --git a/src/__fixtures__/index.ts b/src/__fixtures__/index.ts
index 4110e97d..b280712a 100644
--- a/src/__fixtures__/index.ts
+++ b/src/__fixtures__/index.ts
@@ -5,5 +5,6 @@ export * from "./context_files";
export * from "./prompts";
export * from "./integrations";
export * from "./survey_questions";
+export * from "./integration";
export * from "./chat_links_response";
export * from "./chat_config_thread";
diff --git a/src/__fixtures__/integration.ts b/src/__fixtures__/integration.ts
new file mode 100644
index 00000000..4681f831
--- /dev/null
+++ b/src/__fixtures__/integration.ts
@@ -0,0 +1,110 @@
+import { Integration } from "../services/refact/integrations";
+
+export const INTEGRATION_GET_RESPONSE: Integration = {
+ project_path: "",
+ integr_name: "postgres",
+ integr_config_path: "/Users/marc/.config/refact/integrations.d/postgres.yaml",
+ integr_schema: {
+ fields: {
+ host: {
+ f_type: "string_long",
+ f_desc:
+ "Connect to this host, for example 127.0.0.1 or docker container name.",
+ f_placeholder: "marketing_db_container",
+ },
+ port: {
+ f_type: "string_short",
+ f_desc: "Which port to use.",
+ f_default: "5432",
+ },
+ user: {
+ f_type: "string_short",
+ f_placeholder: "john_doe",
+ },
+ password: {
+ f_type: "string_short",
+ f_default: "$POSTGRES_PASSWORD",
+ smartlinks: [
+ {
+ sl_label: "Open passwords.yaml",
+ sl_goto: "EDITOR:passwords.yaml",
+ },
+ ],
+ },
+ database: {
+ f_type: "string_short",
+ f_placeholder: "marketing_db",
+ },
+ psql_binary_path: {
+ f_type: "string_long",
+ f_desc:
+ "If it can't find a path to `psql` you can provide it here, leave blank if not sure.",
+ f_placeholder: "psql",
+ },
+ },
+ available: {
+ on_your_laptop_possible: true,
+ when_isolated_possible: true,
+ },
+ smartlinks: [
+ {
+ sl_label: "Test",
+ sl_chat: [
+ {
+ role: "user",
+ content:
+ "🔧 The postgres tool should be visible now. To test the tool, list the tables available, briefly desctibe the tables and express\nsatisfaction and relief if it works, and change nothing. If it doesn't work or the tool isn't available, go through the usual plan in the system prompt.\nThe current config file is %CURRENT_CONFIG%.\n",
+ },
+ ],
+ },
+ ],
+ docker: {
+ filter_label: "",
+ filter_image: "postgres",
+ new_container_default: {
+ image: "postgres:13",
+ environment: {
+ POSTGRES_DB: "marketing_db",
+ POSTGRES_USER: "john_doe",
+ POSTGRES_PASSWORD: "$POSTGRES_PASSWORD",
+ },
+ },
+ smartlinks: [
+ {
+ sl_label: "Add Database Container",
+ sl_chat: [
+ {
+ role: "user",
+ content:
+ "🔧 Your job is to create a postgres container, using the image and environment from new_container_default section in the current config file: %CURRENT_CONFIG%. Follow the system prompt.\n",
+ },
+ ],
+ },
+ ],
+ // "smartlinks_for_each_container": [
+ // {
+ // "sl_label": "Use for integration",
+ // "sl_chat": [
+ // {
+ // "role": "user",
+ // "content": "🔧 Your job is to modify postgres connection config in the current file to match the variables from the container, use docker tool to inspect the container if needed. Current config file: %CURRENT_CONFIG%.\n"
+ // }
+ // ]
+ // }
+ // ]
+ },
+ },
+ integr_values: {
+ psql_binary_path: "/usr/bin/psql",
+ host: "localhost",
+ port: "5432",
+ user: "postgres",
+ password: "$POSTGRES_PASSWORD",
+ database: "test_db",
+ available: {
+ on_your_laptop: true,
+ when_isolated: false,
+ },
+ },
+ error_log: [],
+};
diff --git a/src/components/AnimatedInputs/AnimatedCheckbox.tsx b/src/components/AnimatedInputs/AnimatedCheckbox.tsx
new file mode 100644
index 00000000..697a729c
--- /dev/null
+++ b/src/components/AnimatedInputs/AnimatedCheckbox.tsx
@@ -0,0 +1,6 @@
+import React from "react";
+
+export const AnimatedCheckbox: React.FC = () => {
+ // TODO: Implement AnimatedCheckbox
+ return
;
+};
diff --git a/src/components/AnimatedInputs/AnimatedInputs.module.css b/src/components/AnimatedInputs/AnimatedInputs.module.css
new file mode 100644
index 00000000..9d5f1a9d
--- /dev/null
+++ b/src/components/AnimatedInputs/AnimatedInputs.module.css
@@ -0,0 +1,30 @@
+.fade_input {
+ /* position: absolute;
+ top: 0;
+ left: 0; */
+ width: 100%;
+}
+
+.fade_input_in {
+ opacity: 1;
+ animation: 2s linear 1 normal none running fade-opacity;
+}
+
+.fade_input_out {
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: -1;
+ opacity: 0;
+ animation: 2s linear 1 reverse none running fade-opacity;
+ box-shadow: inset 0 0 0 var(--text-field-border-width) var(--green-a7);
+}
+
+@keyframes fade-opacity {
+ 0% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: 1;
+ }
+}
diff --git a/src/components/AnimatedInputs/AnimatedSwitch.tsx b/src/components/AnimatedInputs/AnimatedSwitch.tsx
new file mode 100644
index 00000000..1905fcda
--- /dev/null
+++ b/src/components/AnimatedInputs/AnimatedSwitch.tsx
@@ -0,0 +1,6 @@
+import React from "react";
+
+export const AnimatedSwitch: React.FC = () => {
+ // TODO: Implement the AnimagedSwitch component
+ return ;
+};
diff --git a/src/components/AnimatedInputs/AnimatedTextArea.stories.tsx b/src/components/AnimatedInputs/AnimatedTextArea.stories.tsx
new file mode 100644
index 00000000..a45b8328
--- /dev/null
+++ b/src/components/AnimatedInputs/AnimatedTextArea.stories.tsx
@@ -0,0 +1,29 @@
+import type { Meta, StoryObj } from "@storybook/react";
+import { AnimatedTextArea } from "./AnimatedTextArea";
+import { Theme, Container } from "@radix-ui/themes";
+
+const meta = {
+ title: "Components/AnimatedInputs/AnimatedTextArea",
+ component: AnimatedTextArea,
+ decorators: [
+ (Story) => (
+
+
+
+
+
+ ),
+ ],
+} satisfies Meta;
+
+export default meta;
+
+type Story = StoryObj;
+
+export const Default: Story = {
+ args: {
+ defaultValue: "World",
+ fadeValue: "Hello",
+ name: "test",
+ },
+};
diff --git a/src/components/AnimatedInputs/AnimatedTextArea.tsx b/src/components/AnimatedInputs/AnimatedTextArea.tsx
new file mode 100644
index 00000000..e25d0f61
--- /dev/null
+++ b/src/components/AnimatedInputs/AnimatedTextArea.tsx
@@ -0,0 +1,64 @@
+import React from "react";
+import { Box, TextArea, TextAreaProps } from "@radix-ui/themes";
+import classNames from "classnames";
+import styles from "./AnimatedInputs.module.css";
+
+export type AnimatedTextAreaProps = TextAreaProps & {
+ fadeValue?: TextAreaProps["value"];
+};
+
+function getFadeProps(
+ props: AnimatedTextAreaProps,
+): Omit {
+ const {
+ onChange: _onChange,
+ value: _value,
+ // defaultValue,
+ name: _name,
+ fadeValue,
+ ...rest
+ } = props;
+
+ return { ...rest, defaultValue: fadeValue };
+}
+
+function getInputProps(
+ props: AnimatedTextAreaProps,
+): Omit {
+ const { fadeValue: _fadeValue, ...rest } = props;
+ return rest;
+}
+
+export const AnimatedTextArea: React.FC = (props) => {
+ const fadeProps = getFadeProps(props);
+ const inputProps = getInputProps(props);
+
+ const shouldFade = props.fadeValue ?? false;
+
+ if (!shouldFade) {
+ return ;
+ }
+
+ return (
+
+
+
+
+
+ );
+};
diff --git a/src/components/AnimatedInputs/AnimatedTextField.tsx b/src/components/AnimatedInputs/AnimatedTextField.tsx
new file mode 100644
index 00000000..0a54deaf
--- /dev/null
+++ b/src/components/AnimatedInputs/AnimatedTextField.tsx
@@ -0,0 +1,63 @@
+import React from "react";
+import { Box, TextField } from "@radix-ui/themes";
+import classNames from "classnames";
+import styles from "./AnimatedInputs.module.css";
+
+export type AnimatedTextFieldProps = TextField.RootProps & {
+ fadeValue?: TextField.RootProps["value"];
+};
+
+function getFadeProps(
+ props: AnimatedTextFieldProps,
+): Omit {
+ const {
+ onChange: _onChange,
+ value: _value,
+ // defaultValue,
+ name: _name,
+ fadeValue,
+ ...rest
+ } = props;
+
+ return { ...rest, defaultValue: fadeValue };
+}
+
+function getInputProps(
+ props: AnimatedTextFieldProps,
+): Omit {
+ const { fadeValue: _fadeValue, ...rest } = props;
+ return rest;
+}
+
+export const AnimatedTextField: React.FC = (props) => {
+ const fadeProps = getFadeProps(props);
+ const inputProps = getInputProps(props);
+
+ const shouldFade = props.fadeValue ?? false;
+
+ if (!shouldFade) {
+ return ;
+ }
+
+ return (
+
+
+
+
+ );
+};
diff --git a/src/components/AnimatedInputs/AnimatedTextFiled.stories.tsx b/src/components/AnimatedInputs/AnimatedTextFiled.stories.tsx
new file mode 100644
index 00000000..41b9c7d6
--- /dev/null
+++ b/src/components/AnimatedInputs/AnimatedTextFiled.stories.tsx
@@ -0,0 +1,29 @@
+import type { Meta, StoryObj } from "@storybook/react";
+import { AnimatedTextField } from "./AnimatedTextField";
+import { Theme, Container } from "@radix-ui/themes";
+
+const meta = {
+ title: "Components/AnimatedInputs/AnimatedTextField",
+ component: AnimatedTextField,
+ decorators: [
+ (Story) => (
+
+
+
+
+
+ ),
+ ],
+} satisfies Meta;
+
+export default meta;
+
+type Story = StoryObj;
+
+export const Default: Story = {
+ args: {
+ defaultValue: "World",
+ fadeValue: "Hello",
+ name: "test",
+ },
+};
diff --git a/src/components/AnimatedInputs/index.ts b/src/components/AnimatedInputs/index.ts
new file mode 100644
index 00000000..1749332b
--- /dev/null
+++ b/src/components/AnimatedInputs/index.ts
@@ -0,0 +1,2 @@
+export * from "./AnimatedTextField";
+export * from "./AnimatedTextArea";
diff --git a/src/components/IntegrationsView/IntegrationForm/IntegrationForm.stories.tsx b/src/components/IntegrationsView/IntegrationForm/IntegrationForm.stories.tsx
new file mode 100644
index 00000000..8cdce5bc
--- /dev/null
+++ b/src/components/IntegrationsView/IntegrationForm/IntegrationForm.stories.tsx
@@ -0,0 +1,86 @@
+import type { Meta, StoryObj } from "@storybook/react";
+import { IntegrationForm, IntegrationFormProps } from "./IntegrationForm";
+import { Container } from "@radix-ui/themes";
+import { fn } from "@storybook/test";
+import { Provider } from "react-redux";
+import { setUpStore } from "../../../app/store";
+import { Theme } from "../../Theme";
+import { AbortControllerProvider } from "../../../contexts/AbortControllers";
+import { http, HttpResponse, type HttpHandler } from "msw";
+import React from "react";
+import {
+ INTEGRATION_GET_RESPONSE,
+ INTEGRATIONS_RESPONSE,
+} from "../../../__fixtures__";
+
+const Template: React.FC = (props) => {
+ const store = setUpStore({
+ integrations: {
+ cachedForms: {
+ [INTEGRATION_GET_RESPONSE.integr_config_path]:
+ INTEGRATIONS_RESPONSE.integr_values,
+ },
+ },
+ });
+ return (
+
+
+
+
+
+
+
+
+
+ );
+};
+
+const meta: Meta = {
+ title: "Integrations/IntegrationForm",
+ component: Template,
+ args: {
+ integrationPath: "/path/to/integration",
+ isApplying: false,
+ isDisabled: false,
+ availabilityValues: {},
+ // onCancel: fn(),
+ handleSubmit: fn(),
+ handleChange: fn(),
+ onSchema: fn(),
+ onValues: fn(),
+ setAvailabilityValues: fn(),
+ },
+ parameters: {
+ msw: {
+ handlers: [
+ http.get("http://127.0.0.1:8001/v1/ping", () => {
+ return HttpResponse.text("pong");
+ }),
+
+ http.post("http://127.0.0.1:8001/v1/integration-get", () => {
+ return HttpResponse.json(INTEGRATION_GET_RESPONSE);
+ }),
+ // http.get("https://www.smallcloud.ai/v1/login", () => {
+ // return HttpResponse.json({
+ // retcode: "OK",
+ // account: "party@refact.ai",
+ // inference_url: "https://www.smallcloud.ai/v1",
+ // inference: "PRO",
+ // metering_balance: -100000,
+ // questionnaire: false,
+ // });
+ // }),
+ ],
+ },
+ },
+} satisfies Meta<
+ typeof IntegrationForm & { parameters: { msw: { handlers: HttpHandler[] } } }
+>;
+
+export default meta;
+
+type Story = StoryObj;
+
+export const Primary: Story = {
+ args: {},
+};
diff --git a/src/components/IntegrationsView/IntegrationForm/IntegrationForm.tsx b/src/components/IntegrationsView/IntegrationForm/IntegrationForm.tsx
index d008f7b7..407c6378 100644
--- a/src/components/IntegrationsView/IntegrationForm/IntegrationForm.tsx
+++ b/src/components/IntegrationsView/IntegrationForm/IntegrationForm.tsx
@@ -31,7 +31,7 @@ function areAllFieldsBoolean(json: unknown): json is Record {
);
}
-type IntegrationFormProps = {
+export type IntegrationFormProps = {
integrationPath: string;
isApplying: boolean;
isDisabled: boolean;
diff --git a/src/features/Integrations/integrationsSlice.tsx b/src/features/Integrations/integrationsSlice.tsx
index 1df492d3..29492e30 100644
--- a/src/features/Integrations/integrationsSlice.tsx
+++ b/src/features/Integrations/integrationsSlice.tsx
@@ -41,7 +41,8 @@ export const integrationsSlice = createSlice({
},
},
selectors: {
- maybeSelectIntegrationFromCache: (state, integration: Integration) => {
+ maybeSelectIntegrationFromCache: (state, integration?: Integration) => {
+ if (!integration) return null;
if (!(integration.integr_config_path in state.cachedForms)) return null;
return state.cachedForms[integration.integr_config_path];
},
diff --git a/src/hooks/useGetIntegrationDataByPathQuery.ts b/src/hooks/useGetIntegrationDataByPathQuery.ts
index 3312260c..a8347dd1 100644
--- a/src/hooks/useGetIntegrationDataByPathQuery.ts
+++ b/src/hooks/useGetIntegrationDataByPathQuery.ts
@@ -1,8 +1,12 @@
import { integrationsApi } from "../services/refact/integrations";
import { useGetPing } from "./useGetPing";
-import { addToCacheOnMiss } from "../features/Integrations/integrationsSlice";
+import {
+ addToCacheOnMiss,
+ maybeSelectIntegrationFromCache,
+} from "../features/Integrations/integrationsSlice";
import { useAppDispatch } from "./useAppDispatch";
import { useEffect } from "react";
+import { useAppSelector } from "./useAppSelector";
export const useGetIntegrationDataByPathQuery = (integrationPath: string) => {
const ping = useGetPing();
@@ -22,9 +26,14 @@ export const useGetIntegrationDataByPathQuery = (integrationPath: string) => {
}
}, [dispatch, integration.data]);
+ const cachedValues = useAppSelector((state) =>
+ maybeSelectIntegrationFromCache(state, integration.data),
+ );
+
// TBD: add other methods for checking values here or else where?
return {
integration,
+ cachedValues,
};
};