Skip to content

Commit eac550e

Browse files
committed
refactor: revert changes
1 parent 37fa179 commit eac550e

File tree

5 files changed

+265
-26
lines changed

5 files changed

+265
-26
lines changed

Diff for: src/features/workspace/components/workspace-custom-instructions.tsx

+8-6
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ function useCustomInstructionsValue({
128128
[initialValue],
129129
);
130130
const formState = useFormState(initialFormValues);
131-
const { values, updateFormValues } = formState;
131+
const { values, setInitialValues } = formState;
132132

133133
// Subscribe to changes in the workspace system prompt value in the query cache
134134
useEffect(() => {
@@ -145,14 +145,14 @@ function useCustomInstructionsValue({
145145
const prompt: string | null = getCustomInstructionsFromEvent(event);
146146
if (prompt === values.prompt || prompt === null) return;
147147

148-
updateFormValues({ prompt });
148+
setInitialValues({ prompt });
149149
}
150150
});
151151

152152
return () => {
153153
return unsubscribe();
154154
};
155-
}, [options, queryClient, updateFormValues, values.prompt]);
155+
}, [options, queryClient, setInitialValues, values.prompt]);
156156

157157
return { ...formState };
158158
}
@@ -302,14 +302,16 @@ export function WorkspaceCustomInstructions({
302302
mutateAsync(
303303
{ ...options, body: { prompt: value } },
304304
{
305-
onSuccess: () =>
305+
onSuccess: () => {
306+
formState.setInitialValues({ prompt: values.prompt });
306307
invalidateQueries(queryClient, [
307308
v1GetWorkspaceCustomInstructionsQueryKey,
308-
]),
309+
]);
310+
},
309311
},
310312
);
311313
},
312-
[mutateAsync, options, queryClient],
314+
[formState, mutateAsync, options, queryClient, values.prompt],
313315
);
314316

