From 30428fbabee94c0c0b10669d32f2eae73f3e03c9 Mon Sep 17 00:00:00 2001 From: Giuseppe Scuglia Date: Mon, 27 Jan 2025 16:27:03 +0100 Subject: [PATCH 01/29] feat(mux): wip model overrides workspace update --- .../components/workspace-model-overrides.tsx | 101 ++++++++++++++++++ .../hooks/use-model-overrides-workspace.ts | 42 ++++++++ .../use-mutation-model-overrides-workspace.ts | 14 +++ src/routes/route-workspace.tsx | 6 ++ 4 files changed, 163 insertions(+) create mode 100644 src/features/workspace/components/workspace-model-overrides.tsx create mode 100644 src/features/workspace/hooks/use-model-overrides-workspace.ts create mode 100644 src/features/workspace/hooks/use-mutation-model-overrides-workspace.ts diff --git a/src/features/workspace/components/workspace-model-overrides.tsx b/src/features/workspace/components/workspace-model-overrides.tsx new file mode 100644 index 00000000..616fb398 --- /dev/null +++ b/src/features/workspace/components/workspace-model-overrides.tsx @@ -0,0 +1,101 @@ +import { + Button, + Card, + CardBody, + CardFooter, + Form, + Input, + Label, + Select, + SelectButton, + Text, + TextField, +} from "@stacklok/ui-kit"; +import { twMerge } from "tailwind-merge"; +import { useModelOverridesWorkspace } from "../hooks/use-model-overrides-workspace"; +import { useMutationModelOverridesWorkspace } from "../hooks/use-mutation-model-overrides-workspace"; +import { MuxMatcherType } from "@/api/generated"; + +export function WorkspaceModelOverrides({ + className, + workspaceName, + isArchived, +}: { + className?: string; + workspaceName: string; + isArchived: boolean | undefined; +}) { + const { setOverrideItem, overrides } = useModelOverridesWorkspace(); + const { mutateAsync } = useMutationModelOverridesWorkspace(); + + console.log(overrides); + const handleSubmit = () => { + mutateAsync({ + path: { workspace_name: workspaceName }, + body: overrides.map((item) => ({ + ...item, + matcher_type: MuxMatcherType.FILE_REGEX, + })), + }); + }; + + return ( +
+ + +
+ Model Overrides + + Route to different large language models based on file type, + individual files, or repository. + +
+
+ setOverrideItem({ id: 0, matcher })} + > + + + + +
+
+ + + +
+
+ ); +} diff --git a/src/features/workspace/hooks/use-model-overrides-workspace.ts b/src/features/workspace/hooks/use-model-overrides-workspace.ts new file mode 100644 index 00000000..0b9052c3 --- /dev/null +++ b/src/features/workspace/hooks/use-model-overrides-workspace.ts @@ -0,0 +1,42 @@ +import { MuxRule } from "@/api/generated"; +import { create } from "zustand"; + +type State = { + setOverrides: () => void; + setOverrideItem: ({ + id, + model, + matcher, + }: { + matcher?: string; + id: number; + model?: string; + }) => void; + overrides: Omit[]; +}; + +export const useModelOverridesWorkspace = create((set, get) => ({ + overrides: [ + { + provider: "Anthropic", + model: "claude_3.5", + matcher: "", + }, + ], + setOverrides: () => {}, + setOverrideItem: ({ id, model, matcher }) => { + const { overrides } = get(); + + set({ + overrides: overrides.map((item, index) => + index === id + ? { + ...item, + model: model ?? item.model, + matcher: matcher ?? item.matcher, + } + : item, + ), + }); + }, +})); diff --git a/src/features/workspace/hooks/use-mutation-model-overrides-workspace.ts b/src/features/workspace/hooks/use-mutation-model-overrides-workspace.ts new file mode 100644 index 00000000..21111634 --- /dev/null +++ b/src/features/workspace/hooks/use-mutation-model-overrides-workspace.ts @@ -0,0 +1,14 @@ +import { useToastMutation } from "@/hooks/use-toast-mutation"; +import { useInvalidateWorkspaceQueries } from "./use-invalidate-workspace-queries"; +import { v1SetWorkspaceMuxesMutation } from "@/api/generated/@tanstack/react-query.gen"; + +export function useMutationModelOverridesWorkspace() { + const invalidate = useInvalidateWorkspaceQueries(); + return useToastMutation({ + ...v1SetWorkspaceMuxesMutation(), + onSuccess: async () => { + await invalidate(); + }, + successMsg: () => "Model overrides properly submitted!", + }); +} diff --git a/src/routes/route-workspace.tsx b/src/routes/route-workspace.tsx index 968a3609..76ab8ca3 100644 --- a/src/routes/route-workspace.tsx +++ b/src/routes/route-workspace.tsx @@ -8,6 +8,7 @@ import { useParams } from "react-router-dom"; import { useArchivedWorkspaces } from "@/features/workspace/hooks/use-archived-workspaces"; import { useRestoreWorkspaceButton } from "@/features/workspace/hooks/use-restore-workspace-button"; import { WorkspaceCustomInstructions } from "@/features/workspace/components/workspace-custom-instructions"; +import { WorkspaceModelOverrides } from "@/features/workspace/components/workspace-model-overrides"; function WorkspaceArchivedBanner({ name }: { name: string }) { const restoreButtonProps = useRestoreWorkspaceButton({ workspaceName: name }); @@ -52,6 +53,11 @@ export function RouteWorkspace() { className="mb-4" workspaceName={name} /> + Date: Mon, 27 Jan 2025 18:30:00 +0100 Subject: [PATCH 02/29] fetch providers from BE (currently mocked) --- src/api/generated/types.gen.ts | 27 +- src/api/openapi.json | 290 +++++++++++++----- .../components/workspace-model-overrides.tsx | 19 +- src/hooks/useModelsData.ts | 21 ++ 4 files changed, 240 insertions(+), 117 deletions(-) create mode 100644 src/hooks/useModelsData.ts diff --git a/src/api/generated/types.gen.ts b/src/api/generated/types.gen.ts index c45aecb6..a431b5be 100644 --- a/src/api/generated/types.gen.ts +++ b/src/api/generated/types.gen.ts @@ -53,7 +53,7 @@ export type Conversation = { type: QuestionType; chat_id: string; conversation_timestamp: string; - token_usage_agg: TokenUsageAggregate | null; + token_usage: TokenUsage | null; }; export type CreateOrRenameWorkspaceRequest = { @@ -135,8 +135,6 @@ export enum ProviderType { OPENAI = "openai", ANTHROPIC = "anthropic", VLLM = "vllm", - LLAMACPP = "llamacpp", - OLLAMA = "ollama", } /** @@ -152,26 +150,13 @@ export enum QuestionType { FIM = "fim", } -/** - * TokenUsage it's not a table, it's a model to represent the token usage. - * The data is stored in the outputs table. - */ -export type TokenUsage = { - input_tokens?: number; - output_tokens?: number; - input_cost?: number; - output_cost?: number; -}; - /** * Represents the tokens used. Includes the information of the tokens used by model. * `used_tokens` are the total tokens used in the `tokens_by_model` list. */ -export type TokenUsageAggregate = { - tokens_by_model: { - [key: string]: TokenUsageByModel; - }; - token_usage: TokenUsage; +export type TokenUsage = { + tokens_by_model: Array; + used_tokens: number; }; /** @@ -180,7 +165,7 @@ export type TokenUsageAggregate = { export type TokenUsageByModel = { provider_type: ProviderType; model: string; - token_usage: TokenUsage; + used_tokens: number; }; export type ValidationError = { @@ -408,6 +393,6 @@ export type V1GetWorkspaceTokenUsageData = { }; }; -export type V1GetWorkspaceTokenUsageResponse = TokenUsageAggregate; +export type V1GetWorkspaceTokenUsageResponse = TokenUsage; export type V1GetWorkspaceTokenUsageError = HTTPValidationError; diff --git a/src/api/openapi.json b/src/api/openapi.json index 7c11f8c8..1fbc0be1 100644 --- a/src/api/openapi.json +++ b/src/api/openapi.json @@ -8,7 +8,9 @@ "paths": { "/health": { "get": { - "tags": ["System"], + "tags": [ + "System" + ], "summary": "Health Check", "operationId": "health_check_health_get", "responses": { @@ -25,7 +27,10 @@ }, "/api/v1/provider-endpoints": { "get": { - "tags": ["CodeGate API", "Providers"], + "tags": [ + "CodeGate API", + "Providers" + ], "summary": "List Provider Endpoints", "description": "List all provider endpoints.", "operationId": "v1_list_provider_endpoints", @@ -75,7 +80,10 @@ } }, "post": { - "tags": ["CodeGate API", "Providers"], + "tags": [ + "CodeGate API", + "Providers" + ], "summary": "Add Provider Endpoint", "description": "Add a provider endpoint.", "operationId": "v1_add_provider_endpoint", @@ -115,7 +123,10 @@ }, "/api/v1/provider-endpoints/{provider_id}": { "get": { - "tags": ["CodeGate API", "Providers"], + "tags": [ + "CodeGate API", + "Providers" + ], "summary": "Get Provider Endpoint", "description": "Get a provider endpoint by ID.", "operationId": "v1_get_provider_endpoint", @@ -154,7 +165,10 @@ } }, "put": { - "tags": ["CodeGate API", "Providers"], + "tags": [ + "CodeGate API", + "Providers" + ], "summary": "Update Provider Endpoint", "description": "Update a provider endpoint by ID.", "operationId": "v1_update_provider_endpoint", @@ -203,7 +217,10 @@ } }, "delete": { - "tags": ["CodeGate API", "Providers"], + "tags": [ + "CodeGate API", + "Providers" + ], "summary": "Delete Provider Endpoint", "description": "Delete a provider endpoint by id.", "operationId": "v1_delete_provider_endpoint", @@ -242,7 +259,10 @@ }, "/api/v1/provider-endpoints/{provider_name}/models": { "get": { - "tags": ["CodeGate API", "Providers"], + "tags": [ + "CodeGate API", + "Providers" + ], "summary": "List Models By Provider", "description": "List models by provider.", "operationId": "v1_list_models_by_provider", @@ -287,7 +307,10 @@ }, "/api/v1/provider-endpoints/models": { "get": { - "tags": ["CodeGate API", "Providers"], + "tags": [ + "CodeGate API", + "Providers" + ], "summary": "List All Models For All Providers", "description": "List all models for all providers.", "operationId": "v1_list_all_models_for_all_providers", @@ -311,7 +334,10 @@ }, "/api/v1/workspaces": { "get": { - "tags": ["CodeGate API", "Workspaces"], + "tags": [ + "CodeGate API", + "Workspaces" + ], "summary": "List Workspaces", "description": "List all workspaces.", "operationId": "v1_list_workspaces", @@ -329,7 +355,10 @@ } }, "post": { - "tags": ["CodeGate API", "Workspaces"], + "tags": [ + "CodeGate API", + "Workspaces" + ], "summary": "Create Workspace", "description": "Create a new workspace.", "operationId": "v1_create_workspace", @@ -369,7 +398,10 @@ }, "/api/v1/workspaces/active": { "get": { - "tags": ["CodeGate API", "Workspaces"], + "tags": [ + "CodeGate API", + "Workspaces" + ], "summary": "List Active Workspaces", "description": "List all active workspaces.\n\nIn it's current form, this function will only return one workspace. That is,\nthe globally active workspace.", "operationId": "v1_list_active_workspaces", @@ -387,7 +419,10 @@ } }, "post": { - "tags": ["CodeGate API", "Workspaces"], + "tags": [ + "CodeGate API", + "Workspaces" + ], "summary": "Activate Workspace", "description": "Activate a workspace by name.", "operationId": "v1_activate_workspace", @@ -436,7 +471,10 @@ }, "/api/v1/workspaces/{workspace_name}": { "delete": { - "tags": ["CodeGate API", "Workspaces"], + "tags": [ + "CodeGate API", + "Workspaces" + ], "summary": "Delete Workspace", "description": "Delete a workspace by name.", "operationId": "v1_delete_workspace", @@ -475,7 +513,10 @@ }, "/api/v1/workspaces/archive": { "get": { - "tags": ["CodeGate API", "Workspaces"], + "tags": [ + "CodeGate API", + "Workspaces" + ], "summary": "List Archived Workspaces", "description": "List all archived workspaces.", "operationId": "v1_list_archived_workspaces", @@ -495,7 +536,10 @@ }, "/api/v1/workspaces/archive/{workspace_name}/recover": { "post": { - "tags": ["CodeGate API", "Workspaces"], + "tags": [ + "CodeGate API", + "Workspaces" + ], "summary": "Recover Workspace", "description": "Recover an archived workspace by name.", "operationId": "v1_recover_workspace", @@ -529,7 +573,10 @@ }, "/api/v1/workspaces/archive/{workspace_name}": { "delete": { - "tags": ["CodeGate API", "Workspaces"], + "tags": [ + "CodeGate API", + "Workspaces" + ], "summary": "Hard Delete Workspace", "description": "Hard delete an archived workspace by name.", "operationId": "v1_hard_delete_workspace", @@ -568,7 +615,10 @@ }, "/api/v1/workspaces/{workspace_name}/alerts": { "get": { - "tags": ["CodeGate API", "Workspaces"], + "tags": [ + "CodeGate API", + "Workspaces" + ], "summary": "Get Workspace Alerts", "description": "Get alerts for a workspace.", "operationId": "v1_get_workspace_alerts", @@ -620,7 +670,10 @@ }, "/api/v1/workspaces/{workspace_name}/messages": { "get": { - "tags": ["CodeGate API", "Workspaces"], + "tags": [ + "CodeGate API", + "Workspaces" + ], "summary": "Get Workspace Messages", "description": "Get messages for a workspace.", "operationId": "v1_get_workspace_messages", @@ -665,7 +718,10 @@ }, "/api/v1/workspaces/{workspace_name}/custom-instructions": { "get": { - "tags": ["CodeGate API", "Workspaces"], + "tags": [ + "CodeGate API", + "Workspaces" + ], "summary": "Get Workspace Custom Instructions", "description": "Get the custom instructions of a workspace.", "operationId": "v1_get_workspace_custom_instructions", @@ -704,7 +760,10 @@ } }, "put": { - "tags": ["CodeGate API", "Workspaces"], + "tags": [ + "CodeGate API", + "Workspaces" + ], "summary": "Set Workspace Custom Instructions", "operationId": "v1_set_workspace_custom_instructions", "parameters": [ @@ -745,7 +804,10 @@ } }, "delete": { - "tags": ["CodeGate API", "Workspaces"], + "tags": [ + "CodeGate API", + "Workspaces" + ], "summary": "Delete Workspace Custom Instructions", "operationId": "v1_delete_workspace_custom_instructions", "parameters": [ @@ -778,7 +840,11 @@ }, "/api/v1/workspaces/{workspace_name}/muxes": { "get": { - "tags": ["CodeGate API", "Workspaces", "Muxes"], + "tags": [ + "CodeGate API", + "Workspaces", + "Muxes" + ], "summary": "Get Workspace Muxes", "description": "Get the mux rules of a workspace.\n\nThe list is ordered in order of priority. That is, the first rule in the list\nhas the highest priority.", "operationId": "v1_get_workspace_muxes", @@ -821,7 +887,11 @@ } }, "put": { - "tags": ["CodeGate API", "Workspaces", "Muxes"], + "tags": [ + "CodeGate API", + "Workspaces", + "Muxes" + ], "summary": "Set Workspace Muxes", "description": "Set the mux rules of a workspace.", "operationId": "v1_set_workspace_muxes", @@ -869,7 +939,10 @@ }, "/api/v1/alerts_notification": { "get": { - "tags": ["CodeGate API", "Dashboard"], + "tags": [ + "CodeGate API", + "Dashboard" + ], "summary": "Stream Sse", "description": "Send alerts event", "operationId": "v1_stream_sse", @@ -887,7 +960,10 @@ }, "/api/v1/version": { "get": { - "tags": ["CodeGate API", "Dashboard"], + "tags": [ + "CodeGate API", + "Dashboard" + ], "summary": "Version Check", "operationId": "v1_version_check", "responses": { @@ -904,7 +980,11 @@ }, "/api/v1/workspaces/{workspace_name}/token-usage": { "get": { - "tags": ["CodeGate API", "Workspaces", "Token Usage"], + "tags": [ + "CodeGate API", + "Workspaces", + "Token Usage" + ], "summary": "Get Workspace Token Usage", "description": "Get the token usage of a workspace.", "operationId": "v1_get_workspace_token_usage", @@ -925,7 +1005,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TokenUsageAggregate" + "$ref": "#/components/schemas/TokenUsage" } } } @@ -954,7 +1034,9 @@ } }, "type": "object", - "required": ["name"], + "required": [ + "name" + ], "title": "ActivateWorkspaceRequest" }, "ActiveWorkspace": { @@ -972,7 +1054,11 @@ } }, "type": "object", - "required": ["name", "is_active", "last_updated"], + "required": [ + "name", + "is_active", + "last_updated" + ], "title": "ActiveWorkspace" }, "AlertConversation": { @@ -1059,7 +1145,11 @@ } }, "type": "object", - "required": ["message", "timestamp", "message_id"], + "required": [ + "message", + "timestamp", + "message_id" + ], "title": "ChatMessage", "description": "Represents a chat message." }, @@ -1100,7 +1190,11 @@ } }, "type": "object", - "required": ["code", "language", "filepath"], + "required": [ + "code", + "language", + "filepath" + ], "title": "CodeSnippet" }, "Conversation": { @@ -1135,10 +1229,10 @@ "format": "date-time", "title": "Conversation Timestamp" }, - "token_usage_agg": { + "token_usage": { "anyOf": [ { - "$ref": "#/components/schemas/TokenUsageAggregate" + "$ref": "#/components/schemas/TokenUsage" }, { "type": "null" @@ -1153,7 +1247,7 @@ "type", "chat_id", "conversation_timestamp", - "token_usage_agg" + "token_usage" ], "title": "Conversation", "description": "Represents a conversation." @@ -1177,7 +1271,9 @@ } }, "type": "object", - "required": ["name"], + "required": [ + "name" + ], "title": "CreateOrRenameWorkspaceRequest" }, "CustomInstructions": { @@ -1188,7 +1284,9 @@ } }, "type": "object", - "required": ["prompt"], + "required": [ + "prompt" + ], "title": "CustomInstructions" }, "HTTPValidationError": { @@ -1215,7 +1313,9 @@ } }, "type": "object", - "required": ["workspaces"], + "required": [ + "workspaces" + ], "title": "ListActiveWorkspacesResponse" }, "ListWorkspacesResponse": { @@ -1229,7 +1329,9 @@ } }, "type": "object", - "required": ["workspaces"], + "required": [ + "workspaces" + ], "title": "ListWorkspacesResponse" }, "ModelByProvider": { @@ -1244,13 +1346,19 @@ } }, "type": "object", - "required": ["name", "provider"], + "required": [ + "name", + "provider" + ], "title": "ModelByProvider", "description": "Represents a model supported by a provider.\n\nNote that these are auto-discovered by the provider." }, "MuxMatcherType": { "type": "string", - "enum": ["file_regex", "catch_all"], + "enum": [ + "file_regex", + "catch_all" + ], "title": "MuxMatcherType", "description": "Represents the different types of matchers we support." }, @@ -1280,13 +1388,22 @@ } }, "type": "object", - "required": ["provider", "model", "matcher_type", "matcher"], + "required": [ + "provider", + "model", + "matcher_type", + "matcher" + ], "title": "MuxRule", "description": "Represents a mux rule for a provider." }, "ProviderAuthType": { "type": "string", - "enum": ["none", "passthrough", "api_key"], + "enum": [ + "none", + "passthrough", + "api_key" + ], "title": "ProviderAuthType", "description": "Represents the different types of auth we support for providers." }, @@ -1317,13 +1434,23 @@ } }, "type": "object", - "required": ["id", "name", "provider_type", "endpoint", "auth_type"], + "required": [ + "id", + "name", + "provider_type", + "endpoint", + "auth_type" + ], "title": "ProviderEndpoint", "description": "Represents a provider's endpoint configuration. This\nallows us to persist the configuration for each provider,\nso we can use this for muxing messages." }, "ProviderType": { "type": "string", - "enum": ["openai", "anthropic", "vllm", "llamacpp", "ollama"], + "enum": [ + "openai", + "anthropic", + "vllm" + ], "title": "ProviderType", "description": "Represents the different types of providers we support." }, @@ -1344,58 +1471,41 @@ } }, "type": "object", - "required": ["question", "answer"], + "required": [ + "question", + "answer" + ], "title": "QuestionAnswer", "description": "Represents a question and answer pair." }, "QuestionType": { "type": "string", - "enum": ["chat", "fim"], + "enum": [ + "chat", + "fim" + ], "title": "QuestionType" }, "TokenUsage": { - "properties": { - "input_tokens": { - "type": "integer", - "title": "Input Tokens", - "default": 0 - }, - "output_tokens": { - "type": "integer", - "title": "Output Tokens", - "default": 0 - }, - "input_cost": { - "type": "number", - "title": "Input Cost", - "default": 0 - }, - "output_cost": { - "type": "number", - "title": "Output Cost", - "default": 0 - } - }, - "type": "object", - "title": "TokenUsage", - "description": "TokenUsage it's not a table, it's a model to represent the token usage.\nThe data is stored in the outputs table." - }, - "TokenUsageAggregate": { "properties": { "tokens_by_model": { - "additionalProperties": { + "items": { "$ref": "#/components/schemas/TokenUsageByModel" }, - "type": "object", + "type": "array", "title": "Tokens By Model" }, - "token_usage": { - "$ref": "#/components/schemas/TokenUsage" + "used_tokens": { + "type": "integer", + "title": "Used Tokens" } }, "type": "object", - "required": ["tokens_by_model", "token_usage"], - "title": "TokenUsageAggregate", + "required": [ + "tokens_by_model", + "used_tokens" + ], + "title": "TokenUsage", "description": "Represents the tokens used. Includes the information of the tokens used by model.\n`used_tokens` are the total tokens used in the `tokens_by_model` list." }, "TokenUsageByModel": { @@ -1407,12 +1517,17 @@ "type": "string", "title": "Model" }, - "token_usage": { - "$ref": "#/components/schemas/TokenUsage" + "used_tokens": { + "type": "integer", + "title": "Used Tokens" } }, "type": "object", - "required": ["provider_type", "model", "token_usage"], + "required": [ + "provider_type", + "model", + "used_tokens" + ], "title": "TokenUsageByModel", "description": "Represents the tokens used by a model." }, @@ -1442,7 +1557,11 @@ } }, "type": "object", - "required": ["loc", "msg", "type"], + "required": [ + "loc", + "msg", + "type" + ], "title": "ValidationError" }, "Workspace": { @@ -1457,7 +1576,10 @@ } }, "type": "object", - "required": ["name", "is_active"], + "required": [ + "name", + "is_active" + ], "title": "Workspace" } } diff --git a/src/features/workspace/components/workspace-model-overrides.tsx b/src/features/workspace/components/workspace-model-overrides.tsx index 616fb398..526264ca 100644 --- a/src/features/workspace/components/workspace-model-overrides.tsx +++ b/src/features/workspace/components/workspace-model-overrides.tsx @@ -15,6 +15,7 @@ import { twMerge } from "tailwind-merge"; import { useModelOverridesWorkspace } from "../hooks/use-model-overrides-workspace"; import { useMutationModelOverridesWorkspace } from "../hooks/use-mutation-model-overrides-workspace"; import { MuxMatcherType } from "@/api/generated"; +import { useModelsData } from "@/hooks/useModelsData"; export function WorkspaceModelOverrides({ className, @@ -27,6 +28,7 @@ export function WorkspaceModelOverrides({ }) { const { setOverrideItem, overrides } = useModelOverridesWorkspace(); const { mutateAsync } = useMutationModelOverridesWorkspace(); + const { data: models = [] } = useModelsData(); console.log(overrides); const handleSubmit = () => { @@ -68,18 +70,11 @@ export function WorkspaceModelOverrides({ onSelectionChange={(model) => setOverrideItem({ id: 0, model: model.toString() }) } - items={[ - { - textValue: "Chagpt o4mini", - id: "chat_gpt", - provider: "Openai", - }, - { - textValue: "Claude-3.5-sonnet-latest", - id: "claude_3.5", - provider: "Anthropic", - }, - ]} + items={models.map((model) => ({ + textValue: model.name, + id: model.name, + provider: model.provider, + }))} > diff --git a/src/hooks/useModelsData.ts b/src/hooks/useModelsData.ts new file mode 100644 index 00000000..3fd6d252 --- /dev/null +++ b/src/hooks/useModelsData.ts @@ -0,0 +1,21 @@ +import { useQuery } from "@tanstack/react-query"; +import { v1ListAllModelsForAllProvidersOptions } from "@/api/generated/@tanstack/react-query.gen"; +import { V1ListAllModelsForAllProvidersResponse } from "@/api/generated"; + +export const useModelsData = () => { + return useQuery({ + ...v1ListAllModelsForAllProvidersOptions(), + // eslint-disable-next-line @typescript-eslint/no-unused-vars + queryFn: async ({ queryKey, signal }) => { + const response: V1ListAllModelsForAllProvidersResponse = [ + { name: "claude-3.5", provider: "anthropic" }, + { name: "claude-3.6", provider: "anthropic" }, + { name: "claude-3.7", provider: "anthropic" }, + { name: "chatgpt-4o", provider: "openai" }, + { name: "chatgpt-4p", provider: "openai" }, + ]; + + return response; + }, + }); +}; From 548f25a9a28a2227bb565a02ea8b07c2298d6bd4 Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Mon, 27 Jan 2025 18:46:16 +0100 Subject: [PATCH 03/29] render multiple overrides --- .../components/workspace-model-overrides.tsx | 62 +++++++++++-------- .../hooks/use-model-overrides-workspace.ts | 9 ++- 2 files changed, 42 insertions(+), 29 deletions(-) diff --git a/src/features/workspace/components/workspace-model-overrides.tsx b/src/features/workspace/components/workspace-model-overrides.tsx index 526264ca..44601f82 100644 --- a/src/features/workspace/components/workspace-model-overrides.tsx +++ b/src/features/workspace/components/workspace-model-overrides.tsx @@ -41,6 +41,8 @@ export function WorkspaceModelOverrides({ }); }; + console.log({ models, overrides }); + return (
@@ -52,33 +54,39 @@ export function WorkspaceModelOverrides({ individual files, or repository. -
- setOverrideItem({ id: 0, matcher })} - > - - - - +
+ {overrides.map((override, index) => ( +
+ + setOverrideItem({ id: index, matcher }) + } + > + + + + +
+ ))}
diff --git a/src/features/workspace/hooks/use-model-overrides-workspace.ts b/src/features/workspace/hooks/use-model-overrides-workspace.ts index 0b9052c3..3a8b0615 100644 --- a/src/features/workspace/hooks/use-model-overrides-workspace.ts +++ b/src/features/workspace/hooks/use-model-overrides-workspace.ts @@ -18,10 +18,15 @@ type State = { export const useModelOverridesWorkspace = create((set, get) => ({ overrides: [ { - provider: "Anthropic", - model: "claude_3.5", + provider: "anthropic", + model: "claude-3.5", matcher: "", }, + { + provider: "anthropic", + model: "claude-3.7", + matcher: "hello", + }, ], setOverrides: () => {}, setOverrideItem: ({ id, model, matcher }) => { From e4d278d70b8d9c82739e0378854dfc3fa76df99e Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Mon, 27 Jan 2025 18:48:21 +0100 Subject: [PATCH 04/29] fix form submit --- .../workspace/components/workspace-model-overrides.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/features/workspace/components/workspace-model-overrides.tsx b/src/features/workspace/components/workspace-model-overrides.tsx index 44601f82..e4bff54b 100644 --- a/src/features/workspace/components/workspace-model-overrides.tsx +++ b/src/features/workspace/components/workspace-model-overrides.tsx @@ -16,6 +16,7 @@ import { useModelOverridesWorkspace } from "../hooks/use-model-overrides-workspa import { useMutationModelOverridesWorkspace } from "../hooks/use-mutation-model-overrides-workspace"; import { MuxMatcherType } from "@/api/generated"; import { useModelsData } from "@/hooks/useModelsData"; +import { FormEvent } from "react"; export function WorkspaceModelOverrides({ className, @@ -31,7 +32,8 @@ export function WorkspaceModelOverrides({ const { data: models = [] } = useModelsData(); console.log(overrides); - const handleSubmit = () => { + const handleSubmit = (event: FormEvent) => { + event.preventDefault(); mutateAsync({ path: { workspace_name: workspaceName }, body: overrides.map((item) => ({ From 8e379954dde3d31f9c5e76a017624d632a4a2e65 Mon Sep 17 00:00:00 2001 From: Giuseppe Scuglia Date: Tue, 28 Jan 2025 12:32:44 +0100 Subject: [PATCH 05/29] feat: add/remove model ovveride row --- .../components/workspace-model-overrides.tsx | 85 ++++++++++++------- .../hooks/use-model-overrides-workspace.ts | 14 +++ .../use-mutation-model-overrides-workspace.ts | 3 +- src/mocks/msw/handlers.ts | 16 +++- 4 files changed, 82 insertions(+), 36 deletions(-) diff --git a/src/features/workspace/components/workspace-model-overrides.tsx b/src/features/workspace/components/workspace-model-overrides.tsx index e4bff54b..a19fab6e 100644 --- a/src/features/workspace/components/workspace-model-overrides.tsx +++ b/src/features/workspace/components/workspace-model-overrides.tsx @@ -17,6 +17,7 @@ import { useMutationModelOverridesWorkspace } from "../hooks/use-mutation-model- import { MuxMatcherType } from "@/api/generated"; import { useModelsData } from "@/hooks/useModelsData"; import { FormEvent } from "react"; +import { Plus, Trash01 } from "@untitled-ui/icons-react"; export function WorkspaceModelOverrides({ className, @@ -27,7 +28,8 @@ export function WorkspaceModelOverrides({ workspaceName: string; isArchived: boolean | undefined; }) { - const { setOverrideItem, overrides } = useModelOverridesWorkspace(); + const { removeOverride, setOverrideItem, overrides, addOverride } = + useModelOverridesWorkspace(); const { mutateAsync } = useMutationModelOverridesWorkspace(); const { data: models = [] } = useModelsData(); @@ -48,7 +50,7 @@ export function WorkspaceModelOverrides({ return ( - +
Model Overrides @@ -56,42 +58,59 @@ export function WorkspaceModelOverrides({ individual files, or repository.
-
+
{overrides.map((override, index) => ( -
- - setOverrideItem({ id: index, matcher }) - } - > - - - - +
+
+ + setOverrideItem({ id: index, matcher }) + } + > + {index === 0 && } + + +
+
+ + {index !== 0 && ( + + )} +
))}
- + + + )} +
+
+ ); +} diff --git a/src/components/OverrideEditor/index.tsx b/src/components/OverrideEditor/index.tsx new file mode 100644 index 00000000..4d864f29 --- /dev/null +++ b/src/components/OverrideEditor/index.tsx @@ -0,0 +1,57 @@ +import { + DndContext, + closestCenter, + KeyboardSensor, + PointerSensor, + useSensor, + useSensors, +} from "@dnd-kit/core"; +import { + arrayMove, + SortableContext, + sortableKeyboardCoordinates, + verticalListSortingStrategy, +} from "@dnd-kit/sortable"; + +import { useModelOverridesWorkspace } from "@/features/workspace/hooks/use-model-overrides-workspace"; +import { SortableItem } from "./SortableItem"; + +export function OverrideEditor() { + const { overrides, setOverrides } = useModelOverridesWorkspace(); + const sensors = useSensors( + useSensor(PointerSensor), + useSensor(KeyboardSensor, { + coordinateGetter: sortableKeyboardCoordinates, + }), + ); + + function handleDragEnd(event) { + const { active, over } = event; + + if (active.id !== over.id) { + const oldIndex = overrides.indexOf(active.id); + const newIndex = overrides.indexOf(over.id); + + setOverrides(arrayMove(overrides, oldIndex, newIndex)); + } + } + + return ( +
+ + + {overrides.map((override, index) => ( + + ))} + + +
+ ); +} diff --git a/src/features/workspace/components/workspace-model-overrides.tsx b/src/features/workspace/components/workspace-model-overrides.tsx index a19fab6e..3e72e745 100644 --- a/src/features/workspace/components/workspace-model-overrides.tsx +++ b/src/features/workspace/components/workspace-model-overrides.tsx @@ -4,20 +4,16 @@ import { CardBody, CardFooter, Form, - Input, - Label, - Select, - SelectButton, Text, - TextField, } from "@stacklok/ui-kit"; import { twMerge } from "tailwind-merge"; -import { useModelOverridesWorkspace } from "../hooks/use-model-overrides-workspace"; import { useMutationModelOverridesWorkspace } from "../hooks/use-mutation-model-overrides-workspace"; import { MuxMatcherType } from "@/api/generated"; import { useModelsData } from "@/hooks/useModelsData"; import { FormEvent } from "react"; -import { Plus, Trash01 } from "@untitled-ui/icons-react"; +import { Plus } from "@untitled-ui/icons-react"; +import { useModelOverridesWorkspace } from "../hooks/use-model-overrides-workspace"; +import { OverrideEditor } from "@/components/OverrideEditor"; export function WorkspaceModelOverrides({ className, @@ -28,12 +24,10 @@ export function WorkspaceModelOverrides({ workspaceName: string; isArchived: boolean | undefined; }) { - const { removeOverride, setOverrideItem, overrides, addOverride } = - useModelOverridesWorkspace(); + const { overrides, addOverride } = useModelOverridesWorkspace(); const { mutateAsync } = useMutationModelOverridesWorkspace(); const { data: models = [] } = useModelsData(); - console.log(overrides); const handleSubmit = (event: FormEvent) => { event.preventDefault(); mutateAsync({ @@ -58,54 +52,7 @@ export function WorkspaceModelOverrides({ individual files, or repository.
-
- {overrides.map((override, index) => ( -
-
- - setOverrideItem({ id: index, matcher }) - } - > - {index === 0 && } - - -
-
- - {index !== 0 && ( - - )} -
-
- ))} -
+ - )} - - - ); -} diff --git a/src/components/OverrideEditor/index.tsx b/src/components/OverrideEditor/index.tsx deleted file mode 100644 index 82c21f70..00000000 --- a/src/components/OverrideEditor/index.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { useModelOverridesWorkspace } from "@/features/workspace/hooks/use-model-overrides-workspace"; -import { SortableItem } from "./SortableItem"; -import { Label } from "@stacklok/ui-kit"; -import { SortableArea } from "../SortableArea"; - -export function OverrideEditor() { - const { overrides, setOverrides } = useModelOverridesWorkspace(); - - return ( -
-
-
- -
-
- -
-
- -
- - {(override, index) => ( - - )} - -
-
- ); -} diff --git a/src/features/workspace/components/workspace-model-overrides.tsx b/src/features/workspace/components/workspace-model-overrides.tsx index 835d7c73..3977f0b3 100644 --- a/src/features/workspace/components/workspace-model-overrides.tsx +++ b/src/features/workspace/components/workspace-model-overrides.tsx @@ -4,6 +4,7 @@ import { CardBody, CardFooter, Form, + Label, Text, } from "@stacklok/ui-kit"; import { twMerge } from "tailwind-merge"; @@ -12,7 +13,70 @@ import { MuxMatcherType } from "@/api/generated"; import { FormEvent } from "react"; import { Plus } from "@untitled-ui/icons-react"; import { useModelOverridesWorkspace } from "../hooks/use-model-overrides-workspace"; -import { OverrideEditor } from "@/components/OverrideEditor"; +import { Input, Select, SelectButton, TextField } from "@stacklok/ui-kit"; +import { Trash01 } from "@untitled-ui/icons-react"; +import { OverrideRule } from "@/features/workspace/hooks/use-model-overrides-workspace"; +import { useModelsData } from "@/hooks/useModelsData"; + +import { SortableArea } from "@/components/SortableArea"; + +type SortableItemProps = { + index: number; + override: OverrideRule; +}; + +export function SortableItem({ override, index }: SortableItemProps) { + const { removeOverride, setOverrideItem } = useModelOverridesWorkspace(); + + const { data: models = [] } = useModelsData(); + + return ( +
+
+ event.preventDefault()} + value={override?.matcher ?? ""} + name="matcher" + onChange={(matcher) => { + setOverrideItem(override.id, { matcher }); + }} + > + + +
+
+ + {index !== 0 && ( + + )} +
+
+ ); +} export function WorkspaceModelOverrides({ className, @@ -23,7 +87,7 @@ export function WorkspaceModelOverrides({ workspaceName: string; isArchived: boolean | undefined; }) { - const { overrides, addOverride } = useModelOverridesWorkspace(); + const { overrides, addOverride, setOverrides } = useModelOverridesWorkspace(); const { mutateAsync } = useMutationModelOverridesWorkspace(); const handleSubmit = (event: FormEvent) => { @@ -48,7 +112,28 @@ export function WorkspaceModelOverrides({ individual files, or repository. - +
+
+
+ +
+
+ +
+
+ +
+ + {(override, index) => ( + + )} + +
+
diff --git a/src/features/workspace/components/workspace-model-overrides.tsx b/src/features/workspace/components/workspace-model-overrides.tsx index 1615c73f..626a2fc1 100644 --- a/src/features/workspace/components/workspace-model-overrides.tsx +++ b/src/features/workspace/components/workspace-model-overrides.tsx @@ -88,15 +88,18 @@ export function WorkspaceModelOverrides({ workspaceName: string; isArchived: boolean | undefined; }) { - const { overrides, addOverride, setOverrides } = useModelOverridesWorkspace(); + const { overrides, addOverride, setOverrides, resetOverrides } = + useModelOverridesWorkspace(); const { mutateAsync } = useMutationModelOverridesWorkspace(); const handleSubmit = (event: FormEvent) => { event.preventDefault(); mutateAsync({ path: { workspace_name: workspaceName }, - body: overrides.map((item) => ({ - ...item, + body: overrides.map(({ matcher, model, provider }) => ({ + matcher, + provider, + model, matcher_type: MuxMatcherType.FILE_REGEX, })), }); @@ -141,13 +144,22 @@ export function WorkspaceModelOverrides({ - +
+ + +
diff --git a/src/features/workspace/components/workspace-name.tsx b/src/features/workspace/components/workspace-name.tsx index 68cf8c7c..513ac47b 100644 --- a/src/features/workspace/components/workspace-name.tsx +++ b/src/features/workspace/components/workspace-name.tsx @@ -71,7 +71,6 @@ export function WorkspaceName({ isDisabled={isArchived || name === ""} isPending={isPending} type="submit" - variant="secondary" > Save diff --git a/src/features/workspace/hooks/use-model-overrides-workspace.ts b/src/features/workspace/hooks/use-model-overrides-workspace.ts index 0dd7277d..84e814a8 100644 --- a/src/features/workspace/hooks/use-model-overrides-workspace.ts +++ b/src/features/workspace/hooks/use-model-overrides-workspace.ts @@ -8,6 +8,7 @@ export type OverrideRule = Omit & { type State = { removeOverride: (index: number) => void; + resetOverrides: () => void; addOverride: () => void; setOverrides: (overrides: OverrideRule[]) => void; setOverrideItem: ( @@ -47,6 +48,11 @@ export const useModelOverridesWorkspace = create((set, get) => ({ ], }); }, + resetOverrides: () => { + set({ + overrides: [{ id: uuidv4(), matcher: "", model: "", provider: "" }], + }); + }, removeOverride: (overrideIndex: number) => { const { overrides } = get(); set({ From 693c689e4cd3d993ef25c83663567a30f38a36b8 Mon Sep 17 00:00:00 2001 From: Giuseppe Scuglia Date: Wed, 29 Jan 2025 13:22:58 +0100 Subject: [PATCH 26/29] refactor: configure preferred model --- package-lock.json | 64 ------- package.json | 3 - src/components/SortableArea.tsx | 93 ---------- .../workspace-model-overrides.test.tsx | 142 --------------- .../workspace-preferred-model.test.tsx | 52 ++++++ .../components/workspace-model-overrides.tsx | 167 ------------------ .../components/workspace-preferred-model.tsx | 95 ++++++++++ .../hooks/use-model-overrides-workspace.ts | 80 --------- ...use-mutation-preferred-model-workspace.ts} | 4 +- .../hooks/use-preferred-preferred-model.ts | 19 ++ src/routes/route-workspace.tsx | 4 +- 11 files changed, 170 insertions(+), 553 deletions(-) delete mode 100644 src/components/SortableArea.tsx delete mode 100644 src/features/workspace/components/__tests__/workspace-model-overrides.test.tsx create mode 100644 src/features/workspace/components/__tests__/workspace-preferred-model.test.tsx delete mode 100644 src/features/workspace/components/workspace-model-overrides.tsx create mode 100644 src/features/workspace/components/workspace-preferred-model.tsx delete mode 100644 src/features/workspace/hooks/use-model-overrides-workspace.ts rename src/features/workspace/hooks/{use-mutation-model-overrides-workspace.ts => use-mutation-preferred-model-workspace.ts} (80%) create mode 100644 src/features/workspace/hooks/use-preferred-preferred-model.ts diff --git a/package-lock.json b/package-lock.json index 408eb411..a159e96b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,6 @@ "name": "vite-project", "version": "0.9.0", "dependencies": { - "@dnd-kit/core": "^6.3.1", - "@dnd-kit/sortable": "^10.0.0", "@hey-api/client-fetch": "^0.7.1", "@monaco-editor/react": "^4.6.0", "@radix-ui/react-dialog": "^1.1.4", @@ -34,7 +32,6 @@ "tailwind-merge": "^2.5.5", "tailwind-variants": "^0.3.0", "tailwindcss-animate": "^1.0.7", - "uuid": "^11.0.5", "zustand": "^5.0.3" }, "devDependencies": { @@ -404,55 +401,6 @@ "tough-cookie": "^4.1.4" } }, - "node_modules/@dnd-kit/accessibility": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", - "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", - "dependencies": { - "tslib": "^2.0.0" - }, - "peerDependencies": { - "react": ">=16.8.0" - } - }, - "node_modules/@dnd-kit/core": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", - "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", - "dependencies": { - "@dnd-kit/accessibility": "^3.1.1", - "@dnd-kit/utilities": "^3.2.2", - "tslib": "^2.0.0" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/@dnd-kit/sortable": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz", - "integrity": "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==", - "dependencies": { - "@dnd-kit/utilities": "^3.2.2", - "tslib": "^2.0.0" - }, - "peerDependencies": { - "@dnd-kit/core": "^6.3.0", - "react": ">=16.8.0" - } - }, - "node_modules/@dnd-kit/utilities": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz", - "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==", - "dependencies": { - "tslib": "^2.0.0" - }, - "peerDependencies": { - "react": ">=16.8.0" - } - }, "node_modules/@esbuild/aix-ppc64": { "version": "0.24.2", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", @@ -11914,18 +11862,6 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, - "node_modules/uuid": { - "version": "11.0.5", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.5.tgz", - "integrity": "sha512-508e6IcKLrhxKdBbcA2b4KQZlLVp2+J5UwQ6F7Drckkc5N9ZJwFa4TgWtsww9UG8fGHbm6gbV19TdM5pQ4GaIA==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "bin": { - "uuid": "dist/esm/bin/uuid" - } - }, "node_modules/vfile": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", diff --git a/package.json b/package.json index fda14e1d..45af9315 100644 --- a/package.json +++ b/package.json @@ -20,8 +20,6 @@ "generate-icons": "npx @svgr/cli --typescript --no-dimensions --replace-attr-values '#2E323A=currentColor' --jsx-runtime automatic --out-dir ./src/components/icons/ -- icons" }, "dependencies": { - "@dnd-kit/core": "^6.3.1", - "@dnd-kit/sortable": "^10.0.0", "@hey-api/client-fetch": "^0.7.1", "@monaco-editor/react": "^4.6.0", "@radix-ui/react-dialog": "^1.1.4", @@ -46,7 +44,6 @@ "tailwind-merge": "^2.5.5", "tailwind-variants": "^0.3.0", "tailwindcss-animate": "^1.0.7", - "uuid": "^11.0.5", "zustand": "^5.0.3" }, "devDependencies": { diff --git a/src/components/SortableArea.tsx b/src/components/SortableArea.tsx deleted file mode 100644 index b9623668..00000000 --- a/src/components/SortableArea.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import { - closestCenter, - DndContext, - DragEndEvent, - KeyboardSensor, - PointerSensor, - UniqueIdentifier, - useSensor, - useSensors, -} from "@dnd-kit/core"; -import { - arrayMove, - SortableContext, - sortableKeyboardCoordinates, - verticalListSortingStrategy, -} from "@dnd-kit/sortable"; -import { CSS } from "@dnd-kit/utilities"; -import { useSortable } from "@dnd-kit/sortable"; -import { GripVertical } from "lucide-react"; - -type Props = { - children: (item: T, index: number) => React.ReactNode; - - setItems: (items: T[]) => void; - items: T[]; -}; - -function ItemWrapper({ - children, - id, -}: { - children: React.ReactNode; - id: UniqueIdentifier; -}) { - const { attributes, listeners, setNodeRef, transform, transition } = - useSortable({ id }); - const style = { - transform: CSS.Transform.toString(transform), - transition, - }; - return ( -
-
- -
-
{children}
-
- ); -} - -export function SortableArea({ - children, - setItems, - items, -}: Props) { - const sensors = useSensors( - useSensor(PointerSensor), - useSensor(KeyboardSensor, { - coordinateGetter: sortableKeyboardCoordinates, - }), - ); - - function handleDragEnd(event: DragEndEvent) { - const { active, over } = event; - - if (over == null) { - return; - } - - if (active.id !== over.id) { - const oldIndex = items.findIndex(({ id }) => id === active.id); - const newIndex = items.findIndex(({ id }) => id === over.id); - - setItems(arrayMove(items, oldIndex, newIndex)); - } - } - - return ( - - - {items.map((item, index) => ( - - {children(item, index)} - - ))} - - - ); -} diff --git a/src/features/workspace/components/__tests__/workspace-model-overrides.test.tsx b/src/features/workspace/components/__tests__/workspace-model-overrides.test.tsx deleted file mode 100644 index a236264b..00000000 --- a/src/features/workspace/components/__tests__/workspace-model-overrides.test.tsx +++ /dev/null @@ -1,142 +0,0 @@ -import { render } from "@/lib/test-utils"; -import { screen, waitFor } from "@testing-library/react"; -import { WorkspaceModelOverrides } from "../workspace-model-overrides"; -import userEvent from "@testing-library/user-event"; - -test("render model overrides", () => { - render( - , - ); - expect(screen.getByText(/model overrides/i)).toBeVisible(); - expect( - screen.getByText( - /route to different large language models based on file type, individual files, or repository./i, - ), - ).toBeVisible(); - expect( - screen.getAllByRole("textbox", { name: /filter by/i }).length, - ).toBeGreaterThanOrEqual(1); - expect( - screen.getAllByRole("button", { name: /preferred model/i }).length, - ).toBeGreaterThanOrEqual(1); - - expect( - screen.getAllByPlaceholderText(/eg file type, file name, or repository/i) - .length, - ).toBeGreaterThanOrEqual(1); -}); - -test("submit model overrides", async () => { - render( - , - ); - - const filterByEl = screen.getAllByRole("textbox", { name: /filter by/i })[0]; - await userEvent.type(filterByEl as HTMLFormElement, "*.tsx"); - - const modelEl = screen.getAllByRole("button", { - name: /preferred model/i, - })[0]; - await userEvent.click(modelEl as HTMLFormElement); - - await userEvent.click( - screen.getByRole("option", { - name: "claude-3.5", - }), - ); - - await userEvent.click(screen.getByRole("button", { name: /save/i })); - - await waitFor(() => { - expect( - screen.getByText( - /model overrides on fake-workspace successfully submitted!/i, - ), - ); - }); -}); - -test("submit additional model overrides", async () => { - render( - , - ); - - expect(screen.getAllByRole("textbox", { name: /filter by/i }).length).toEqual( - 2, - ); - - await userEvent.click( - screen.getByRole("button", { name: /additional filter/i }), - ); - - const textFields = await screen.findAllByRole("textbox", { - name: /filter by/i, - }); - expect(textFields.length).toEqual(3); - expect(textFields[2]).toBeDefined(); - await userEvent.type(textFields[2] as HTMLFormElement, "*.ts"); - - await userEvent.click( - screen.getAllByRole("button", { - name: /preferred model/i, - })[2] as HTMLFormElement, - ); - - await userEvent.click( - screen.getByRole("option", { - name: "claude-3.5", - }), - ); - - await userEvent.click(screen.getByRole("button", { name: /save/i })); - - await waitFor(() => { - expect( - screen.getByText( - /model overrides on fake-workspace successfully submitted!/i, - ), - ); - }); -}); - -test("remove the first model override and submit", async () => { - render( - , - ); - - const textFields = screen.getAllByRole("textbox", { name: /filter by/i }); - expect(textFields.length).toEqual(3); - await userEvent.click( - screen.getAllByRole("button", { - name: /remove override/i, - })[0] as HTMLFormElement, - ); - - await waitFor(() => - expect( - screen.getAllByRole("textbox", { name: /filter by/i }).length, - ).toEqual(2), - ); - - await userEvent.click(screen.getByRole("button", { name: /save/i })); - - await waitFor(() => { - expect( - screen.getByText( - /model overrides on fake-workspace successfully submitted!/i, - ), - ); - }); -}); diff --git a/src/features/workspace/components/__tests__/workspace-preferred-model.test.tsx b/src/features/workspace/components/__tests__/workspace-preferred-model.test.tsx new file mode 100644 index 00000000..57ad1b74 --- /dev/null +++ b/src/features/workspace/components/__tests__/workspace-preferred-model.test.tsx @@ -0,0 +1,52 @@ +import { render } from "@/lib/test-utils"; +import { screen, waitFor } from "@testing-library/react"; +import { WorkspacePreferredModel } from "../workspace-preferred-model"; +import userEvent from "@testing-library/user-event"; + +test("render model overrides", () => { + render( + , + ); + expect(screen.getByText(/preferred model/i)).toBeVisible(); + expect( + screen.getByText( + /select the model you would like to use in this workspace./i, + ), + ).toBeVisible(); + expect( + screen.getByRole("button", { name: /select the model/i }), + ).toBeVisible(); + expect(screen.getByRole("button", { name: /save/i })).toBeVisible(); +}); + +test("submit preferred model", async () => { + render( + , + ); + + await userEvent.click( + screen.getByRole("button", { name: /select the model/i }), + ); + + await userEvent.click( + screen.getByRole("option", { + name: "anthropic/claude-3.5", + }), + ); + + await userEvent.click(screen.getByRole("button", { name: /save/i })); + + await waitFor(() => { + expect( + screen.getByText( + /preferred model on fake-workspace successfully submitted!/i, + ), + ); + }); +}); diff --git a/src/features/workspace/components/workspace-model-overrides.tsx b/src/features/workspace/components/workspace-model-overrides.tsx deleted file mode 100644 index 626a2fc1..00000000 --- a/src/features/workspace/components/workspace-model-overrides.tsx +++ /dev/null @@ -1,167 +0,0 @@ -import { - Button, - Card, - CardBody, - CardFooter, - Form, - Label, - Text, -} from "@stacklok/ui-kit"; -import { twMerge } from "tailwind-merge"; -import { useMutationModelOverridesWorkspace } from "../hooks/use-mutation-model-overrides-workspace"; -import { MuxMatcherType } from "@/api/generated"; -import { FormEvent } from "react"; -import { Plus } from "@untitled-ui/icons-react"; -import { useModelOverridesWorkspace } from "../hooks/use-model-overrides-workspace"; -import { Input, Select, SelectButton, TextField } from "@stacklok/ui-kit"; -import { Trash01 } from "@untitled-ui/icons-react"; -import { OverrideRule } from "@/features/workspace/hooks/use-model-overrides-workspace"; -import { useModelsData } from "@/hooks/useModelsData"; - -import { SortableArea } from "@/components/SortableArea"; - -type SortableItemProps = { - index: number; - override: OverrideRule; -}; - -export function SortableItem({ override, index }: SortableItemProps) { - const { removeOverride, setOverrideItem } = useModelOverridesWorkspace(); - - const { data: models = [] } = useModelsData(); - - return ( -
-
- event.preventDefault()} - value={override?.matcher ?? ""} - name="matcher" - onChange={(matcher) => { - setOverrideItem(override.id, { matcher }); - }} - > - - -
-
- - {index !== 0 && ( - - )} -
-
- ); -} - -export function WorkspaceModelOverrides({ - className, - workspaceName, - isArchived, -}: { - className?: string; - workspaceName: string; - isArchived: boolean | undefined; -}) { - const { overrides, addOverride, setOverrides, resetOverrides } = - useModelOverridesWorkspace(); - const { mutateAsync } = useMutationModelOverridesWorkspace(); - - const handleSubmit = (event: FormEvent) => { - event.preventDefault(); - mutateAsync({ - path: { workspace_name: workspaceName }, - body: overrides.map(({ matcher, model, provider }) => ({ - matcher, - provider, - model, - matcher_type: MuxMatcherType.FILE_REGEX, - })), - }); - }; - - return ( -
- - -
- Model Overrides - - Route to different large language models based on file type, - individual files, or repository. - -
-
-
-
 
-
- -
-
- -
-
- -
- - {(override, index) => ( - - )} - -
-
-
- - -
- - -
-
-
-
- ); -} diff --git a/src/features/workspace/components/workspace-preferred-model.tsx b/src/features/workspace/components/workspace-preferred-model.tsx new file mode 100644 index 00000000..c36f317c --- /dev/null +++ b/src/features/workspace/components/workspace-preferred-model.tsx @@ -0,0 +1,95 @@ +import { + Button, + Card, + CardBody, + CardFooter, + Form, + Text, +} from "@stacklok/ui-kit"; +import { twMerge } from "tailwind-merge"; +import { useMutationPreferredModelWorkspace } from "../hooks/use-mutation-preferred-model-workspace"; +import { MuxMatcherType } from "@/api/generated"; +import { FormEvent } from "react"; +import { usePreferredModelWorkspace } from "../hooks/use-preferred-preferred-model"; +import { Select, SelectButton } from "@stacklok/ui-kit"; +import { useModelsData } from "@/hooks/useModelsData"; + +export function WorkspacePreferredModel({ + className, + workspaceName, + isArchived, +}: { + className?: string; + workspaceName: string; + isArchived: boolean | undefined; +}) { + const { preferredModel, setPreferredModel } = usePreferredModelWorkspace(); + const { mutateAsync } = useMutationPreferredModelWorkspace(); + const { data: providerModels = [] } = useModelsData(); + const { model, provider } = preferredModel; + + const handleSubmit = (event: FormEvent) => { + event.preventDefault(); + mutateAsync({ + path: { workspace_name: workspaceName }, + body: [ + { + matcher: "", + provider, + model, + matcher_type: MuxMatcherType.CATCH_ALL, + }, + ], + }); + }; + + return ( +
+ + +
+ Preferred Model + + Select the model you would like to use in this workspace. + +
+
+
+ +
+
+
+ + + +
+
+ ); +} diff --git a/src/features/workspace/hooks/use-model-overrides-workspace.ts b/src/features/workspace/hooks/use-model-overrides-workspace.ts deleted file mode 100644 index 84e814a8..00000000 --- a/src/features/workspace/hooks/use-model-overrides-workspace.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { MuxRule } from "@/api/generated"; -import { create } from "zustand"; -import { v4 as uuidv4 } from "uuid"; - -export type OverrideRule = Omit & { - id: string; -}; - -type State = { - removeOverride: (index: number) => void; - resetOverrides: () => void; - addOverride: () => void; - setOverrides: (overrides: OverrideRule[]) => void; - setOverrideItem: ( - idToChange: string, - { - model, - matcher, - }: { - matcher?: string; - model?: string; - }, - ) => void; - overrides: OverrideRule[]; -}; - -export const useModelOverridesWorkspace = create((set, get) => ({ - overrides: [ - { - id: uuidv4(), - provider: "anthropic", - model: "claude-3.5", - matcher: "", - }, - { - id: uuidv4(), - provider: "anthropic", - model: "claude-3.7", - matcher: "hello", - }, - ], - addOverride: () => { - const { overrides } = get(); - set({ - overrides: [ - ...overrides, - { id: uuidv4(), matcher: "", model: "", provider: "" }, - ], - }); - }, - resetOverrides: () => { - set({ - overrides: [{ id: uuidv4(), matcher: "", model: "", provider: "" }], - }); - }, - removeOverride: (overrideIndex: number) => { - const { overrides } = get(); - set({ - overrides: overrides.filter((_, index) => index !== overrideIndex), - }); - }, - setOverrides: (overrides: OverrideRule[]) => { - set({ overrides }); - }, - setOverrideItem: (idToChange, { model, matcher }) => { - const { overrides } = get(); - - set({ - overrides: overrides.map((item) => - item.id === idToChange - ? { - ...item, - model: model ?? item.model, - matcher: matcher ?? item.matcher, - } - : item, - ), - }); - }, -})); diff --git a/src/features/workspace/hooks/use-mutation-model-overrides-workspace.ts b/src/features/workspace/hooks/use-mutation-preferred-model-workspace.ts similarity index 80% rename from src/features/workspace/hooks/use-mutation-model-overrides-workspace.ts rename to src/features/workspace/hooks/use-mutation-preferred-model-workspace.ts index c6f0a738..cac96996 100644 --- a/src/features/workspace/hooks/use-mutation-model-overrides-workspace.ts +++ b/src/features/workspace/hooks/use-mutation-preferred-model-workspace.ts @@ -2,7 +2,7 @@ import { useToastMutation } from "@/hooks/use-toast-mutation"; import { useInvalidateWorkspaceQueries } from "./use-invalidate-workspace-queries"; import { v1SetWorkspaceMuxesMutation } from "@/api/generated/@tanstack/react-query.gen"; -export function useMutationModelOverridesWorkspace() { +export function useMutationPreferredModelWorkspace() { const invalidate = useInvalidateWorkspaceQueries(); return useToastMutation({ ...v1SetWorkspaceMuxesMutation(), @@ -10,6 +10,6 @@ export function useMutationModelOverridesWorkspace() { await invalidate(); }, successMsg: (variables) => - `Model overrides on ${variables.path.workspace_name} successfully submitted!`, + `Preferred model on ${variables.path.workspace_name} successfully submitted!`, }); } diff --git a/src/features/workspace/hooks/use-preferred-preferred-model.ts b/src/features/workspace/hooks/use-preferred-preferred-model.ts new file mode 100644 index 00000000..30a3570c --- /dev/null +++ b/src/features/workspace/hooks/use-preferred-preferred-model.ts @@ -0,0 +1,19 @@ +import { MuxRule } from "@/api/generated"; +import { create } from "zustand"; + +export type ModelRule = Omit & {}; + +type State = { + setPreferredModel: (model: ModelRule) => void; + preferredModel: ModelRule; +}; + +export const usePreferredModelWorkspace = create((set) => ({ + preferredModel: { + provider: "", + model: "", + }, + setPreferredModel: ({ model, provider }: ModelRule) => { + set({ preferredModel: { provider, model } }); + }, +})); diff --git a/src/routes/route-workspace.tsx b/src/routes/route-workspace.tsx index 76ab8ca3..b42ddd24 100644 --- a/src/routes/route-workspace.tsx +++ b/src/routes/route-workspace.tsx @@ -8,7 +8,7 @@ import { useParams } from "react-router-dom"; import { useArchivedWorkspaces } from "@/features/workspace/hooks/use-archived-workspaces"; import { useRestoreWorkspaceButton } from "@/features/workspace/hooks/use-restore-workspace-button"; import { WorkspaceCustomInstructions } from "@/features/workspace/components/workspace-custom-instructions"; -import { WorkspaceModelOverrides } from "@/features/workspace/components/workspace-model-overrides"; +import { WorkspacePreferredModel } from "@/features/workspace/components/workspace-preferred-model"; function WorkspaceArchivedBanner({ name }: { name: string }) { const restoreButtonProps = useRestoreWorkspaceButton({ workspaceName: name }); @@ -53,7 +53,7 @@ export function RouteWorkspace() { className="mb-4" workspaceName={name} /> - Date: Wed, 29 Jan 2025 13:25:10 +0100 Subject: [PATCH 27/29] hide preferred model section --- src/routes/route-workspace.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/route-workspace.tsx b/src/routes/route-workspace.tsx index b42ddd24..ba265320 100644 --- a/src/routes/route-workspace.tsx +++ b/src/routes/route-workspace.tsx @@ -54,7 +54,7 @@ export function RouteWorkspace() { workspaceName={name} /> From 0540f34b8a2478c1ce1fdc26823c2a1dc8b74d5c Mon Sep 17 00:00:00 2001 From: Giuseppe Scuglia Date: Wed, 29 Jan 2025 15:00:58 +0100 Subject: [PATCH 28/29] chore: update api --- .../generated/@tanstack/react-query.gen.ts | 24 +++++ src/api/generated/sdk.gen.ts | 20 ++++ src/api/generated/types.gen.ts | 25 ++++- src/api/openapi.json | 94 +++++++++++++++++-- .../components/workspace-preferred-model.tsx | 6 +- .../hooks/use-preferred-preferred-model.ts | 6 +- src/mocks/msw/handlers.ts | 4 +- 7 files changed, 160 insertions(+), 19 deletions(-) diff --git a/src/api/generated/@tanstack/react-query.gen.ts b/src/api/generated/@tanstack/react-query.gen.ts index 8fe97ff7..28e8ffbb 100644 --- a/src/api/generated/@tanstack/react-query.gen.ts +++ b/src/api/generated/@tanstack/react-query.gen.ts @@ -12,6 +12,7 @@ import { v1GetProviderEndpoint, v1UpdateProviderEndpoint, v1DeleteProviderEndpoint, + v1ConfigureAuthMaterial, v1ListWorkspaces, v1CreateWorkspace, v1ListActiveWorkspaces, @@ -44,6 +45,9 @@ import type { V1DeleteProviderEndpointData, V1DeleteProviderEndpointError, V1DeleteProviderEndpointResponse, + V1ConfigureAuthMaterialData, + V1ConfigureAuthMaterialError, + V1ConfigureAuthMaterialResponse, V1CreateWorkspaceData, V1CreateWorkspaceError, V1CreateWorkspaceResponse, @@ -293,6 +297,26 @@ export const v1DeleteProviderEndpointMutation = ( return mutationOptions; }; +export const v1ConfigureAuthMaterialMutation = ( + options?: Partial>, +) => { + const mutationOptions: UseMutationOptions< + V1ConfigureAuthMaterialResponse, + V1ConfigureAuthMaterialError, + OptionsLegacyParser + > = { + mutationFn: async (localOptions) => { + const { data } = await v1ConfigureAuthMaterial({ + ...options, + ...localOptions, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; +}; + export const v1ListWorkspacesQueryKey = (options?: OptionsLegacyParser) => [ createQueryKey("v1ListWorkspaces", options), ]; diff --git a/src/api/generated/sdk.gen.ts b/src/api/generated/sdk.gen.ts index b94d71b6..8069e38d 100644 --- a/src/api/generated/sdk.gen.ts +++ b/src/api/generated/sdk.gen.ts @@ -28,6 +28,9 @@ import type { V1DeleteProviderEndpointData, V1DeleteProviderEndpointError, V1DeleteProviderEndpointResponse, + V1ConfigureAuthMaterialData, + V1ConfigureAuthMaterialError, + V1ConfigureAuthMaterialResponse, V1ListWorkspacesError, V1ListWorkspacesResponse, V1CreateWorkspaceData, @@ -218,6 +221,23 @@ export const v1DeleteProviderEndpoint = ( }); }; +/** + * Configure Auth Material + * Configure auth material for a provider. + */ +export const v1ConfigureAuthMaterial = ( + options: OptionsLegacyParser, +) => { + return (options?.client ?? client).put< + V1ConfigureAuthMaterialResponse, + V1ConfigureAuthMaterialError, + ThrowOnError + >({ + ...options, + url: "/api/v1/provider-endpoints/{provider_id}/auth-material", + }); +}; + /** * List Workspaces * List all workspaces. diff --git a/src/api/generated/types.gen.ts b/src/api/generated/types.gen.ts index 24105bd0..ec403510 100644 --- a/src/api/generated/types.gen.ts +++ b/src/api/generated/types.gen.ts @@ -44,6 +44,14 @@ export type CodeSnippet = { libraries?: Array; }; +/** + * Represents a request to configure auth material for a provider. + */ +export type ConfigureAuthMaterial = { + auth_type: ProviderAuthType; + api_key?: string | null; +}; + /** * Represents a conversation. */ @@ -100,10 +108,10 @@ export enum MuxMatcherType { * Represents a mux rule for a provider. */ export type MuxRule = { - provider: string; + provider_id: string; model: string; matcher_type: MuxMatcherType; - matcher: string | null; + matcher?: string | null; }; /** @@ -126,7 +134,7 @@ export type ProviderEndpoint = { description?: string; provider_type: ProviderType; endpoint: string; - auth_type: ProviderAuthType; + auth_type?: ProviderAuthType | null; }; /** @@ -263,6 +271,17 @@ export type V1DeleteProviderEndpointResponse = unknown; export type V1DeleteProviderEndpointError = HTTPValidationError; +export type V1ConfigureAuthMaterialData = { + body: ConfigureAuthMaterial; + path: { + provider_id: string; + }; +}; + +export type V1ConfigureAuthMaterialResponse = void; + +export type V1ConfigureAuthMaterialError = HTTPValidationError; + export type V1ListWorkspacesResponse = ListWorkspacesResponse; export type V1ListWorkspacesError = unknown; diff --git a/src/api/openapi.json b/src/api/openapi.json index 829cd2d0..67d67ecb 100644 --- a/src/api/openapi.json +++ b/src/api/openapi.json @@ -336,6 +336,54 @@ } } }, + "/api/v1/provider-endpoints/{provider_id}/auth-material": { + "put": { + "tags": [ + "CodeGate API", + "Providers" + ], + "summary": "Configure Auth Material", + "description": "Configure auth material for a provider.", + "operationId": "v1_configure_auth_material", + "parameters": [ + { + "name": "provider_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Provider Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConfigureAuthMaterial" + } + } + } + }, + "responses": { + "204": { + "description": "Successful Response" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, "/api/v1/workspaces": { "get": { "tags": [ @@ -1201,6 +1249,30 @@ ], "title": "CodeSnippet" }, + "ConfigureAuthMaterial": { + "properties": { + "auth_type": { + "$ref": "#/components/schemas/ProviderAuthType" + }, + "api_key": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Api Key" + } + }, + "type": "object", + "required": [ + "auth_type" + ], + "title": "ConfigureAuthMaterial", + "description": "Represents a request to configure auth material for a provider." + }, "Conversation": { "properties": { "question_answers": { @@ -1373,9 +1445,9 @@ }, "MuxRule": { "properties": { - "provider": { + "provider_id": { "type": "string", - "title": "Provider" + "title": "Provider Id" }, "model": { "type": "string", @@ -1398,10 +1470,9 @@ }, "type": "object", "required": [ - "provider", + "provider_id", "model", - "matcher_type", - "matcher" + "matcher_type" ], "title": "MuxRule", "description": "Represents a mux rule for a provider." @@ -1447,15 +1518,22 @@ "title": "Endpoint" }, "auth_type": { - "$ref": "#/components/schemas/ProviderAuthType" + "anyOf": [ + { + "$ref": "#/components/schemas/ProviderAuthType" + }, + { + "type": "null" + } + ], + "default": "none" } }, "type": "object", "required": [ "name", "provider_type", - "endpoint", - "auth_type" + "endpoint" ], "title": "ProviderEndpoint", "description": "Represents a provider's endpoint configuration. This\nallows us to persist the configuration for each provider,\nso we can use this for muxing messages." diff --git a/src/features/workspace/components/workspace-preferred-model.tsx b/src/features/workspace/components/workspace-preferred-model.tsx index c36f317c..f3a1c75b 100644 --- a/src/features/workspace/components/workspace-preferred-model.tsx +++ b/src/features/workspace/components/workspace-preferred-model.tsx @@ -26,7 +26,7 @@ export function WorkspacePreferredModel({ const { preferredModel, setPreferredModel } = usePreferredModelWorkspace(); const { mutateAsync } = useMutationPreferredModelWorkspace(); const { data: providerModels = [] } = useModelsData(); - const { model, provider } = preferredModel; + const { model, provider_id } = preferredModel; const handleSubmit = (event: FormEvent) => { event.preventDefault(); @@ -35,7 +35,7 @@ export function WorkspacePreferredModel({ body: [ { matcher: "", - provider, + provider_id, model, matcher_type: MuxMatcherType.CATCH_ALL, }, @@ -69,7 +69,7 @@ export function WorkspacePreferredModel({ if (preferredModelProvider) { setPreferredModel({ model: preferredModelProvider.name, - provider: preferredModelProvider.provider_id, + provider_id: preferredModelProvider.provider_id, }); } }} diff --git a/src/features/workspace/hooks/use-preferred-preferred-model.ts b/src/features/workspace/hooks/use-preferred-preferred-model.ts index 30a3570c..555a2a23 100644 --- a/src/features/workspace/hooks/use-preferred-preferred-model.ts +++ b/src/features/workspace/hooks/use-preferred-preferred-model.ts @@ -10,10 +10,10 @@ type State = { export const usePreferredModelWorkspace = create((set) => ({ preferredModel: { - provider: "", + provider_id: "", model: "", }, - setPreferredModel: ({ model, provider }: ModelRule) => { - set({ preferredModel: { provider, model } }); + setPreferredModel: ({ model, provider_id }: ModelRule) => { + set({ preferredModel: { provider_id, model } }); }, })); diff --git a/src/mocks/msw/handlers.ts b/src/mocks/msw/handlers.ts index 20f348ab..be2bd963 100644 --- a/src/mocks/msw/handlers.ts +++ b/src/mocks/msw/handlers.ts @@ -96,13 +96,13 @@ export const handlers = [ http.get("*/api/v1/workspaces/:workspace_name/muxes", () => HttpResponse.json([ { - provider: "openai", + provider_id: "openai", model: "gpt-3.5-turbo", matcher_type: "file_regex", matcher: ".*\\.txt", }, { - provider: "anthropic", + provider_id: "anthropic", model: "davinci", matcher_type: "catch_all", }, From 88588e7496cb64c6d0da971c5e847f3a19f629ba Mon Sep 17 00:00:00 2001 From: Giuseppe Scuglia Date: Wed, 29 Jan 2025 15:13:11 +0100 Subject: [PATCH 29/29] refactor: notification message --- .../components/__tests__/workspace-preferred-model.test.tsx | 6 +----- .../hooks/use-mutation-preferred-model-workspace.ts | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/features/workspace/components/__tests__/workspace-preferred-model.test.tsx b/src/features/workspace/components/__tests__/workspace-preferred-model.test.tsx index 57ad1b74..e7ac8314 100644 --- a/src/features/workspace/components/__tests__/workspace-preferred-model.test.tsx +++ b/src/features/workspace/components/__tests__/workspace-preferred-model.test.tsx @@ -43,10 +43,6 @@ test("submit preferred model", async () => { await userEvent.click(screen.getByRole("button", { name: /save/i })); await waitFor(() => { - expect( - screen.getByText( - /preferred model on fake-workspace successfully submitted!/i, - ), - ); + expect(screen.getByText(/preferred model for fake-workspace updated/i)); }); }); diff --git a/src/features/workspace/hooks/use-mutation-preferred-model-workspace.ts b/src/features/workspace/hooks/use-mutation-preferred-model-workspace.ts index cac96996..3558b37b 100644 --- a/src/features/workspace/hooks/use-mutation-preferred-model-workspace.ts +++ b/src/features/workspace/hooks/use-mutation-preferred-model-workspace.ts @@ -10,6 +10,6 @@ export function useMutationPreferredModelWorkspace() { await invalidate(); }, successMsg: (variables) => - `Preferred model on ${variables.path.workspace_name} successfully submitted!`, + `Preferred model for ${variables.path.workspace_name} updated`, }); }