+ Model Muxing
+
+ Select the model you would like to use in this workspace. This
+ section applies only if you are using the MUX endpoint.
+
+ Learn more
+
+
+
+
+
+
+
+
+
+
+
+ Manage providers
+
+
+
+
+
+ Revert changes
+
+
+ Save
+
+
+
+
+
+
+ )
+}
diff --git a/src/features/workspace/components/form-mux-select-matcher-type.tsx b/src/features/workspace/components/form-mux-select-matcher-type.tsx
new file mode 100644
index 00000000..25833d93
--- /dev/null
+++ b/src/features/workspace/components/form-mux-select-matcher-type.tsx
@@ -0,0 +1,48 @@
+import { MuxMatcherType } from '@/api/generated'
+import { SelectButton } from '@stacklok/ui-kit'
+import { getMuxFieldName } from '../lib/get-mux-field-name'
+import { FormSelect } from './tmp/form-select'
+import { FieldValuesMuxRow } from '../lib/schema-mux'
+
+export function FormSelectMatcherType({
+ index,
+ row,
+ isDisabled,
+}: {
+ index: number
+ row: FieldValuesMuxRow & { id: string }
+ isDisabled: boolean
+}) {
+ return (
+
+
+
+ )
+}
diff --git a/src/features/workspace/components/form-mux-text-field-matcher.tsx b/src/features/workspace/components/form-mux-text-field-matcher.tsx
new file mode 100644
index 00000000..26651962
--- /dev/null
+++ b/src/features/workspace/components/form-mux-text-field-matcher.tsx
@@ -0,0 +1,29 @@
+import { Input } from '@stacklok/ui-kit'
+import { getMuxFieldName } from '../lib/get-mux-field-name'
+import { FieldValuesMuxRow } from '../lib/schema-mux'
+import { MuxMatcherType } from '@/api/generated'
+import { FormTextField } from './tmp/form-text-field'
+
+export function FormMuxTextFieldMatcher({
+ index,
+ row,
+ isDisabled,
+}: {
+ index: number
+ row: FieldValuesMuxRow & { id: string }
+ isDisabled: boolean
+}) {
+ return (
+
+
+
+ )
+}
diff --git a/src/features/workspace/components/tmp/form-checkbox-group.tsx b/src/features/workspace/components/tmp/form-checkbox-group.tsx
new file mode 100644
index 00000000..c3dec8c5
--- /dev/null
+++ b/src/features/workspace/components/tmp/form-checkbox-group.tsx
@@ -0,0 +1,53 @@
+import { CheckboxGroup, FieldError } from '@stacklok/ui-kit'
+import type { ComponentProps } from 'react'
+import { useController, useFormContext } from 'react-hook-form'
+
+/**
+ * A `FormCheckboxGroup` connects a `CheckboxGroup` to a `Form` component using `react-hook-form`.
+ *
+ * [React Aria Documentation](https://react-spectrum.adobe.com/react-aria/CheckboxGroup.html)
+ */
+export function FormCheckboxGroup({
+ children,
+ ...props
+}: ComponentProps) {
+ if (props.name == null)
+ throw new Error('FormCheckboxGroup requires a name prop')
+
+ const { control } = useFormContext()
+
+ const {
+ field: { disabled: isDisabledByForm, name, onBlur, onChange, ref, value },
+ fieldState: { error, invalid },
+ } = useController({
+ control,
+ defaultValue: props.value ?? props.defaultValue ?? [],
+ name: props.name,
+ })
+
+ return (
+ {
+ onChange(k)
+ props.onChange?.(k)
+ }}
+ ref={ref}
+ defaultValue={value}
+ validationBehavior="aria" // Let React Hook Form handle validation instead of the browser.
+ >
+ {(renderProps) => {
+ return (
+ <>
+ {typeof children === 'function' ? children(renderProps) : children}
+ {error?.message}
+ >
+ )
+ }}
+
+ )
+}
diff --git a/src/features/workspace/components/tmp/form-combobox.tsx b/src/features/workspace/components/tmp/form-combobox.tsx
new file mode 100644
index 00000000..dea8ada1
--- /dev/null
+++ b/src/features/workspace/components/tmp/form-combobox.tsx
@@ -0,0 +1,60 @@
+import { ComboBox, FieldError, OptionsSchema } from '@stacklok/ui-kit'
+import type { ComponentProps } from 'react'
+import { useController, useFormContext } from 'react-hook-form'
+
+/**
+ * A `FormComboBox` connects a `ComboBox` to a `Form` component using `react-hook-form`.
+ *
+ * [React Aria Documentation](https://react-spectrum.adobe.com/react-aria/ComboBox.html)
+ */
+export function FormComboBox<
+ T extends OptionsSchema<'listbox'> = OptionsSchema<'listbox'>,
+>({
+ children,
+ shouldShowValidationError = true,
+ ...props
+}: ComponentProps> & {
+ shouldShowValidationError?: boolean
+}) {
+ if (props.name == null) throw new Error('FormComboBox requires a name prop')
+
+ const { control } = useFormContext()
+
+ const {
+ field: { disabled: isDisabledByForm, name, onBlur, onChange, ref, value },
+ fieldState: { error, invalid },
+ } = useController({
+ control,
+ defaultValue: props.selectedKey ?? props.defaultSelectedKey,
+ name: props.name,
+ })
+
+ return (
+ {
+ onChange(k)
+ props.onSelectionChange?.(k)
+ }}
+ ref={ref}
+ selectedKey={undefined} // react-hook-form relies on uncontrolled component
+ validationBehavior="aria" // Let react-hook-form handle validation
+ >
+ {(rp) => {
+ return (
+ <>
+ {typeof children === 'function' ? children(rp) : children}
+ {shouldShowValidationError ? (
+ {error?.message}
+ ) : null}
+ >
+ )
+ }}
+
+ )
+}
diff --git a/src/features/workspace/components/tmp/form-discard-changes-button.tsx b/src/features/workspace/components/tmp/form-discard-changes-button.tsx
new file mode 100644
index 00000000..ff0d06a3
--- /dev/null
+++ b/src/features/workspace/components/tmp/form-discard-changes-button.tsx
@@ -0,0 +1,31 @@
+import { Button } from '@stacklok/ui-kit'
+import { FlipBackward } from '@untitled-ui/icons-react'
+import type { ComponentProps } from 'react'
+import { type FieldValues, useFormContext } from 'react-hook-form'
+
+export function FormDiscardChangesButton({
+ children = (
+ <>
+
+ Discard changes
+ >
+ ),
+ variant = 'tertiary',
+ defaultValues,
+ ...props
+}: ComponentProps & { defaultValues: T }) {
+ const { reset, formState } = useFormContext()
+ const { isDirty, isValidating } = formState || {}
+
+ return (
+
+ )
+}
diff --git a/src/features/workspace/components/tmp/form-radio-group.tsx b/src/features/workspace/components/tmp/form-radio-group.tsx
new file mode 100644
index 00000000..ad654c84
--- /dev/null
+++ b/src/features/workspace/components/tmp/form-radio-group.tsx
@@ -0,0 +1,60 @@
+import { FieldError, RadioGroup } from '@stacklok/ui-kit'
+import type { ComponentProps } from 'react'
+import { useController, useFormContext } from 'react-hook-form'
+
+/**
+ * A `FormRadioGroup` connects a `RadioGroup` to a `Form` component using `react-hook-form`.
+ *
+ * [React Aria Documentation](https://react-spectrum.adobe.com/react-aria/RadioGroup.html)
+ */
+export function FormRadioGroup({
+ children,
+ ...props
+}: ComponentProps) {
+ if (props.name == null) throw new Error('FormRadioGroup requires a name prop')
+
+ const { control } = useFormContext()
+
+ const {
+ field: {
+ disabled: isDisabledByForm,
+ name,
+ onBlur,
+ onChange,
+ ref,
+ value = '',
+ },
+ fieldState: { error, invalid },
+ } = useController({
+ control,
+ defaultValue: props.value ?? props.defaultValue,
+ name: props.name,
+ })
+
+ return (
+ {
+ onChange(k)
+ props.onChange?.(k)
+ }}
+ ref={ref}
+ value={undefined} // react-hook-form relies on uncontrolled component
+ validationBehavior="aria" // Let react-hook-form handle validation
+ >
+ {(renderProps) => {
+ return (
+ <>
+ {typeof children === 'function' ? children(renderProps) : children}
+ {error?.message}
+ >
+ )
+ }}
+
+ )
+}
diff --git a/src/features/workspace/components/tmp/form-reset-on-submit.tsx b/src/features/workspace/components/tmp/form-reset-on-submit.tsx
new file mode 100644
index 00000000..3a8774ff
--- /dev/null
+++ b/src/features/workspace/components/tmp/form-reset-on-submit.tsx
@@ -0,0 +1,23 @@
+import { useEffect } from 'react'
+import { useFormContext } from 'react-hook-form'
+
+/**
+ * A component that resets the form after a successful submission.
+ * Used to test that form fields are reset after a successful submission.
+ */
+export function FormResetOnSubmit() {
+ const {
+ formState: { isSubmitSuccessful },
+ reset,
+ } = useFormContext()
+
+ // It is recommended in the React Hook Form documentation to use `useEffect` to
+ // handle side effects like resetting the form after a successful submission.
+ useEffect(() => {
+ if (isSubmitSuccessful) {
+ reset()
+ }
+ }, [isSubmitSuccessful, reset])
+
+ return null
+}
diff --git a/src/features/workspace/components/tmp/form-select.tsx b/src/features/workspace/components/tmp/form-select.tsx
new file mode 100644
index 00000000..abf9dca5
--- /dev/null
+++ b/src/features/workspace/components/tmp/form-select.tsx
@@ -0,0 +1,58 @@
+import { FieldError, OptionsSchema, Select } from '@stacklok/ui-kit'
+import type { ComponentProps } from 'react'
+import { useController, useFormContext } from 'react-hook-form'
+
+/**
+ * A `FormSelect` connects a `Select` to a `Form` component using `react-hook-form`.
+ *
+ * [React Aria Documentation](https://react-spectrum.adobe.com/react-aria/Select.html)
+ */
+export function FormSelect<
+ T extends OptionsSchema<'listbox'> = OptionsSchema<'listbox'>,
+>({
+ children,
+ shouldShowValidationError = true,
+ ...props
+}: ComponentProps> & { shouldShowValidationError?: boolean }) {
+ if (props.name == null) throw new Error('FormSelect requires a name prop')
+
+ const { control } = useFormContext()
+
+ const {
+ field: { disabled: isDisabledByForm, name, onBlur, onChange, ref, value },
+ fieldState: { error, invalid },
+ } = useController({
+ control,
+ defaultValue: props.selectedKey ?? props.defaultSelectedKey,
+ name: props.name,
+ })
+
+ return (
+
+ )
+}
diff --git a/src/features/workspace/components/tmp/form-submit-button.tsx b/src/features/workspace/components/tmp/form-submit-button.tsx
new file mode 100644
index 00000000..0e0d1d7b
--- /dev/null
+++ b/src/features/workspace/components/tmp/form-submit-button.tsx
@@ -0,0 +1,27 @@
+import { Button } from '@stacklok/ui-kit'
+import type { ComponentProps } from 'react'
+import { useFormState } from 'react-hook-form'
+
+export function FormSubmitButton({
+ children = 'Submit',
+ variant = 'primary',
+ ...props
+}: ComponentProps) {
+ const { isSubmitting, isValidating, isDirty } = useFormState()
+
+ return (
+
+ )
+}
diff --git a/src/features/workspace/components/tmp/form-text-field.tsx b/src/features/workspace/components/tmp/form-text-field.tsx
new file mode 100644
index 00000000..e3cbb31e
--- /dev/null
+++ b/src/features/workspace/components/tmp/form-text-field.tsx
@@ -0,0 +1,56 @@
+import { FieldError, TextField } from '@stacklok/ui-kit'
+import type { ComponentProps } from 'react'
+import { useController, useFormContext } from 'react-hook-form'
+
+/**
+ * A form text field connects a `TextField` to a `Form` component using `react-hook-form`.
+ *
+ * [React Aria Documentation](https://react-spectrum.adobe.com/react-aria/TextField.html)
+ */
+export function FormTextField({
+ children,
+ shouldShowValidationError = true,
+ ...props
+}: ComponentProps & { shouldShowValidationError?: boolean }) {
+ if (props.name == null) throw new Error('FormTextField requires a name prop')
+
+ const { control } = useFormContext()
+
+ const {
+ field: { disabled: isDisabledByForm, name, onBlur, onChange, ref, value },
+ fieldState: { error, invalid },
+ } = useController({
+ control,
+ defaultValue: props.value ?? props.defaultValue,
+ name: props.name,
+ })
+
+ return (
+ {
+ onChange(v)
+ props.onChange?.(v)
+ }}
+ ref={ref}
+ value={undefined} // react-hook-form relies on uncontrolled component
+ validationBehavior="aria" // Let react-hook-form handle validation
+ >
+ {(renderProps) => {
+ return (
+ <>
+ {typeof children === 'function' ? children(renderProps) : children}
+ {shouldShowValidationError ? (
+ {error?.message}
+ ) : null}
+ >
+ )
+ }}
+
+ )
+}
diff --git a/src/features/workspace/components/workspace-muxing-model.tsx b/src/features/workspace/components/workspace-muxing-model.tsx
deleted file mode 100644
index d6dcfcf5..00000000
--- a/src/features/workspace/components/workspace-muxing-model.tsx
+++ /dev/null
@@ -1,301 +0,0 @@
-import {
- Alert,
- Button,
- Card,
- CardBody,
- CardFooter,
- Form,
- Input,
- Label,
- Link,
- LinkButton,
- Select,
- SelectButton,
- Text,
- TextField,
- Tooltip,
- TooltipInfoButton,
- TooltipTrigger,
-} from '@stacklok/ui-kit'
-import { twMerge } from 'tailwind-merge'
-import { useMutationPreferredModelWorkspace } from '../hooks/use-mutation-preferred-model-workspace'
-import {
- ProviderType,
- V1ListAllModelsForAllProvidersResponse,
-} from '@/api/generated'
-import { FormEvent } from 'react'
-import {
- LayersThree01,
- LinkExternal01,
- Plus,
- Trash01,
-} from '@untitled-ui/icons-react'
-import { SortableArea } from '@/components/SortableArea'
-import { WorkspaceModelsDropdown } from './workspace-models-dropdown'
-import { useQueryListAllModelsForAllProviders } from '@/hooks/use-query-list-all-models-for-all-providers'
-import { useQueryMuxingRulesWorkspace } from '../hooks/use-query-muxing-rules-workspace'
-import {
- PreferredMuxRule,
- useMuxingRulesFormState,
-} from '../hooks/use-muxing-rules-form-workspace'
-import { FormButtons } from '@/components/FormButtons'
-import { getRuleData, isRequestType } from '../lib/utils'
-import { z } from 'zod'
-
-function MissingProviderBanner() {
- return (
- // TODO needs to update the related ui-kit component that diverges from the design
-
-
- Configure a provider
-
-
- )
-}
-
-type SortableItemProps = {
- index: number
- rule: PreferredMuxRule
- models: V1ListAllModelsForAllProvidersResponse
- isArchived: boolean
- showRemoveButton: boolean
- isDefaultRule: boolean
- setRuleItem: (rule: PreferredMuxRule) => void
- removeRule: (index: number) => void
-}
-
-function SortableItem({
- rule,
- index,
- setRuleItem,
- removeRule,
- models,
- showRemoveButton,
- isArchived,
- isDefaultRule,
-}: SortableItemProps) {
- const { selectedKey, placeholder, items } = getRuleData({
- isDefaultRule,
- matcher_type: rule.matcher_type,
- })
-
- return (
-