315317
return (
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
import {
2+
Alert,
3+
Button,
4+
Card,
5+
CardBody,
6+
CardFooter,
7+
Form,
8+
Input,
9+
Label,
10+
Link,
11+
LinkButton,
12+
Text,
13+
TextField,
14+
} from "@stacklok/ui-kit";
15+
import { twMerge } from "tailwind-merge";
16+
import { useMutationPreferredModelWorkspace } from "../hooks/use-mutation-preferred-model-workspace";
17+
import { V1ListAllModelsForAllProvidersResponse } from "@/api/generated";
18+
import { FormEvent } from "react";
19+
import {
20+
LayersThree01,
21+
LinkExternal01,
22+
Plus,
23+
Trash01,
24+
} from "@untitled-ui/icons-react";
25+
import { SortableArea } from "@/components/SortableArea";
26+
import { WorkspaceModelsDropdown } from "./workspace-models-dropdown";
27+
import { useQueryListAllModelsForAllProviders } from "@/hooks/use-query-list-all-models-for-all-providers";
28+
import { useQueryMuxingRulesWorkspace } from "../hooks/use-query-muxing-rules-workspace";
29+
import {
30+
PreferredMuxRule,
31+
useMuxingRulesFormState,
32+
} from "../hooks/use-muxing-rules-form-workspace";
33+
import { FormButtons } from "@/components/FormButtons";
34+
35+
function MissingProviderBanner() {
36+
return (
37+
// TODO needs to update the related ui-kit component that diverges from the design
38+
<Alert
39+
variant="warning"
40+
className="bg-brand-200 text-primary font-normal dark:bg-[#272472]"
41+
title="You must configure at least one provider before selecting your desired model."
42+
>
43+
<LinkButton
44+
className="mt-4 text-white dark:bg-[#7D7DED]"
45+
href="/providers"
46+
>
47+
Configure a provider
48+
</LinkButton>
49+
</Alert>
50+
);
51+
}
52+
53+
type SortableItemProps = {
54+
index: number;
55+
rule: PreferredMuxRule;
56+
models: V1ListAllModelsForAllProvidersResponse;
57+
isArchived: boolean;
58+
showRemoveButton: boolean;
59+
setRuleItem: (rule: PreferredMuxRule) => void;
60+
removeRule: (index: number) => void;
61+
};
62+
63+
function SortableItem({
64+
rule,
65+
index,
66+
setRuleItem,
67+
removeRule,
68+
models,
69+
showRemoveButton,
70+
isArchived,
71+
}: SortableItemProps) {
72+
return (
73+
<div className="flex items-center gap-2" key={rule.id}>
74+
<div className="flex w-full justify-between">
75+
<TextField
76+
aria-labelledby="filter-by-label-id"
77+
onFocus={(event) => event.preventDefault()}
78+
value={rule?.matcher ?? ""}
79+
isDisabled={isArchived}
80+
name="matcher"
81+
onChange={(matcher) => {
82+
setRuleItem({ ...rule, matcher });
83+
}}
84+
>
85+
<Input placeholder="eg file type, file name" />
86+
</TextField>
87+
</div>
88+
<div className="flex w-3/5 gap-2">
89+
<WorkspaceModelsDropdown
90+
rule={rule}
91+
isArchived={isArchived}
92+
models={models}
93+
onChange={({ model, provider_id }) =>
94+
setRuleItem({ ...rule, provider_id, model })
95+
}
96+
/>
97+
{showRemoveButton && (
98+
<Button
99+
aria-label="remove mux rule"
100+
isIcon
101+
variant="tertiary"
102+
onPress={() => removeRule(index)}
103+
>
104+
<Trash01 />
105+
</Button>
106+
)}
107+
</div>
108+
</div>
109+
);
110+
}
111+
112+
export function WorkspaceMuxingModel({
113+
className,
114+
workspaceName,
115+
isArchived,
116+
}: {
117+
className?: string;
118+
workspaceName: string;
119+
isArchived: boolean | undefined;
120+
}) {
121+
const { data: muxingRules, isPending } =
122+
useQueryMuxingRulesWorkspace(workspaceName);
123+
const { addRule, setRules, setRuleItem, removeRule, formState } =
124+
useMuxingRulesFormState(muxingRules);
125+
const {
126+
values: { rules },
127+
} = formState;
128+
129+
const { mutateAsync } = useMutationPreferredModelWorkspace();
130+
const { data: providerModels = [] } = useQueryListAllModelsForAllProviders();
131+
const isModelsEmpty = !isPending && providerModels.length === 0;
132+
const showRemoveButton = rules.length > 1;
133+
134+
const handleSubmit = (event: FormEvent) => {
135+
event.preventDefault();
136+
mutateAsync(
137+
{
138+
path: { workspace_name: workspaceName },
139+
body: rules.map(({ id, ...rest }) => {
140+
void id;
141+
return { ...rest };
142+
}),
143+
},
144+
{
145+
onSuccess: () => {
146+
formState.setInitialValues({ rules });
147+
},
148+
},
149+
);
150+
};
151+
152+
if (isModelsEmpty) {
153+
return (
154+
<Card className={twMerge(className, "shrink-0")}>
155+
<CardBody className="flex flex-col gap-2">
156+
<Text className="text-primary">Model Muxing</Text>
157+
<MissingProviderBanner />
158+
</CardBody>
159+
</Card>
160+
);
161+
}
162+
163+
return (
164+
<Form
165+
onSubmit={handleSubmit}
166+
validationBehavior="aria"
167+
data-testid="preferred-model"
168+
>
169+
<Card className={twMerge(className, "shrink-0")}>
170+
<CardBody className="flex flex-col gap-6">
171+
<div className="flex flex-col justify-start">
172+
<Text className="text-primary">Model Muxing</Text>
173+
<Text className="flex items-center gap-1 text-secondary mb-0 text-balance">
174+
Filters will be applied in order (top to bottom), empty filters
175+
will apply to all.
176+
<Link
177+
variant="primary"
178+
className="flex items-center gap-1 no-underline"
179+
href="https://docs.codegate.ai/features/muxing"
180+
target="_blank"
181+
>
182+
Learn more <LinkExternal01 className="size-4" />
183+
</Link>
184+
</Text>
185+
</div>
186+
187+
<div className="flex flex-col gap-2 w-full">
188+
<div className="flex gap-2">
189+
<div className="w-12">&nbsp;</div>
190+
<div className="w-full">
191+
<Label id="filter-by-label-id">Filter by</Label>
192+
</div>
193+
<div className="w-3/5">
194+
<Label id="preferred-model-id">Preferred Model</Label>
195+
</div>
196+
</div>
197+
<SortableArea items={rules} setItems={setRules}>
198+
{(rule, index) => (
199+
<SortableItem
200+
key={rule.id}
201+
index={index}
202+
rule={rule}
203+
setRuleItem={setRuleItem}
204+
removeRule={removeRule}
205+
models={providerModels}
206+
showRemoveButton={showRemoveButton}
207+
isArchived={!!isArchived}
208+
/>
209+
)}
210+
</SortableArea>
211+
</div>
212+
</CardBody>
213+
<CardFooter className="justify-between">
214+
<div className="flex gap-2">
215+
<Button
216+
className="w-fit"
217+
variant="tertiary"
218+
onPress={addRule}
219+
isDisabled={isArchived}
220+
>
221+
<Plus /> Add Filter
222+
</Button>
223+
224+
<LinkButton className="w-fit" variant="tertiary" href="/providers">
225+
<LayersThree01 /> Manage providers
226+
</LinkButton>
227+
</div>
228+
<FormButtons
229+
isPending={isPending}
230+
formState={formState}
231+
canSubmit={!isArchived}
232+
/>
233+
</CardFooter>
234+
</Card>
235+
</Form>
236+
);
237+
}

Diff for: src/features/workspace/components/workspace-name.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,10 @@ export function WorkspaceName({
3939
mutateAsync(
4040
{ body: { name: workspaceName, rename_to: values.workspaceName } },
4141
{
42-
onSuccess: () => navigate(`/workspace/${values.workspaceName}`),
42+
onSuccess: () => {
43+
formState.setInitialValues({ workspaceName: values.workspaceName });
44+
navigate(`/workspace/${values.workspaceName}`);
45+
},
4346
},
4447
);
4548
};

Diff for: src/features/workspace/hooks/use-muxing-rules-form-workspace.ts

+3-4
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,14 @@ export const useMuxingRulesFormState = (initialValues: MuxRule[]) => {
1919
}>({
2020
rules: [{ ...DEFAULT_STATE, id: uuidv4() }],
2121
});
22-
const { values, updateFormValues } = formState;
22+
const { values, updateFormValues, setInitialValues } = formState;
2323

2424
useEffect(() => {
2525
if (initialValues.length === 0) return;
26-
27-
updateFormValues({
26+
setInitialValues({
2827
rules: initialValues.map((item) => ({ ...item, id: uuidv4() })),
2928
});
30-
}, [initialValues, updateFormValues]);
29+
}, [initialValues, setInitialValues]);
3130

3231
const addRule = useCallback(() => {
3332
updateFormValues({

Diff for: src/hooks/useFormState.ts

+13-15
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,22 @@ import { useCallback, useMemo, useRef, useState } from "react";
44
export type FormState<T> = {
55
values: T;
66
updateFormValues: (newState: Partial<T>) => void;
7+
setInitialValues: (newState: T) => void;
78
resetForm: () => void;
89
isDirty: boolean;
910
};
1011

11-
function useDeepMemo<T>(value: T): T {
12-
const ref = useRef<T>(value);
13-
if (!isEqual(ref.current, value)) {
14-
ref.current = value;
15-
}
16-
return ref.current;
17-
}
18-
1912
export function useFormState<Values extends Record<string, unknown>>(
2013
initialValues: Values,
2114
): FormState<Values> {
22-
const memoizedInitialValues = useDeepMemo(initialValues);
15+
const memoizedInitialValues = useRef(initialValues);
2316
// this could be replaced with some form library later
24-
const [values, setValues] = useState<Values>(memoizedInitialValues);
17+
const [values, setValues] = useState<Values>(memoizedInitialValues.current);
18+
19+
const setInitialValues = useCallback((newInitialValues: Values) => {
20+
memoizedInitialValues.current = newInitialValues;
21+
setValues(newInitialValues);
22+
}, []);
2523

2624
const updateFormValues = useCallback((newState: Partial<Values>) => {
2725
setValues((prevState: Values) => {
@@ -31,17 +29,17 @@ export function useFormState<Values extends Record<string, unknown>>(
3129
}, []);
3230

3331
const resetForm = useCallback(() => {
34-
setValues(memoizedInitialValues);
32+
setValues(memoizedInitialValues.current);
3533
}, [memoizedInitialValues]);
3634

3735
const isDirty = useMemo(
38-
() => !isEqual(values, initialValues),
39-
[values, initialValues],
36+
() => !isEqual(values, memoizedInitialValues.current),
37+
[values, memoizedInitialValues],
4038
);
4139

4240
const formState = useMemo(
43-
() => ({ values, updateFormValues, resetForm, isDirty }),
44-
[values, updateFormValues, resetForm, isDirty],
41+
() => ({ values, updateFormValues, resetForm, isDirty, setInitialValues }),
42+
[values, updateFormValues, resetForm, isDirty, setInitialValues],
4543
);
4644

4745
return formState;

0 commit comments

Comments
 (0)