refactor: global variables logic (#3144)

This commit is contained in:
Lucas Oliveira 2024-08-01 17:26:29 -03:00 committed by GitHub
commit 83e48affb1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 130 additions and 258 deletions

View file

@ -1,4 +1,4 @@
import { useContext, useEffect, useState } from "react";
import { useContext, useEffect } from "react";
import { Cookies } from "react-cookie";
import { ErrorBoundary } from "react-error-boundary";
import "reactflow/dist/style.css";
@ -19,7 +19,6 @@ import {
useRefreshAccessToken,
} from "./controllers/API/queries/auth";
import { useGetHealthQuery } from "./controllers/API/queries/health";
import { useGetGlobalVariables } from "./controllers/API/queries/variables";
import { useGetVersionQuery } from "./controllers/API/queries/version";
import { setupAxiosDefaults } from "./controllers/API/utils";
import useTrackLastVisitedPath from "./hooks/use-track-last-visited-path";
@ -50,8 +49,6 @@ export default function App() {
useGetVersionQuery();
const isLoadingFolders = useFolderStore((state) => state.isLoadingFolders);
const { mutate: mutateGetGlobalVariables } = useGetGlobalVariables();
const { mutate: mutateRefresh } = useRefreshAccessToken();
const isLoginPage = location.pathname.includes("login");
@ -77,7 +74,6 @@ export default function App() {
if (user && user["access_token"]) {
user["refresh_token"] = "auto";
login(user["access_token"], "auto");
mutateGetGlobalVariables();
setUserData(user);
setAutoLogin(true);
fetchAllData();

View file

@ -1,8 +1,11 @@
import { usePostGlobalVariables } from "@/controllers/API/queries/variables";
import { useState } from "react";
import {
useGetGlobalVariables,
usePostGlobalVariables,
} from "@/controllers/API/queries/variables";
import getUnavailableFields from "@/stores/globalVariablesStore/utils/get-unavailable-fields";
import { useEffect, useState } from "react";
import BaseModal from "../../modals/baseModal";
import useAlertStore from "../../stores/alertStore";
import { useGlobalVariablesStore } from "../../stores/globalVariablesStore/globalVariables";
import { useTypesStore } from "../../stores/typesStore";
import { ResponseErrorDetailAPI } from "../../types/api";
import ForwardedIconComponent from "../genericIconComponent";
@ -28,26 +31,21 @@ export default function AddNewVariableButton({
const [open, setOpen] = useState(false);
const setErrorData = useAlertStore((state) => state.setErrorData);
const componentFields = useTypesStore((state) => state.ComponentFields);
const unavaliableFields = new Set(
Object.keys(
useGlobalVariablesStore((state) => state.unavaliableFields) ?? {},
),
);
const availableFields = () => {
const fields = Array.from(componentFields).filter(
(field) => !unavaliableFields.has(field),
);
return sortByName(fields);
};
const addGlobalVariable = useGlobalVariablesStore(
(state) => state.addGlobalVariable,
);
const { mutate: mutateAddGlobalVariable } = usePostGlobalVariables();
const { data: globalVariables } = useGetGlobalVariables();
const [availableFields, setAvailableFields] = useState<string[]>([]);
useEffect(() => {
if (globalVariables && componentFields.size > 0) {
const unavailableFields = getUnavailableFields(globalVariables);
const fields = Array.from(componentFields).filter(
(field) => !unavailableFields.hasOwnProperty(field),
);
setAvailableFields(sortByName(fields));
}
}, [globalVariables, componentFields]);
const setSuccessData = useAlertStore((state) => state.setSuccessData);
function handleSaveVariable() {
@ -65,8 +63,7 @@ export default function AddNewVariableButton({
mutateAddGlobalVariable(data, {
onSuccess: (res) => {
const { name, id, type } = res.data;
addGlobalVariable(name, id, type, fields);
const { name } = res;
setKey("");
setValue("");
setType("");
@ -156,7 +153,7 @@ export default function AddNewVariableButton({
<InputComponent
setSelectedOptions={(value) => setFields(value)}
selectedOptions={fields}
options={availableFields()}
options={availableFields}
password={false}
placeholder="Choose a field for the variable..."
id={"apply-to-fields"}

View file

@ -1,8 +1,10 @@
import { useDeleteGlobalVariables } from "@/controllers/API/queries/variables";
import {
useDeleteGlobalVariables,
useGetGlobalVariables,
} from "@/controllers/API/queries/variables";
import { useEffect } from "react";
import DeleteConfirmationModal from "../../modals/deleteConfirmationModal";
import useAlertStore from "../../stores/alertStore";
import { useGlobalVariablesStore } from "../../stores/globalVariablesStore/globalVariables";
import { InputGlobalComponentType } from "../../types/components";
import { cn } from "../../utils/utils";
import AddNewVariableButton from "../addNewVariableButtonComponent/addNewVariableButton";
@ -17,33 +19,29 @@ export default function InputGlobalComponent({
data,
editNode = false,
}: InputGlobalComponentType): JSX.Element {
const globalVariablesEntries = useGlobalVariablesStore(
(state) => state.globalVariablesEntries,
);
const getVariableId = useGlobalVariablesStore((state) => state.getVariableId);
const removeGlobalVariable = useGlobalVariablesStore(
(state) => state.removeGlobalVariable,
);
const setErrorData = useAlertStore((state) => state.setErrorData);
const { data: globalVariables } = useGetGlobalVariables();
const { mutate: mutateDeleteGlobalVariable } = useDeleteGlobalVariables();
useEffect(() => {
if (data && globalVariablesEntries)
if (data.load_from_db && !globalVariablesEntries.includes(data.value)) {
if (data && globalVariables)
if (
data.load_from_db &&
!globalVariables.find((variable) => variable.name === data.value)
) {
onChange("", false, true);
}
}, [globalVariablesEntries]);
}, [globalVariables]);
async function handleDelete(key: string) {
const id = getVariableId(key);
if (!globalVariables) return;
const id = globalVariables.find((variable) => variable.name === key)?.id;
if (id !== undefined) {
mutateDeleteGlobalVariable(
{ id },
{
onSuccess: () => {
removeGlobalVariable(key);
if (data?.value === key && data?.load_from_db) {
onChange("", false);
}
@ -70,7 +68,7 @@ export default function InputGlobalComponent({
disabled={disabled}
password={data.password ?? false}
value={data.value ?? ""}
options={globalVariablesEntries}
options={globalVariables?.map((variable) => variable.name) ?? []}
optionsPlaceholder={"Global Variables"}
optionsIcon="Globe"
optionsButton={
@ -113,8 +111,10 @@ export default function InputGlobalComponent({
)}
selectedOption={
data?.load_from_db &&
globalVariablesEntries &&
globalVariablesEntries.includes(data?.value ?? "")
globalVariables &&
globalVariables
?.map((variable) => variable.name)
.includes(data?.value ?? "")
? data?.value
: ""
}

View file

@ -1,4 +1,3 @@
import { ColDef, ColGroupDef } from "ag-grid-community";
import { AxiosRequestConfig, AxiosResponse } from "axios";
import { Edge, Node, ReactFlowJsonObject } from "reactflow";
import { BASE_URL_API, MAX_BATCH_SIZE } from "../../constants/constants";
@ -8,21 +7,14 @@ import {
APITemplateType,
Component,
CustomComponentRequest,
LoginType,
ProfilePicturesTypeAPI,
Users,
VertexBuildTypeAPI,
VerticesOrderTypeAPI,
changeUser,
resetPasswordType,
sendAllProps,
} from "../../types/api/index";
import { UserInputType } from "../../types/components";
import { FlowStyleType, FlowType } from "../../types/flow";
import { Message } from "../../types/messages";
import { StoreComponentResponse } from "../../types/store";
import { FlowPoolType } from "../../types/zustand/flow";
import { extractColumnsFromRows } from "../../utils/utils";
import {
APIClassType,
BuildStatusTypeAPI,
@ -766,23 +758,6 @@ export async function requestLogout() {
}
}
export async function updateGlobalVariable(
name: string,
value: string,
id: string,
) {
try {
const response = api.patch(`${BASE_URL_API}variables/${id}`, {
name,
value,
});
return response;
} catch (error) {
throw error;
}
}
export async function getVerticesOrder(
flowId: string,
startNodeId?: string | null,

View file

@ -12,7 +12,7 @@ export const useDeleteGlobalVariables: useMutationFunctionType<
undefined,
DeleteGlobalVariablesParams
> = (options?) => {
const { mutate } = UseRequestProcessor();
const { mutate, queryClient } = UseRequestProcessor();
const deleteGlobalVariables = async ({
id,
@ -25,7 +25,12 @@ export const useDeleteGlobalVariables: useMutationFunctionType<
DeleteGlobalVariablesParams,
any,
DeleteGlobalVariablesParams
> = mutate(["useDeleteGlobalVariables"], deleteGlobalVariables, options);
> = mutate(["useDeleteGlobalVariables"], deleteGlobalVariables, {
onSettled: () => {
queryClient.refetchQueries({ queryKey: ["useGetGlobalVariables"] });
},
...options,
});
return mutation;
};

View file

@ -1,54 +1,37 @@
import { useGlobalVariablesStore } from "@/stores/globalVariablesStore/globalVariables";
import { useMutationFunctionType } from "@/types/api";
import { UseMutationResult } from "@tanstack/react-query";
import getUnavailableFields from "@/stores/globalVariablesStore/utils/get-unavailable-fields";
import { useQueryFunctionType } from "@/types/api";
import { GlobalVariable } from "@/types/global_variables";
import { UseQueryResult } from "@tanstack/react-query";
import { api } from "../../api";
import { getURL } from "../../helpers/constants";
import { UseRequestProcessor } from "../../services/request-processor";
type GlobalVariable = {
id: string;
type: string;
default_fields: string[];
name: string;
};
export const useGetGlobalVariables: useQueryFunctionType<
undefined,
GlobalVariable[]
> = (options?) => {
const { query } = UseRequestProcessor();
export const useGetGlobalVariables: useMutationFunctionType<undefined, void> = (
options?,
) => {
const { mutate } = UseRequestProcessor();
const setGlobalVariables = useGlobalVariablesStore(
(state) => state.setGlobalVariables,
const setGlobalVariablesEntries = useGlobalVariablesStore(
(state) => state.setGlobalVariablesEntries,
);
const setUnavailableFields = useGlobalVariablesStore(
(state) => state.setUnavailableFields,
);
const getGlobalVariables = async (): Promise<[GlobalVariable]> => {
const getGlobalVariablesFn = async (): Promise<GlobalVariable[]> => {
const res = await api.get(`${getURL("VARIABLES")}/`);
setGlobalVariablesEntries(res.data.map((entry) => entry.name));
setUnavailableFields(getUnavailableFields(res.data));
return res.data;
};
const getGlobalVariablesFn = async (): Promise<{
[key: string]: GlobalVariable;
}> => {
const data = await getGlobalVariables();
const globalVariables = {};
data?.forEach((element) => {
globalVariables[element.name] = {
id: element.id,
type: element.type,
default_fields: element.default_fields,
};
});
setGlobalVariables(globalVariables);
return globalVariables;
};
const mutation: UseMutationResult<any, any, any> = mutate(
const queryResult: UseQueryResult<GlobalVariable[], any> = query(
["useGetGlobalVariables"],
getGlobalVariablesFn,
options,
);
return mutation;
return queryResult;
};

View file

@ -1,4 +1,4 @@
import { changeUser, useMutationFunctionType } from "@/types/api";
import { useMutationFunctionType } from "@/types/api";
import { UseMutationResult } from "@tanstack/react-query";
import { api } from "../../api";
import { getURL } from "../../helpers/constants";
@ -14,7 +14,7 @@ export const usePatchGlobalVariables: useMutationFunctionType<
undefined,
PatchGlobalVariablesParams
> = (options?) => {
const { mutate } = UseRequestProcessor();
const { mutate, queryClient } = UseRequestProcessor();
async function patchGlobalVariables({
name,
@ -32,7 +32,12 @@ export const usePatchGlobalVariables: useMutationFunctionType<
PatchGlobalVariablesParams,
any,
PatchGlobalVariablesParams
> = mutate(["usePatchGlobalVariables"], patchGlobalVariables, options);
> = mutate(["usePatchGlobalVariables"], patchGlobalVariables, {
onSettled: () => {
queryClient.refetchQueries({ queryKey: ["useGetGlobalVariables"] });
},
...options,
});
return mutation;
};

View file

@ -16,7 +16,7 @@ export const usePostGlobalVariables: useMutationFunctionType<
undefined,
PostGlobalVariablesParams
> = (options?) => {
const { mutate } = UseRequestProcessor();
const { mutate, queryClient } = UseRequestProcessor();
const postGlobalVariablesFunction = async ({
name,
@ -24,17 +24,22 @@ export const usePostGlobalVariables: useMutationFunctionType<
type,
default_fields = [],
}): Promise<AxiosResponse<{ name: string; id: string; type: string }>> => {
const res = await api.post(`${getURL("VARIABLES")}`, {
const res = await api.post(`${getURL("VARIABLES")}/`, {
name,
value,
type,
default_fields: default_fields,
});
return res;
return res.data;
};
const mutation: UseMutationResult<any, any, PostGlobalVariablesParams> =
mutate(["usePostGlobalVariables"], postGlobalVariablesFunction, options);
mutate(["usePostGlobalVariables"], postGlobalVariablesFunction, {
onSettled: () => {
queryClient.refetchQueries({ queryKey: ["useGetGlobalVariables"] });
},
...options,
});
return mutation;
};

View file

@ -1,4 +1,5 @@
import {
QueryClient,
useMutation,
UseMutationOptions,
useQuery,
@ -10,6 +11,7 @@ import { MutationFunctionType, QueryFunctionType } from "../../../types/api";
export function UseRequestProcessor(): {
query: QueryFunctionType;
mutate: MutationFunctionType;
queryClient: QueryClient;
} {
const queryClient = useQueryClient();
@ -41,5 +43,5 @@ export function UseRequestProcessor(): {
});
}
return { query, mutate };
return { query, mutate, queryClient };
}

View file

@ -1,5 +1,4 @@
import { useLoginUser } from "@/controllers/API/queries/auth";
import { useGetGlobalVariables } from "@/controllers/API/queries/variables";
import useFlowsManagerStore from "@/stores/flowsManagerStore";
import { useFolderStore } from "@/stores/foldersStore";
import { useContext, useState } from "react";
@ -24,8 +23,6 @@ export default function LoginAdminPage() {
const { login } = useContext(AuthContext);
const setLoading = useAlertStore((state) => state.setLoading);
const { mutate: mutateGetGlobalVariables } = useGetGlobalVariables();
const setAllFlows = useFlowsManagerStore((state) => state.setAllFlows);
const setSelectedFolder = useFolderStore((state) => state.setSelectedFolder);
@ -51,11 +48,7 @@ export default function LoginAdminPage() {
setSelectedFolder(null);
setLoading(true);
login(res.access_token, "login");
mutateGetGlobalVariables();
navigate("/admin/");
},
onError: (error) => {
setErrorData({

View file

@ -1,3 +1,4 @@
import { useGetGlobalVariables } from "@/controllers/API/queries/variables";
import { useEffect } from "react";
import { useNavigate, useParams } from "react-router-dom";
import FlowToolbar from "../../components/chatComponent";
@ -17,6 +18,7 @@ export default function FlowPage({ view }: { view?: boolean }): JSX.Element {
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
const { id } = useParams();
const navigate = useNavigate();
useGetGlobalVariables();
const flows = useFlowsManagerStore((state) => state.flows);

View file

@ -50,7 +50,6 @@ export default function LoginPage(): JSX.Element {
setLoading(true);
login(data.access_token, "login");
navigate("/admin/");
},
onError: (error) => {
setErrorData({

View file

@ -1,30 +1,21 @@
import IconComponent from "../../../../components/genericIconComponent";
import { Button } from "../../../../components/ui/button";
import { useDeleteGlobalVariables } from "@/controllers/API/queries/variables";
import {
useDeleteGlobalVariables,
useGetGlobalVariables,
} from "@/controllers/API/queries/variables";
import { ColDef, ColGroupDef, SelectionChangedEvent } from "ag-grid-community";
import { useEffect, useState } from "react";
import { useState } from "react";
import AddNewVariableButton from "../../../../components/addNewVariableButtonComponent/addNewVariableButton";
import Dropdown from "../../../../components/dropdownComponent";
import ForwardedIconComponent from "../../../../components/genericIconComponent";
import TableComponent from "../../../../components/tableComponent";
import { Badge } from "../../../../components/ui/badge";
import useAlertStore from "../../../../stores/alertStore";
import { useGlobalVariablesStore } from "../../../../stores/globalVariablesStore/globalVariables";
export default function GlobalVariablesPage() {
const globalVariablesEntries = useGlobalVariablesStore(
(state) => state.globalVariablesEntries,
);
const removeGlobalVariable = useGlobalVariablesStore(
(state) => state.removeGlobalVariable,
);
const globalVariables = useGlobalVariablesStore(
(state) => state.globalVariables,
);
const setErrorData = useAlertStore((state) => state.setErrorData);
const getVariableId = useGlobalVariablesStore((state) => state.getVariableId);
const BadgeRenderer = (props) => {
return props.value !== "" ? (
<div>
@ -37,35 +28,6 @@ export default function GlobalVariablesPage() {
);
};
const [rowData, setRowData] = useState<
{
type: string | undefined;
id: string;
name: string;
default_fields: string | undefined;
}[]
>([]);
useEffect(() => {
const rows: Array<{
type: string | undefined;
id: string;
name: string;
default_fields: string | undefined;
}> = [];
if (globalVariablesEntries === undefined) return;
globalVariablesEntries.forEach((entrie) => {
const globalVariableObj = globalVariables[entrie];
rows.push({
type: globalVariableObj.type,
id: globalVariableObj.id,
default_fields: (globalVariableObj.default_fields ?? []).join(", "),
name: entrie,
});
});
setRowData(rows);
}, [globalVariables]);
const DropdownEditor = ({ options, value, onValueChange }) => {
return (
<Dropdown options={options} value={value} onSelect={onValueChange}>
@ -108,17 +70,15 @@ export default function GlobalVariablesPage() {
const [selectedRows, setSelectedRows] = useState<string[]>([]);
const { data: globalVariables } = useGetGlobalVariables();
const { mutate: mutateDeleteGlobalVariable } = useDeleteGlobalVariables();
async function removeVariables() {
selectedRows.map(async (row) => {
const id = getVariableId(row);
const id = globalVariables?.find((variable) => variable.name === row)?.id;
mutateDeleteGlobalVariable(
{ id },
{
onSuccess: () => {
removeGlobalVariable(row);
},
onError: () => {
setErrorData({
title: `Error deleting variable`,
@ -166,7 +126,7 @@ export default function GlobalVariablesPage() {
suppressRowClickSelection={true}
pagination={true}
columnDefs={colDefs}
rowData={rowData}
rowData={globalVariables ?? []}
onDelete={removeVariables}
/>
</div>

View file

@ -43,7 +43,6 @@ const useAuthStore = create<AuthStoreType>((set, get) => ({
// getUser: () => {
// const setLoading = useAlertStore.getState().setLoading;
// const getFoldersApi = useFolderStore.getState().getFoldersApi;
// const setGlobalVariables = useGlobalVariablesStore.getState().setGlobalVariables;
// const checkHasStore = useStoreStore.getState().checkHasStore;
// const fetchApiData = useStoreStore.getState().fetchApiData;
@ -51,8 +50,6 @@ const useAuthStore = create<AuthStoreType>((set, get) => ({
// .then(async (user) => {
// set({ userData: user, isAdmin: user.is_superuser });
// getFoldersApi(true, true);
// const res = await getGlobalVariables();
// setGlobalVariables(res);
// checkHasStore();
// fetchApiData();
// })

View file

@ -344,7 +344,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
updateGroupRecursion(
newNode,
selection.edges,
useGlobalVariablesStore.getState().unavaliableFields,
useGlobalVariablesStore.getState().unavailableFields,
useGlobalVariablesStore.getState().globalVariablesEntries,
);

View file

@ -2,7 +2,6 @@ import { brokenEdgeMessage } from "@/utils/utils";
import { AxiosError } from "axios";
import { cloneDeep } from "lodash";
import pDebounce from "p-debounce";
import { useLocation } from "react-router-dom";
import { Edge, Node, Viewport, XYPosition } from "reactflow";
import { create } from "zustand";
import {
@ -223,7 +222,7 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
updateGroupRecursion(
node,
flowData?.edges,
useGlobalVariablesStore.getState().unavaliableFields,
useGlobalVariablesStore.getState().unavailableFields,
useGlobalVariablesStore.getState().globalVariablesEntries,
);
});

View file

@ -1,49 +1,15 @@
import { create } from "zustand";
import { GlobalVariablesStore } from "../../types/zustand/globalVariables";
import getUnavailableFields from "./utils/get-unavailable-fields";
export const useGlobalVariablesStore = create<GlobalVariablesStore>(
(set, get) => ({
unavaliableFields: undefined,
setUnavaliableFields: (fields) => {
set({ unavaliableFields: fields });
unavailableFields: {},
setUnavailableFields: (fields) => {
set({ unavailableFields: fields });
},
removeUnavaliableField: (field) => {
const newFields = get().unavaliableFields || {};
delete newFields[field];
set({ unavaliableFields: newFields });
},
globalVariablesEntries: undefined,
globalVariables: {},
setGlobalVariables: (variables) => {
set({
globalVariables: variables,
globalVariablesEntries: Object.keys(variables) || [],
unavaliableFields: getUnavailableFields(variables),
});
},
addGlobalVariable: (name, id, type, default_fields) => {
const data = { id, type, default_fields };
const newVariables = { ...get().globalVariables, [name]: data };
set({
globalVariables: newVariables,
globalVariablesEntries: Object.keys(newVariables) || [],
unavaliableFields: getUnavailableFields(newVariables),
});
},
removeGlobalVariable: async (name) => {
const id = get().globalVariables[name]?.id;
if (id === undefined) return;
const newVariables = { ...get().globalVariables };
delete newVariables[name];
set({
globalVariables: newVariables,
globalVariablesEntries: Object.keys(newVariables) || [],
unavaliableFields: getUnavailableFields(newVariables),
});
},
getVariableId: (name) => {
return get().globalVariables[name]?.id;
globalVariablesEntries: [],
setGlobalVariablesEntries: (entries) => {
set({ globalVariablesEntries: entries });
},
}),
);

View file

@ -1,11 +1,13 @@
export default function getUnavailableFields(variables: {
[key: string]: { default_fields?: string[] };
}): { [name: string]: string } {
import { GlobalVariable } from "@/types/global_variables";
export default function getUnavailableFields(variables: GlobalVariable[]): {
[name: string]: string;
} {
const unVariables: { [name: string]: string } = {};
Object.keys(variables).forEach((key) => {
if (variables[key].default_fields) {
variables[key].default_fields!.forEach((field) => {
unVariables[field] = key;
variables.forEach((variable) => {
if (variable.default_fields) {
variable.default_fields!.forEach((field) => {
unVariables[field] = variable.name;
});
}
});

View file

@ -0,0 +1,6 @@
export type GlobalVariable = {
id: string;
type: string;
default_fields: string[];
name: string;
};

View file

@ -1,31 +1,6 @@
export type GlobalVariablesStore = {
globalVariablesEntries: Array<string> | undefined;
globalVariables: {
[name: string]: {
id: string;
type?: string;
default_fields?: string[];
value?: string;
};
};
setGlobalVariables: (variables: {
[name: string]: {
id: string;
type?: string;
default_fields?: string[];
value?: string;
};
}) => void;
addGlobalVariable: (
name: string,
id: string,
type?: string,
default_fields?: string[],
value?: string,
) => void;
removeGlobalVariable: (name: string) => Promise<void>;
getVariableId: (name: string) => string | undefined;
unavaliableFields: { [name: string]: string } | undefined;
setUnavaliableFields: (fields: { [name: string]: string }) => void;
removeUnavaliableField: (field: string) => void;
globalVariablesEntries: Array<string>;
setGlobalVariablesEntries: (entries: Array<string>) => void;
unavailableFields: { [name: string]: string };
setUnavailableFields: (fields: { [name: string]: string }) => void;
};

View file

@ -43,14 +43,19 @@ test("should interact with global variables", async ({ page }) => {
.getByPlaceholder("Insert a value for the variable...")
.fill("testtesttesttesttesttesttesttest");
await page.getByTestId("popover-anchor-apply-to-fields").click();
await page.waitForTimeout(2000);
await page.waitForTimeout(5000);
await page.getByPlaceholder("Search options...").fill("System Message");
await page.waitForSelector("text=System Message", { timeout: 30000 });
await page.getByText("System Message").first().click();
await page.getByPlaceholder("Search options...").fill("openAI");
await page.waitForSelector("text=OpenAI API Base", { timeout: 30000 });
await page.getByText("OpenAI API Base").first().click();
await page.getByPlaceholder("Search options...").fill("llama");