diff --git a/src/features/providers/components/table-providers.tsx b/src/features/providers/components/table-providers.tsx
index 8b8a886a..6f4de724 100644
--- a/src/features/providers/components/table-providers.tsx
+++ b/src/features/providers/components/table-providers.tsx
@@ -33,12 +33,19 @@ const COLUMNS: Column[] = [
id: "provider",
isRowHeader: true,
children: "Name & Description",
- width: "40%",
+ minWidth: 450,
+ maxWidth: 520,
},
- { id: "type", children: "Provider", width: "10%", className: "capitalize" },
- { id: "endpoint", children: "Endpoint", width: "20%", minWidth: 250 },
- { id: "auth", children: "Authentication", width: "20%" },
- { id: "configuration", alignment: "end", width: "10%", children: "" },
+ {
+ id: "type",
+ children: "Provider",
+ minWidth: 110,
+ maxWidth: 130,
+ className: "capitalize",
+ },
+ { id: "endpoint", children: "Endpoint", minWidth: 250 },
+ { id: "auth", children: "Authentication", minWidth: 140 },
+ { id: "configuration", alignment: "end", minWidth: 40, children: "" },
];
function CellRenderer({
diff --git a/src/features/workspace/components/__tests__/archive-workspace.test.tsx b/src/features/workspace/components/__tests__/archive-workspace.test.tsx
index 9a6b32de..e0fde0c3 100644
--- a/src/features/workspace/components/__tests__/archive-workspace.test.tsx
+++ b/src/features/workspace/components/__tests__/archive-workspace.test.tsx
@@ -8,7 +8,7 @@ import { mswEndpoint } from "@/test/msw-endpoint";
test("has correct buttons when not archived", async () => {
const { getByRole, queryByRole } = render(
-
,
+
);
expect(getByRole("button", { name: /archive/i })).toBeVisible();
@@ -17,7 +17,7 @@ test("has correct buttons when not archived", async () => {
test("has correct buttons when archived", async () => {
const { getByRole } = render(
-
,
+
);
expect(getByRole("button", { name: /restore/i })).toBeVisible();
expect(getByRole("button", { name: /permanently delete/i })).toBeVisible();
@@ -25,7 +25,7 @@ test("has correct buttons when archived", async () => {
test("can archive workspace", async () => {
const { getByText, getByRole } = render(
-
,
+
);
await userEvent.click(getByRole("button", { name: /archive/i }));
@@ -37,7 +37,7 @@ test("can archive workspace", async () => {
test("can restore archived workspace", async () => {
const { getByText, getByRole } = render(
-
,
+
);
await userEvent.click(getByRole("button", { name: /restore/i }));
@@ -49,7 +49,7 @@ test("can restore archived workspace", async () => {
test("can permanently delete archived workspace", async () => {
const { getByText, getByRole } = render(
-
,
+
);
await userEvent.click(getByRole("button", { name: /permanently delete/i }));
@@ -76,11 +76,11 @@ test("can't archive active workspace", async () => {
last_updated: new Date(Date.now()).toISOString(),
},
],
- }),
- ),
+ })
+ )
);
const { getByRole } = render(
-
,
+
);
await waitFor(() => {
@@ -91,7 +91,7 @@ test("can't archive active workspace", async () => {
test("can't archive default workspace", async () => {
const { getByRole } = render(
-
,
+
);
await waitFor(() => {
diff --git a/src/features/workspace/components/__tests__/workspace-custom-instructions.test.tsx b/src/features/workspace/components/__tests__/workspace-custom-instructions.test.tsx
index b9d83c1f..01ebbecc 100644
--- a/src/features/workspace/components/__tests__/workspace-custom-instructions.test.tsx
+++ b/src/features/workspace/components/__tests__/workspace-custom-instructions.test.tsx
@@ -22,7 +22,7 @@ vi.mock("@monaco-editor/react", () => {
const renderComponent = () =>
render(
-
,
+
);
test("can update custom instructions", async () => {
@@ -31,8 +31,8 @@ test("can update custom instructions", async () => {
mswEndpoint("/api/v1/workspaces/:workspace_name/custom-instructions"),
() => {
return HttpResponse.json({ prompt: "initial prompt from server" });
- },
- ),
+ }
+ )
);
const { getByRole, getByText } = renderComponent();
@@ -53,15 +53,15 @@ test("can update custom instructions", async () => {
mswEndpoint("/api/v1/workspaces/:workspace_name/custom-instructions"),
() => {
return HttpResponse.json({ prompt: "new prompt from test" });
- },
- ),
+ }
+ )
);
await userEvent.click(getByRole("button", { name: /Save/i }));
await waitFor(() => {
expect(
- getByText(/successfully updated custom instructions/i),
+ getByText(/successfully updated custom instructions/i)
).toBeVisible();
});
diff --git a/src/features/workspace/components/__tests__/workspace-name.test.tsx b/src/features/workspace/components/__tests__/workspace-name.test.tsx
index 84bfc93f..27e32f93 100644
--- a/src/features/workspace/components/__tests__/workspace-name.test.tsx
+++ b/src/features/workspace/components/__tests__/workspace-name.test.tsx
@@ -8,7 +8,7 @@ import { mswEndpoint } from "@/test/msw-endpoint";
test("can rename workspace", async () => {
const { getByRole, getByText } = render(
-
,
+
);
const input = getByRole("textbox", { name: /workspace name/i });
@@ -26,7 +26,7 @@ test("can rename workspace", async () => {
test("can't rename archived workspace", async () => {
const { getByRole } = render(
-
,
+
);
expect(getByRole("textbox", { name: /workspace name/i })).toBeDisabled();
@@ -44,11 +44,11 @@ test("can't rename active workspace", async () => {
last_updated: new Date(Date.now()).toISOString(),
},
],
- }),
- ),
+ })
+ )
);
const { getByRole } = render(
-
,
+
);
expect(getByRole("textbox", { name: /workspace name/i })).toBeDisabled();
@@ -57,7 +57,7 @@ test("can't rename active workspace", async () => {
test("can't rename default workspace", async () => {
const { getByRole } = render(
-
,
+
);
expect(getByRole("textbox", { name: /workspace name/i })).toBeDisabled();
diff --git a/src/features/workspace/components/archive-workspace.tsx b/src/features/workspace/components/archive-workspace.tsx
index aedb2fbf..a3a2045a 100644
--- a/src/features/workspace/components/archive-workspace.tsx
+++ b/src/features/workspace/components/archive-workspace.tsx
@@ -13,7 +13,7 @@ import { useArchiveWorkspaceButton } from "../hooks/use-archive-workspace-button
import { useConfirmHardDeleteWorkspace } from "../hooks/use-confirm-hard-delete-workspace";
import { useNavigate } from "react-router-dom";
import { hrefs } from "@/lib/hrefs";
-import { useActiveWorkspaceName } from "../hooks/use-active-workspace-name";
+import { useQueryActiveWorkspaceName } from "../../../hooks/use-query-active-workspace-name";
function getContextualText({
activeWorkspaceName,
@@ -34,7 +34,7 @@ function getContextualText({
// NOTE: You can't show a tooltip on a disabled button
// React Aria's recommended approach is https://spectrum.adobe.com/page/contextual-help/
function ContextualHelp({ workspaceName }: { workspaceName: string }) {
- const { data: activeWorkspaceName } = useActiveWorkspaceName();
+ const { data: activeWorkspaceName } = useQueryActiveWorkspaceName();
if (!activeWorkspaceName) return null;
const text = getContextualText({ activeWorkspaceName, workspaceName });
diff --git a/src/features/workspace/components/table-actions-workspaces.tsx b/src/features/workspace/components/table-actions-workspaces.tsx
index f36f46da..b651bd78 100644
--- a/src/features/workspace/components/table-actions-workspaces.tsx
+++ b/src/features/workspace/components/table-actions-workspaces.tsx
@@ -10,7 +10,7 @@ import {
import { useMutationArchiveWorkspace } from "@/features/workspace/hooks/use-mutation-archive-workspace";
import { useMutationRestoreWorkspace } from "../hooks/use-mutation-restore-workspace";
import { useMutationHardDeleteWorkspace } from "../hooks/use-mutation-hard-delete-workspace";
-import { useMutationActivateWorkspace } from "../hooks/use-mutation-activate-workspace";
+import { useMutationActivateWorkspace } from "../../../hooks/use-mutation-activate-workspace";
import { useConfirmHardDeleteWorkspace } from "../hooks/use-confirm-hard-delete-workspace";
import { hrefs } from "@/lib/hrefs";
import {
diff --git a/src/features/workspace/components/table-workspaces.tsx b/src/features/workspace/components/table-workspaces.tsx
index 46187e8c..c4a1fac2 100644
--- a/src/features/workspace/components/table-workspaces.tsx
+++ b/src/features/workspace/components/table-workspaces.tsx
@@ -1,5 +1,7 @@
import {
Badge,
+ Card,
+ CardBody,
Cell,
Column,
Row,
@@ -8,8 +10,8 @@ import {
TableHeader,
} from "@stacklok/ui-kit";
-import { useListAllWorkspaces } from "../hooks/use-query-list-all-workspaces";
-import { useActiveWorkspaceName } from "../hooks/use-active-workspace-name";
+import { useListAllWorkspaces } from "../../../hooks/use-query-list-all-workspaces";
+import { useQueryActiveWorkspaceName } from "../../../hooks/use-query-active-workspace-name";
import { TableActionsWorkspaces } from "./table-actions-workspaces";
import { hrefs } from "@/lib/hrefs";
@@ -24,7 +26,7 @@ function CellName({
}) {
if (isArchived)
return (
-
+
{name}
@@ -35,7 +37,7 @@ function CellName({
if (isActive)
return (
-
+
{name}
@@ -49,35 +51,42 @@ function CellName({
export function TableWorkspaces() {
const { data: workspaces } = useListAllWorkspaces();
- const { data: activeWorkspaceName } = useActiveWorkspaceName();
+ const { data: activeWorkspaceName } = useQueryActiveWorkspaceName();
return (
-
-
-
-
- Name
-
-
-
-
-
- {workspaces.map((workspace) => (
-
-
-
-
- |
+
+
+
+
+
+
+ Name
+
+
+
- ))}
-
-
+
+ {workspaces.map((workspace) => (
+
+
+
+
+ |
+
+ ))}
+
+
+
+
);
}
diff --git a/src/features/workspace/components/workspace-heading.tsx b/src/features/workspace/components/workspace-heading.tsx
deleted file mode 100644
index 8daaf93a..00000000
--- a/src/features/workspace/components/workspace-heading.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import { Heading } from "@stacklok/ui-kit";
-import React from "react";
-
-export function WorkspaceHeading({
- title,
- children,
-}: {
- title: React.ReactNode;
- children?: React.ReactNode;
-}) {
- return (
-
- {title}
- {children}
-
- );
-}
diff --git a/src/features/workspace/components/workspace-preferred-model.tsx b/src/features/workspace/components/workspace-preferred-model.tsx
index 60ba4331..d0dda4d4 100644
--- a/src/features/workspace/components/workspace-preferred-model.tsx
+++ b/src/features/workspace/components/workspace-preferred-model.tsx
@@ -15,7 +15,7 @@ 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/use-models-data";
+import { useQueryListAllModelsForAllProviders } from "@/hooks/use-query-list-all-models-for-all-providers";
function MissingProviderBanner() {
return (
@@ -42,7 +42,7 @@ export function WorkspacePreferredModel({
const { preferredModel, setPreferredModel, isPending } =
usePreferredModelWorkspace(workspaceName);
const { mutateAsync } = useMutationPreferredModelWorkspace();
- const { data: providerModels = [] } = useModelsData();
+ const { data: providerModels = [] } = useQueryListAllModelsForAllProviders();
const { model, provider_id } = preferredModel;
const isModelsEmpty = !isPending && providerModels.length === 0;
diff --git a/src/features/workspace/hooks/use-archive-workspace-button.tsx b/src/features/workspace/hooks/use-archive-workspace-button.tsx
index e6782ed4..42085b3a 100644
--- a/src/features/workspace/hooks/use-archive-workspace-button.tsx
+++ b/src/features/workspace/hooks/use-archive-workspace-button.tsx
@@ -1,14 +1,14 @@
import { Button } from "@stacklok/ui-kit";
import { ComponentProps } from "react";
import { useMutationArchiveWorkspace } from "@/features/workspace/hooks/use-mutation-archive-workspace";
-import { useActiveWorkspaceName } from "./use-active-workspace-name";
+import { useQueryActiveWorkspaceName } from "../../../hooks/use-query-active-workspace-name";
export function useArchiveWorkspaceButton({
workspaceName,
}: {
workspaceName: string;
}): ComponentProps {
- const { data: activeWorkspaceName } = useActiveWorkspaceName();
+ const { data: activeWorkspaceName } = useQueryActiveWorkspaceName();
const { mutateAsync, isPending } = useMutationArchiveWorkspace();
return {
diff --git a/src/features/workspace/hooks/use-mutation-archive-workspace.ts b/src/features/workspace/hooks/use-mutation-archive-workspace.ts
index 7dc0267e..954211f6 100644
--- a/src/features/workspace/hooks/use-mutation-archive-workspace.ts
+++ b/src/features/workspace/hooks/use-mutation-archive-workspace.ts
@@ -10,12 +10,12 @@ import {
V1ListArchivedWorkspacesResponse,
V1ListWorkspacesResponse,
} from "@/api/generated";
-import { useActiveWorkspaceName } from "./use-active-workspace-name";
+import { useQueryActiveWorkspaceName } from "../../../hooks/use-query-active-workspace-name";
export function useMutationArchiveWorkspace() {
const queryClient = useQueryClient();
const invalidate = useInvalidateWorkspaceQueries();
- const { data: activeWorkspaceName } = useActiveWorkspaceName();
+ const { data: activeWorkspaceName } = useQueryActiveWorkspaceName();
return useToastMutation({
...v1DeleteWorkspaceMutation(),
diff --git a/src/features/workspace/hooks/use-preferred-preferred-model.ts b/src/features/workspace/hooks/use-preferred-preferred-model.ts
index 78af9e21..4d917084 100644
--- a/src/features/workspace/hooks/use-preferred-preferred-model.ts
+++ b/src/features/workspace/hooks/use-preferred-preferred-model.ts
@@ -3,12 +3,12 @@ import { v1GetWorkspaceMuxesOptions } from "@/api/generated/@tanstack/react-quer
import { useQuery } from "@tanstack/react-query";
import { useEffect, useMemo, useState } from "react";
+type ModelRule = Omit & {};
+
const DEFAULT_STATE = {
provider_id: "",
model: "",
-};
-
-export type ModelRule = Omit & {};
+} as const satisfies ModelRule;
const usePreferredModel = (options: {
path: {
@@ -28,7 +28,7 @@ export const usePreferredModelWorkspace = (workspaceName: string) => {
() => ({
path: { workspace_name: workspaceName },
}),
- [workspaceName],
+ [workspaceName]
);
const { data, isPending } = usePreferredModel(options);
diff --git a/src/hooks/use-confirm.tsx b/src/hooks/use-confirm.tsx
index dc1305cc..207637a0 100644
--- a/src/hooks/use-confirm.tsx
+++ b/src/hooks/use-confirm.tsx
@@ -1,25 +1,8 @@
"use client";
import { ConfirmContext } from "@/context/confirm-context";
-import type { ReactNode } from "react";
import { useContext } from "react";
-type Buttons = {
- yes: ReactNode;
- no: ReactNode;
-};
-
-type Config = {
- buttons: Buttons;
- title?: ReactNode;
- isDestructive?: boolean;
-};
-
-export type ConfirmFunction = (
- message: ReactNode,
- config: Config,
-) => Promise;
-
export const useConfirm = () => {
const context = useContext(ConfirmContext);
if (!context) {
diff --git a/src/hooks/use-mobile.tsx b/src/hooks/use-mobile.tsx
deleted file mode 100644
index 3799a13d..00000000
--- a/src/hooks/use-mobile.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import * as React from "react"
-
-const MOBILE_BREAKPOINT = 1024
-
-export function useIsMobile() {
- const [isMobile, setIsMobile] = React.useState(undefined)
-
- React.useEffect(() => {
- const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
- const onChange = () => {
- setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
- }
- mql.addEventListener("change", onChange)
- setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
- return () => mql.removeEventListener("change", onChange)
- }, [])
-
- return !!isMobile
-}
diff --git a/src/features/workspace/hooks/use-mutation-activate-workspace.ts b/src/hooks/use-mutation-activate-workspace.ts
similarity index 100%
rename from src/features/workspace/hooks/use-mutation-activate-workspace.ts
rename to src/hooks/use-mutation-activate-workspace.ts
diff --git a/src/features/workspace/hooks/use-active-workspace-name.ts b/src/hooks/use-query-active-workspace-name.ts
similarity index 66%
rename from src/features/workspace/hooks/use-active-workspace-name.ts
rename to src/hooks/use-query-active-workspace-name.ts
index 85eddfd4..21e78885 100644
--- a/src/features/workspace/hooks/use-active-workspace-name.ts
+++ b/src/hooks/use-query-active-workspace-name.ts
@@ -1,5 +1,5 @@
import { ListActiveWorkspacesResponse } from "@/api/generated";
-import { useActiveWorkspaces } from "./use-active-workspaces";
+import { useQueryListActiveWorkspaces } from "./use-query-list-active-workspaces";
// NOTE: This needs to be a stable function reference to enable memo-isation of
// the select operation on each React re-render.
@@ -7,8 +7,8 @@ function select(data: ListActiveWorkspacesResponse | undefined): string | null {
return data?.workspaces?.[0]?.name ?? null;
}
-export function useActiveWorkspaceName() {
- return useActiveWorkspaces({
+export function useQueryActiveWorkspaceName() {
+ return useQueryListActiveWorkspaces({
select,
});
}
diff --git a/src/hooks/use-query-get-workspace-messages.ts b/src/hooks/use-query-get-workspace-messages.ts
index fbb7e2be..2e7de014 100644
--- a/src/hooks/use-query-get-workspace-messages.ts
+++ b/src/hooks/use-query-get-workspace-messages.ts
@@ -1,29 +1,30 @@
import { useQuery } from "@tanstack/react-query";
import {
- Conversation,
V1GetWorkspaceMessagesResponse,
V1GetWorkspaceMessagesData,
} from "@/api/generated";
import { v1GetWorkspaceMessagesOptions } from "@/api/generated/@tanstack/react-query.gen";
-import { useActiveWorkspaceName } from "@/features/workspace/hooks/use-active-workspace-name";
+import { useQueryActiveWorkspaceName } from "@/hooks/use-query-active-workspace-name";
import { getQueryCacheConfig } from "@/lib/react-query-utils";
+import { useMemo } from "react";
-// NOTE: This needs to be a stable function reference to enable memo-isation of
-// the select operation on each React re-render.
-function select(data: V1GetWorkspaceMessagesResponse): Conversation[] {
- return data.filter((prompt) =>
- prompt.question_answers?.every((item) => item.answer && item.question),
- );
-}
-
-export const useQueryGetWorkspaceMessages = () => {
- const { data: activeWorkspaceName } = useActiveWorkspaceName();
+export const useQueryGetWorkspaceMessages = <
+ T = V1GetWorkspaceMessagesResponse,
+>({
+ select,
+}: {
+ select?: (data: V1GetWorkspaceMessagesResponse) => T;
+} = {}) => {
+ const { data: activeWorkspaceName } = useQueryActiveWorkspaceName();
- const options: V1GetWorkspaceMessagesData = {
- path: {
- workspace_name: activeWorkspaceName ?? "default",
- },
- };
+ const options: V1GetWorkspaceMessagesData = useMemo(
+ () => ({
+ path: {
+ workspace_name: activeWorkspaceName ?? "default",
+ },
+ }),
+ [activeWorkspaceName],
+ );
return useQuery({
...v1GetWorkspaceMessagesOptions(options),
diff --git a/src/features/workspace/hooks/use-active-workspaces.ts b/src/hooks/use-query-list-active-workspaces.ts
similarity index 84%
rename from src/features/workspace/hooks/use-active-workspaces.ts
rename to src/hooks/use-query-list-active-workspaces.ts
index 9996d3f5..4aa87c64 100644
--- a/src/features/workspace/hooks/use-active-workspaces.ts
+++ b/src/hooks/use-query-list-active-workspaces.ts
@@ -2,7 +2,7 @@ import { ListActiveWorkspacesResponse } from "@/api/generated";
import { v1ListActiveWorkspacesOptions } from "@/api/generated/@tanstack/react-query.gen";
import { useQuery } from "@tanstack/react-query";
-export function useActiveWorkspaces({
+export function useQueryListActiveWorkspaces({
select,
}: {
select?: (data?: ListActiveWorkspacesResponse) => T;
diff --git a/src/hooks/use-models-data.ts b/src/hooks/use-query-list-all-models-for-all-providers.ts
similarity index 79%
rename from src/hooks/use-models-data.ts
rename to src/hooks/use-query-list-all-models-for-all-providers.ts
index b9fc280b..5b731708 100644
--- a/src/hooks/use-models-data.ts
+++ b/src/hooks/use-query-list-all-models-for-all-providers.ts
@@ -1,7 +1,7 @@
import { useQuery } from "@tanstack/react-query";
import { v1ListAllModelsForAllProvidersOptions } from "@/api/generated/@tanstack/react-query.gen";
-export const useModelsData = () => {
+export const useQueryListAllModelsForAllProviders = () => {
return useQuery({
...v1ListAllModelsForAllProvidersOptions(),
});
diff --git a/src/features/workspace/hooks/use-query-list-all-workspaces.ts b/src/hooks/use-query-list-all-workspaces.ts
similarity index 100%
rename from src/features/workspace/hooks/use-query-list-all-workspaces.ts
rename to src/hooks/use-query-list-all-workspaces.ts
diff --git a/src/features/workspace/hooks/use-list-workspaces.ts b/src/hooks/use-query-list-workspaces.ts
similarity index 85%
rename from src/features/workspace/hooks/use-list-workspaces.ts
rename to src/hooks/use-query-list-workspaces.ts
index 03afcf01..5c78c0b8 100644
--- a/src/features/workspace/hooks/use-list-workspaces.ts
+++ b/src/hooks/use-query-list-workspaces.ts
@@ -2,7 +2,7 @@ import { useQuery } from "@tanstack/react-query";
import { v1ListWorkspacesOptions } from "@/api/generated/@tanstack/react-query.gen";
import { V1ListWorkspacesResponse } from "@/api/generated";
-export function useListWorkspaces({
+export function useQueryListWorkspaces({
select,
}: {
select?: (data: V1ListWorkspacesResponse) => T;
diff --git a/src/hooks/useCurrentPromptStore.ts b/src/hooks/useCurrentPromptStore.ts
deleted file mode 100644
index 28b7004f..00000000
--- a/src/hooks/useCurrentPromptStore.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { create } from "zustand";
-
-type CurrentPromptState = {
- currentPromptId: string;
- setCurrentPromptId: (id: string) => void;
-};
-
-export const useCurrentPromptStore = create((set) => ({
- currentPromptId: "",
- setCurrentPromptId: (id: string) => set({ currentPromptId: id }),
-}));
diff --git a/src/index.css b/src/index.css
index 2ba4db1c..20998261 100644
--- a/src/index.css
+++ b/src/index.css
@@ -4,9 +4,11 @@
html,
body {
- height: 100%;
- margin: 0;
- overflow: hidden;
+}
+
+html {
+ scrollbar-gutter: stable;
+ overflow-x: hidden;
}
:root {
diff --git a/src/lib/currency.ts b/src/lib/currency.ts
index 70a83d24..accacdb3 100644
--- a/src/lib/currency.ts
+++ b/src/lib/currency.ts
@@ -7,7 +7,7 @@ type FormatCurrencyOptions = {
to_minor?: boolean;
};
-export const getCurrencyFormatOptions = (currency: Currency) => {
+const getCurrencyFormatOptions = (currency: Currency) => {
return new Intl.NumberFormat(undefined, {
currency: currency,
currencyDisplay: "code",
@@ -70,10 +70,3 @@ export function convertCurrencyFromMinor(
): number {
return amount / 10 ** getDigits(currency);
}
-
-export function formatCurrencyWithTrailingZeroes(
- number: number,
- currency: Currency,
-) {
- return number.toFixed(getDigits(currency));
-}
diff --git a/src/lib/format-time.ts b/src/lib/format-time.ts
new file mode 100644
index 00000000..c6c3882d
--- /dev/null
+++ b/src/lib/format-time.ts
@@ -0,0 +1,23 @@
+import { formatDistanceToNow } from "date-fns";
+
+type Format = "relative" | "absolute";
+
+export const formatTime = (
+ date: Date,
+ options: {
+ format: Format;
+ } = {
+ format: "relative",
+ },
+) => {
+ switch (options.format) {
+ case "absolute":
+ return date.toLocaleString();
+ case "relative":
+ return formatDistanceToNow(date, {
+ addSuffix: true,
+ });
+ default:
+ return options.format satisfies never;
+ }
+};
diff --git a/src/features/alerts/lib/is-alert-critical.ts b/src/lib/is-alert-critical.ts
similarity index 100%
rename from src/features/alerts/lib/is-alert-critical.ts
rename to src/lib/is-alert-critical.ts
diff --git a/src/lib/is-alert-malicious.ts b/src/lib/is-alert-malicious.ts
new file mode 100644
index 00000000..4a458dba
--- /dev/null
+++ b/src/lib/is-alert-malicious.ts
@@ -0,0 +1,17 @@
+import { Alert, AlertConversation, Conversation } from "@/api/generated";
+
+export function isConversationWithMaliciousAlerts(
+ conversation: Conversation | null,
+): boolean {
+ return conversation?.alerts?.some(isAlertMalicious) ?? false;
+}
+
+export function isAlertMalicious(alert: Alert | AlertConversation | null) {
+ return (
+ alert?.trigger_category === "critical" &&
+ alert.trigger_string !== null &&
+ typeof alert.trigger_string === "object" &&
+ "status" in alert.trigger_string &&
+ alert.trigger_string.status === "malicious"
+ );
+}
diff --git a/src/lib/is-alert-secret.ts b/src/lib/is-alert-secret.ts
new file mode 100644
index 00000000..36d825d6
--- /dev/null
+++ b/src/lib/is-alert-secret.ts
@@ -0,0 +1,14 @@
+import { Alert, AlertConversation, Conversation } from "@/api/generated";
+
+export function isConversationWithSecretAlerts(
+ conversation: Conversation | null,
+): boolean {
+ return conversation?.alerts?.some(isAlertSecret) ?? false;
+}
+
+export function isAlertSecret(alert: Alert | AlertConversation | null) {
+ return (
+ alert?.trigger_category === "critical" &&
+ alert.trigger_type === "codegate-secrets"
+ );
+}
diff --git a/src/lib/test-utils.tsx b/src/lib/test-utils.tsx
index 8dc22683..cf3e1394 100644
--- a/src/lib/test-utils.tsx
+++ b/src/lib/test-utils.tsx
@@ -1,4 +1,3 @@
-import { SidebarProvider } from "@/components/ui/sidebar";
import { ConfirmProvider } from "@/context/confirm-context";
import { DarkModeProvider, Toaster } from "@stacklok/ui-kit";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
@@ -28,6 +27,9 @@ export const TestQueryClientProvider = ({
new QueryClient({
defaultOptions: {
queries: {
+ refetchOnMount: true,
+ refetchOnReconnect: true,
+ refetchOnWindowFocus: true,
gcTime: 0,
staleTime: 0,
},
@@ -52,10 +54,7 @@ const renderWithProviders = (
- {children}}
- />
+
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
index 976388a4..64df408c 100644
--- a/src/lib/utils.ts
+++ b/src/lib/utils.ts
@@ -1,12 +1,5 @@
-import { AlertConversation, Conversation } from "@/api/generated/types.gen";
-import { isAlertSecret } from "@/features/alerts/lib/is-alert-secret";
-import { isAlertMalicious } from "@/features/alerts/lib/is-alert-malicious";
-import { format, isToday, isYesterday } from "date-fns";
+import { format } from "date-fns";
-const ONE_DAY_MS = 24 * 60 * 60 * 1000;
-const SEVEN_DAYS_MS = 7 * ONE_DAY_MS;
-const TEEN_DAYS_MS = 14 * ONE_DAY_MS;
-const THTY_DAYS_MS = 30 * ONE_DAY_MS;
const FILEPATH_REGEX = /(?:---FILEPATH|Path:|\/\/\s*filepath:)\s*([^\s]+)/g;
const COMPARE_CODE_REGEX = /Compare this snippet[^:]*:/g;
@@ -50,52 +43,6 @@ export function parsingPromptText(message: string, timestamp: string) {
}
}
-function getGroup(differenceInMs: number, promptDate: Date): string {
- if (isToday(promptDate)) {
- return "Today";
- }
- if (isYesterday(promptDate)) {
- return "Yesterday";
- }
- if (differenceInMs <= SEVEN_DAYS_MS) {
- return "Previous 7 days";
- }
- if (differenceInMs <= TEEN_DAYS_MS) {
- return "Previous 14 days";
- }
- if (differenceInMs <= THTY_DAYS_MS) {
- return "Previous 30 days";
- }
- return "Beyond 30 days";
-}
-
-export function groupPromptsByRelativeDate(prompts: Conversation[]) {
- const promptsSorted = prompts.sort(
- (a, b) =>
- new Date(b.conversation_timestamp).getTime() -
- new Date(a.conversation_timestamp).getTime()
- );
-
- const grouped = promptsSorted.reduce(
- (groups, prompt) => {
- const promptDate = new Date(prompt.conversation_timestamp);
- const now = new Date();
- const differenceInMs = now.getTime() - promptDate.getTime();
- const group = getGroup(differenceInMs, promptDate);
-
- if (!groups[group]) {
- groups[group] = [];
- }
-
- (groups[group] ?? []).push(prompt);
- return groups;
- },
- {} as Record
- );
-
- return grouped;
-}
-
export function sanitizeQuestionPrompt({
question,
answer,
@@ -123,17 +70,3 @@ export function sanitizeQuestionPrompt({
return question;
}
}
-
-export function getIssueDetectedType(
- alert: AlertConversation
-): "malicious_package" | "leaked_secret" | null {
- if (isAlertMalicious(alert)) return "malicious_package";
- if (isAlertSecret(alert)) return "leaked_secret";
-
- return null;
-}
-
-export function capitalize(text: string) {
- const [first, ...rest] = text;
- return first ? first.toUpperCase() + rest.join("") : text;
-}
diff --git a/src/main.tsx b/src/main.tsx
index 7398fed0..0628ebc3 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -3,7 +3,7 @@ import { createRoot } from "react-dom/client";
import "./index.css";
import "@stacklok/ui-kit/style";
import App from "./App.tsx";
-import { SidebarProvider } from "./components/ui/sidebar.tsx";
+
import ErrorBoundary from "./components/ErrorBoundary.tsx";
import { Error } from "./components/Error.tsx";
import { DarkModeProvider, Toaster } from "@stacklok/ui-kit";
@@ -24,20 +24,18 @@ createRoot(document.getElementById("root")!).render(
-
-
- }>
-
-
-
-
-
-
-
-
-
+
+ }>
+
+
+
+
+
+
+
+
- ,
+
);
diff --git a/src/mocks/msw/fixtures/GET_MESSAGES.json b/src/mocks/msw/fixtures/GET_MESSAGES.json
deleted file mode 100644
index 8e8b1158..00000000
--- a/src/mocks/msw/fixtures/GET_MESSAGES.json
+++ /dev/null
@@ -1,306 +0,0 @@
-[
- {
- "question_answers": [
- {
- "question": {
- "message": "```/Users/giuseppe/workspace/codegate/src/codegate/server.py\nimport traceback\n\nimport structlog\nfrom fastapi import APIRouter, FastAPI\nfrom fastapi.middleware.cors import CORSMiddleware\nfrom fastapi.responses import JSONResponse\nfrom starlette.middleware.errors import ServerErrorMiddleware\n\nfrom codegate import __description__, __version__\nfrom codegate.dashboard.dashboard import dashboard_router\nfrom codegate.pipeline.factory import PipelineFactory\nfrom codegate.providers.anthropic.provider import AnthropicProvider\nfrom codegate.providers.llamacpp.provider import LlamaCppProvider\nfrom codegate.providers.ollama.provider import OllamaProvider\nfrom codegate.providers.openai.provider import OpenAIProvider\nfrom codegate.providers.registry import ProviderRegistry\nfrom codegate.providers.vllm.provider import VLLMProvider\n\nlogger = structlog.get_logger(\"codegate\")\n\n\nasync def custom_error_handler(request, exc: Exception):\n \"\"\"This is a Middleware to handle exceptions and log them.\"\"\"\n # Capture the stack trace\n extracted_traceback = traceback.extract_tb(exc.__traceback__)\n # Log only the last 3 items of the stack trace. 3 is an arbitrary number.\n logger.error(traceback.print_list(extracted_traceback[-3:]))\n return JSONResponse({\"error\": str(exc)}, status_code=500)\n\n\ndef init_app(pipeline_factory: PipelineFactory) -> FastAPI:\n \"\"\"Create the FastAPI application.\"\"\"\n app = FastAPI(\n title=\"CodeGate\",\n description=__description__,\n version=__version__,\n )\n app.add_middleware(\n CORSMiddleware,\n allow_origins=[\"*\"],\n allow_credentials=True,\n allow_methods=[\"*\"],\n allow_headers=[\"*\"],\n )\n # Apply error handling middleware\n app.add_middleware(ServerErrorMiddleware, handler=custom_error_handler)\n\n # Create provider registry\n registry = ProviderRegistry(app)\n\n # Register all known providers\n registry.add_provider(\n \"openai\",\n OpenAIProvider(\n pipeline_processor=pipeline_factory.create_input_pipeline(),\n fim_pipeline_processor=pipeline_factory.create_fim_pipeline(),\n output_pipeline_processor=pipeline_factory.create_output_pipeline(),\n fim_output_pipeline_processor=pipeline_factory.create_fim_output_pipeline(),\n ),\n )\n registry.add_provider(\n \"anthropic\",\n AnthropicProvider(\n pipeline_processor=pipeline_factory.create_input_pipeline(),\n fim_pipeline_processor=pipeline_factory.create_fim_pipeline(),\n output_pipeline_processor=pipeline_factory.create_output_pipeline(),\n fim_output_pipeline_processor=pipeline_factory.create_fim_output_pipeline(),\n ),\n )\n registry.add_provider(\n \"llamacpp\",\n LlamaCppProvider(\n pipeline_processor=pipeline_factory.create_input_pipeline(),\n fim_pipeline_processor=pipeline_factory.create_fim_pipeline(),\n output_pipeline_processor=pipeline_factory.create_output_pipeline(),\n fim_output_pipeline_processor=pipeline_factory.create_fim_output_pipeline(),\n ),\n )\n registry.add_provider(\n \"vllm\",\n VLLMProvider(\n pipeline_processor=pipeline_factory.create_input_pipeline(),\n fim_pipeline_processor=pipeline_factory.create_fim_pipeline(),\n output_pipeline_processor=pipeline_factory.create_output_pipeline(),\n fim_output_pipeline_processor=pipeline_factory.create_fim_output_pipeline(),\n ),\n )\n registry.add_provider(\n \"ollama\",\n OllamaProvider(\n pipeline_processor=pipeline_factory.create_input_pipeline(),\n fim_pipeline_processor=pipeline_factory.create_fim_pipeline(),\n output_pipeline_processor=pipeline_factory.create_output_pipeline(),\n fim_output_pipeline_processor=pipeline_factory.create_fim_output_pipeline(),\n ),\n )\n\n # Create and add system routes\n system_router = APIRouter(tags=[\"System\"])\n\n @system_router.get(\"/health\")\n async def health_check():\n return {\"status\": \"healthy\"}\n\n app.include_router(system_router)\n app.include_router(dashboard_router)\n\n return app\n\n```\n\nserver.py do you see any security issue?",
- "timestamp": "2025-01-03T09:09:33.743797Z",
- "message_id": "a1549789-b610-4999-9907-f8f010c2f3b7"
- },
- "answer": {
- "message": "Yes, there are several security issues in the provided `server.py` code. Here are the main ones:\n\n1. **CORS Configuration**:\n ```python\n app.add_middleware(\n CORSMiddleware,\n allow_origins=[\"*\"],\n allow_credentials=True,\n allow_methods=[\"*\"],\n allow_headers=[\"*\"],\n )\n ```\n - Allowing all origins (`allow_origins=[\"*\"]`) and methods (`allow_methods=[\"*\"]`) can expose your server to Cross-Site Request Forgery (CSRF) attacks and other security vulnerabilities. It's recommended to restrict these settings to only the origins and methods that your application needs to support.\n\n2. **Error Handling Middleware**:\n ```python\n async def custom_error_handler(request, exc: Exception):\n \"\"\"This is a Middleware to handle exceptions and log them.\"\"\"\n # Capture the stack trace\n extracted_traceback = traceback.extract_tb(exc.__traceback__)\n # Log only the last 3 items of the stack trace. 3 is an arbitrary number.\n logger.error(traceback.print_list(extracted_traceback[-3:]))\n return JSONResponse({\"error\": str(exc)}, status_code=500)\n ```\n - Exposing detailed error messages in the response can be dangerous, as it can provide attackers with information that can be used to exploit vulnerabilities. It's better to log the error details and return a generic error message to the client.\n\n3. **Handling Exceptions**:\n - The `custom_error_handler` function logs the traceback and returns the exception message directly to the client. This can expose sensitive information about the server's internal state. It's better to log the error and return a generic message.\n\n4. **Provider Configuration**:\n - The providers are configured with the same pipeline processors. This might not be a security issue in itself, but it's important to ensure that the pipeline processors are secure and correctly configured.\n\n5. **Logging**:\n - The `logger.error` function is used to log the error traceback. Ensure that the logging configuration is set up correctly to prevent logging sensitive information to insecure destinations.\n\nHere is a revised version of the code with some of these issues addressed:\n\n```python\nimport traceback\n\nimport structlog\nfrom fastapi import APIRouter, FastAPI\nfrom fastapi.middleware.cors import CORSMiddleware\nfrom fastapi.responses import JSONResponse\nfrom starlette.middleware.errors import ServerErrorMiddleware\n\nfrom codegate import __description__, __version__\nfrom codegate.dashboard.dashboard import dashboard_router\nfrom codegate.pipeline.factory import PipelineFactory\nfrom codegate.providers.anthropic.provider import AnthropicProvider\nfrom codegate.providers.llamacpp.provider import LlamaCppProvider\nfrom codegate.providers.ollama.provider import OllamaProvider\nfrom codegate.providers.openai.provider import OpenAIProvider\nfrom codegate.providers.registry import ProviderRegistry\nfrom codegate.providers.vllm.provider import VLLMProvider\n\nlogger = structlog.get_logger(\"codegate\")\n\n\nasync def custom_error_handler(request, exc: Exception):\n \"\"\"This is a Middleware to handle exceptions and log them.\"\"\"\n # Capture the stack trace\n extracted_traceback = traceback.extract_tb(exc.__traceback__)\n # Log only the last 3 items of the stack trace. 3 is an arbitrary number.\n logger.error(traceback.print_list(extracted_traceback[-3:]))\n return JSONResponse({\"error\": \"An internal server error occurred\"}, status_code=500)\n\n\ndef init_app(pipeline_factory: PipelineFactory) -> FastAPI:\n \"\"\"Create the FastAPI application.\"\"\"\n app = FastAPI(\n title=\"CodeGate\",\n description=__description__,\n version=__version__,\n )\n app.add_middleware(\n CORSMiddleware,\n allow_origins=[\"https://example.com\"], # Restrict to specific origins\n allow_credentials=True,\n allow_methods=[\"GET\", \"POST\"], # Restrict to specific methods\n allow_headers=[\"*\"],\n )\n # Apply error handling middleware\n app.add_middleware(ServerErrorMiddleware, handler=custom_error_handler)\n\n # Create provider registry\n registry = ProviderRegistry(app)\n\n # Register all known providers\n registry.add_provider(\n \"openai\",\n OpenAIProvider(\n pipeline_processor=pipeline_factory.create_input_pipeline(),\n fim_pipeline_processor=pipeline_factory.create_fim_pipeline(),\n output_pipeline_processor=pipeline_factory.create_output_pipeline(),\n fim_output_pipeline_processor=pipeline_factory.create_fim_output_pipeline(),\n ),\n )\n registry.add_provider(\n \"anthropic\",\n AnthropicProvider(\n pipeline_processor=pipeline_factory.create_input_pipeline(),\n fim_pipeline_processor=pipeline_factory.create_fim_pipeline(),\n output_pipeline_processor=pipeline_factory.create_output_pipeline(),\n fim_output_pipeline_processor=pipeline_factory.create_fim_output_pipeline(),\n ),\n )\n registry.add_provider(\n \"llamacpp\",\n LlamaCppProvider(\n pipeline_processor=pipeline_factory.create_input_pipeline(),\n fim_pipeline_processor=pipeline_factory.create_fim_pipeline(),\n output_pipeline_processor=pipeline_factory.create_output_pipeline(),\n fim_output_pipeline_processor=pipeline_factory.create_fim_output_pipeline(),\n ),\n )\n registry.add_provider(\n \"vllm\",\n VLLMProvider(\n pipeline_processor=pipeline_factory.create_input_pipeline(),\n fim_pipeline_processor=pipeline_factory.create_fim_pipeline(),\n output_pipeline_processor=pipeline_factory.create_output_pipeline(),\n fim_output_pipeline_processor=pipeline_factory.create_fim_output_pipeline(),\n ),\n )\n registry.add_provider(\n \"ollama\",\n OllamaProvider(\n pipeline_processor=pipeline_factory.create_input_pipeline(),\n fim_pipeline_processor=pipeline_factory.create_fim_pipeline(),\n output_pipeline_processor=pipeline_factory.create_output_pipeline(),\n fim_output_pipeline_processor=pipeline_factory.create_fim_output_pipeline(),\n ),\n )\n\n # Create and add system routes\n system_router = APIRouter(tags=[\"System\"])\n\n @system_router.get(\"/health\")\n async def health_check():\n return {\"status\": \"healthy\"}\n\n app.include_router(system_router)\n app.include_router(dashboard_router)\n\n return app\n```\n\nIn this revised version, the CORS middleware is restricted to specific origins and methods, and the error handler returns a generic error message to the client.",
- "timestamp": "2025-01-03T09:09:34.359269Z",
- "message_id": "68e7c9bc-9afa-4240-8eba-621eaae4a2a0"
- }
- }
- ],
- "provider": "vllm",
- "type": "chat",
- "chat_id": "chatcmpl-eebad2bd68434c7c947d10754a204ea4",
- "conversation_timestamp": "2025-01-03T09:09:33.743797Z"
- },
- {
- "question_answers": [
- {
- "question": {
- "message": "\n\n```tsx src/App.tsx (23-29)\n const { prompts, loading, fetchPrompts } = usePromptsStore();\n useSse();\n\n const test = \"REDACTED<$lCzMsqxUWElmZqd/qX4llR3pqsrBJVxkt6SB6WXXKKhYG37ZnscxPawjfZmgPVoSpcSR975hOIE2s/6iVr2JETpVdwdaG4hAYlzJ4tteJg==>\";\n const breadcrumb = useBreadcrumb();\n\n\n```\nPlease explain the cause of this error and how to solve it: 'test' is declared but its value is never read.",
- "timestamp": "2025-01-02T13:42:48.721165Z",
- "message_id": "031cf7a3-1ce6-435d-bbee-0f939b30cc20"
- },
- "answer": {
- "message": "The error message \"'test' is declared but its value is never read\" indicates that the variable `test` is defined in your code, but it is never used anywhere. This can lead to confusion for other developers reading the code and might indicate that the intended functionality has not been implemented.\n\n### Cause of the Error\n\n1. **Unused Variable**: The variable `test` is declared but not used in the rest of the code within the `App` component. This can be a result of incomplete implementation or a placeholder that was meant to be used later but was forgotten.\n\n### How to Solve It\n\nYou have a few options to resolve this issue:\n\n1. **Remove the Unused Variable**:\n - If the variable is not needed, you can safely remove it to clean up the code and eliminate the warning.\n\n ```tsx\n const { prompts, loading, fetchPrompts } = usePromptsStore();\n useSse();\n\n const breadcrumb = useBreadcrumb();\n\n useEffect(() => {\n fetchPrompts();\n }, [fetchPrompts]);\n ```\n\n2. **Use the Variable**:\n - If the variable is intended to be used, ensure that it is utilized somewhere in the component. For example, you could log it for debugging purposes or use it in some part of the application logic.\n\n ```tsx\n const { prompts, loading, fetchPrompts } = usePromptsStore();\n useSse();\n\n const test = \"REDACTED<$lCzMsqxUWElmZqd/qX4llR3pqsrBJVxkt6SB6WXXKKhYG37ZnscxPawjfZmgPVoSpcSR975hOIE2s/6iVr2JETpVdwdaG4hAYlzJ4tteJg==>\";\n const breadcrumb = useBreadcrumb();\n\n useEffect(() => {\n console.log(test); // Example usage\n fetchPrompts();\n }, [fetchPrompts]);\n ```\n\n3. **Suppress the Warning (Not Recommended)**:\n - If you are sure that the variable is intentionally unused and do not want to remove it, you can suppress the warning. However, this is generally not recommended as it can lead to oversight of real issues.\n\n ```tsx\n // @ts-ignore\n const test = \"REDACTED<$lCzMsqxUWElmZqd/qX4llR3pqsrBJVxkt6SB6WXXKKhYG37ZnscxPawjfZmgPVoSpcSR975hOIE2s/6iVr2JETpVdwdaG4hAYlzJ4tteJg==>\";\n const breadcrumb = useBreadcrumb();\n ```\n\n### Best Practices\n\n- **Keep Code Clean**: Regularly clean up unused variables to maintain a clean and understandable codebase.\n- **Use Linters**: Utilize tools like ESLint to automatically detect and flag unused variables, helping you maintain code quality.\n\nBy addressing this issue, you can improve the maintainability and readability of your code.",
- "timestamp": "2025-01-02T13:42:49.361714Z",
- "message_id": "a542408b-d0bc-4f6f-8b71-64c93c1e388a"
- }
- }
- ],
- "provider": "vllm",
- "type": "chat",
- "chat_id": "chatcmpl-7d87679de7ed41639eb91d8ebbaa6f72",
- "conversation_timestamp": "2025-01-02T13:42:48.721165Z"
- },
- {
- "question_answers": [
- {
- "question": {
- "message": "```/Users/giuseppe/workspace/codegate-ui/src/App.tsx\nimport { Header } from \"./components/Header\";\nimport { PromptList } from \"./components/PromptList\";\nimport { useEffect } from \"react\";\nimport { Dashboard } from \"./components/Dashboard\";\nimport { Routes, Route, Link } from \"react-router-dom\";\nimport { Chat } from \"./components/Chat\";\nimport { usePromptsStore } from \"./hooks/usePromptsStore\";\nimport { Sidebar } from \"./components/Sidebar\";\nimport { useSse } from \"./hooks/useSse\";\nimport { Help } from \"./components/Help\";\nimport { Certificates } from \"./components/Certificates\";\nimport { CertificateSecurity } from \"./components/CertificateSecurity\";\nimport {\n Breadcrumb,\n BreadcrumbList,\n BreadcrumbItem,\n BreadcrumbSeparator,\n BreadcrumbPage,\n} from \"./components/ui/breadcrumb\";\nimport { useBreadcrumb } from \"./hooks/useBreadcrumb\";\n\nfunction App() {\n const { prompts, loading, fetchPrompts } = usePromptsStore();\n useSse();\n\n const test = \"REDACTED<$4g3iWc/V3YD6pAXIeA43M197ostjpL3XZuDbJuxTN+jG1Mcjv6cMFcC73SsIDzECoWSuZqSYGkrWe5vQqbheaJbDlocBrPZMkgQWkcP8gw==>\";\n const breadcrumb = useBreadcrumb();\n\n useEffect(() => {\n fetchPrompts();\n }, [fetchPrompts]);\n\n return (\n \n \n \n \n \n \n\n \n \n \n \n Dashboard\n \n {breadcrumb && (\n <>\n \n \n \n {breadcrumb}\n \n \n >\n )}\n \n \n \n\n \n \n } />\n } />\n } />\n } />\n }\n />\n \n \n \n \n );\n}\n\nexport default App;\n\n```\n\nApp.tsx could you review?",
- "timestamp": "2025-01-02T13:40:18.343146Z",
- "message_id": "33b28648-c3a8-4f52-a110-e59a19dc6fab"
- },
- "answer": {
- "message": "**CodeGate Security Analysis**\n\n### Security Vulnerabilities and Potential Risks\n\n1. **Unsanitized User Input and Cross-Site Scripting (XSS)**\n - The `breadcrumb` variable, which is derived from user interaction, is directly rendered within the `` component. If the `breadcrumb` contains user-controlled data, it could be vulnerable to XSS attacks if not properly sanitized.\n\n2. **Potential Sensitive Data Exposure**\n - The `test` variable contains a string that begins with \"REDACTED\", indicating it may contain sensitive information. Ensure that such strings are not exposed or logged in a manner that could lead to data breaches.\n\n3. **Lack of Authentication and Authorization**\n - The code does not include any authentication or authorization mechanisms. This could allow unauthorized users to access sensitive components like `/certificates` and `/certificates/security`.\n\n4. **Security of Sensitive Components**\n - Components like `CertificateSecurity` and `Certificates` might handle sensitive information. Ensure that these components implement proper security measures to protect the data they handle.\n\n### Best Practices for Secure Implementation\n\n1. **Sanitize and Encode User Input**\n - Always sanitize and encode user inputs to prevent XSS attacks. Use libraries like `DOMPurify` to sanitize HTML content.\n\n2. **Implement Authentication and Authorization**\n - Integrate a robust authentication system (e.g., OAuth, JWT) to ensure that only authorized users can access sensitive routes and components.\n\n3. **Protect Sensitive Data**\n - Ensure that any sensitive data, such as certificates and security-related information, is encrypted both at rest and in transit. Use HTTPS for all communications.\n\n4. **Regular Security Audits and Testing**\n - Conduct regular security audits and penetration testing to identify and fix vulnerabilities in the application.\n\n### Recommendations for Security Improvements\n\n1. **Sanitize Breadcrumb Data**\n - Modify the `BreadcrumbPage` component to sanitize the `breadcrumb` data before rendering it. For example:\n ```jsx\n \n {DOMPurify.sanitize(breadcrumb)}\n \n ```\n\n2. **Implement Authentication**\n - Use a library like `react-router-dom` with authentication middleware to protect routes. For example:\n ```jsx\n \n \n \n }\n />\n ```\n\n3. **Secure Components**\n - Ensure that components like `CertificateSecurity` and `Certificates` implement proper security measures, such as input validation, encryption, and access controls.\n\n4. **Review and Secure Dependencies**\n - Regularly review and update dependencies to ensure they are secure and free from known vulnerabilities. Use tools like `npm audit` to identify and fix vulnerabilities.\n\n### References to Relevant Security Standards or Guidelines\n\n- **OWASP Top Ten**: A list of the most critical security risks to web applications, including XSS and injection flaws.\n- **OWASP Security Testing Guide**: Provides detailed guidance on how to test for various security vulnerabilities.\n- **NIST Cybersecurity Framework**: Offers a comprehensive set of guidelines for managing and reducing cybersecurity risks.\n\nBy addressing these security considerations, you can significantly enhance the security posture of your application.",
- "timestamp": "2025-01-02T13:40:18.881023Z",
- "message_id": "82b89210-811a-478d-9229-e45bd3d60913"
- }
- }
- ],
- "provider": "vllm",
- "type": "chat",
- "chat_id": "chatcmpl-92720293078e459e9d65be5298a9dae6",
- "conversation_timestamp": "2025-01-02T13:40:18.343146Z"
- },
- {
- "question_answers": [
- {
- "question": {
- "message": "hello",
- "timestamp": "2025-01-02T13:39:39.656516Z",
- "message_id": "1273fc1a-43e7-42c3-82b0-0057858d9b98"
- },
- "answer": {
- "message": "Hello! How can I assist you today?",
- "timestamp": "2025-01-02T13:39:40.159594Z",
- "message_id": "24aa2eec-f5bc-4275-8e30-a8f68d3f3002"
- }
- }
- ],
- "provider": "vllm",
- "type": "chat",
- "chat_id": "chatcmpl-40c88006b69940dab2d454175ff4c533",
- "conversation_timestamp": "2025-01-02T13:39:39.656516Z"
- },
- {
- "question_answers": [
- {
- "question": {
- "message": "<|fim_prefix|>// diff --git a/src/App.tsx b/src/App.tsx\n// index fafa5bf..46b5fbd 100644\n// --- a/src/App.tsx\n// +++ b/src/App.tsx\n// @@ -22,6 +22,8 @@ import { useBreadcrumb } from \"./hooks/useBreadcrumb\";\n// function App() {\n// const { prompts, loading, fetchPrompts } = usePromptsStore();\n// useSse();\n// +\n// + cosnt test = REDACTED<$u0UEa5CGkrHrKv9oWKNC/aUuzW/D1d0ULifi+LvgIaGefP/xzkPr73YSZpciPs1YUAfjgcx35E3X81EwaKw4C68LTZuOXBBHEzZokLNZbg==>\n// const breadcrumb = useBreadcrumb();\n// \n// useEffect(() => {\n// src/App.tsx\nimport { useEffect } from \"react\";\nimport { Dashboard } from \"./components/Dashboard\";\nimport { Routes, Route, Link } from \"react-router-dom\";\nimport { Chat } from \"./components/Chat\";\nimport { usePromptsStore } from \"./hooks/usePromptsStore\";\nimport { Sidebar } from \"./components/Sidebar\";\nimport { useSse } from \"./hooks/useSse\";\nimport { Help } from \"./components/Help\";\nimport { Certificates } from \"./components/Certificates\";\nimport { CertificateSecurity } from \"./components/CertificateSecurity\";\nimport {\n Breadcrumb,\n BreadcrumbList,\n BreadcrumbItem,\n BreadcrumbSeparator,\n BreadcrumbPage,\n} from \"./components/ui/breadcrumb\";\nimport { useBreadcrumb } from \"./hooks/useBreadcrumb\";\n\nfunction App() {\n const { prompts, loading, fetchPrompts } = usePromptsStore();\n useSse();\n\n cosnt test = 'REDACTED<$m2fVY6C1zlk0swockzeQM/vDxL0h6im+Ber6vzYFFplvdR7dncAecjH18/Wptj0h3lQu7xCKxCFfCXxn7qe0clMZisPnRvohJOS/+W6P/m0++N/asPFkZnhJ3YPS>'\n const breadcrumb = useBreadcrumb();\n\n useEffect(() => {\n fetchPrompts();\n }, [fetchPrompts]);\n\n return (\n \n \n \n \n \n \n\n \n \n \n \n Dashboard\n \n {breadcrumb && (\n <>\n \n <|fim_middle|>",
- "timestamp": "2025-01-02T13:40:06.097504Z",
- "message_id": "ccf1c87a-4846-4d81-b69f-f882f1971cf7"
- },
- "answer": null
- }
- ],
- "provider": "vllm",
- "type": "fim",
- "chat_id": "ccf1c87a-4846-4d81-b69f-f882f1971cf7",
- "conversation_timestamp": "2025-01-02T13:40:06.097504Z"
- },
- {
- "question_answers": [
- {
- "question": {
- "message": "<|im_start|>user\n```tsx\nimport { Header } from \"./components/Header\";\nimport { PromptList } from \"./components/PromptList\";\nimport { useEffect } from \"react\";\nimport { Dashboard } from \"./components/Dashboard\";\nimport { Routes, Route, Link } from \"react-router-dom\";\nimport { Chat } from \"./components/Chat\";\nimport { usePromptsStore } from \"./hooks/usePromptsStore\";\nimport { Sidebar } from \"./components/Sidebar\";\nimport { useSse } from \"./hooks/useSse\";\nimport { Help } from \"./components/Help\";\nimport { Certificates } from \"./components/Certificates\";\nimport { CertificateSecurity } from \"./components/CertificateSecurity\";\nimport {\n Breadcrumb,\n BreadcrumbList,\n BreadcrumbItem,\n BreadcrumbSeparator,\n BreadcrumbPage,\n} from \"./components/ui/breadcrumb\";\nimport { useBreadcrumb } from \"./hooks/useBreadcrumb\";\n\nfunction App() {\n const { prompts, loading, fetchPrompts } = usePromptsStore();\n useSse();\n\n const test = \"REDACTED<$lAJek6QoLZgSKq3z4ybqMVFOCtiHQtP8hbBzWeQysFQoGwwNrEghIPnFCsXY985bpTjx4ZIiBzx11E1yM3ybbUzoxG/W4TrUPzM4aHAjIg==>\";\n const breadcrumb = useBreadcrumb();\n\n useEffect(() => {\n fetchPrompts();\n }, [fetchPrompts]);\n\n return (\n \n \n \n \n \n \n\n \n \n \n \n Dashboard\n \n {breadcrumb && (\n <>\n \n \n \n {breadcrumb}\n \n \n >\n )}\n \n \n \n\n \n \n } />\n } />\n } />\n } />\n }\n />\n \n \n \n \n );\n}\n\nexport default App;\n\n\n```\n\nPlease rewrite the entire code block above in order to satisfy the following request: \"The following code was suggested as an edit:\n```\n// @ts-ignore\nconst test = \"REDACTED<$gGYIqfbrVCHST8Xn/yfGtFDfQQ3Kn6mQ9t1+YrUCKYxNEiRYb4u9BQmxZkOQE5I+9UZRasD0/bY3m00v4OSo+PI6Zzn97RSVNJgM47nUlQ==>\";\nconst breadcrumb = useBreadcrumb();\n```\nPlease apply it to the previous code.\". You should rewrite the entire code block without leaving placeholders, even if the code is the same as before.<|im_end|>\n<|im_start|>assistant\nSure! Here's the entire rewritten code block:\n```tsx\n",
- "timestamp": "2025-01-02T14:19:58.024100Z",
- "message_id": "b97fbe59-0e34-4b98-8f2f-41332ebc059a"
- },
- "answer": null
- }
- ],
- "provider": "vllm",
- "type": "fim",
- "chat_id": "b97fbe59-0e34-4b98-8f2f-41332ebc059a",
- "conversation_timestamp": "2025-01-02T14:19:58.024100Z"
- },
- {
- "question_answers": [
- {
- "question": {
- "message": "<|fim_prefix|>// diff --git a/package.json b/package.json\n// index 3b62d87..4774c65 100644\n// --- a/package.json\n// +++ b/package.json\n// @@ -21,7 +21,6 @@\n// \"@radix-ui/react-switch\": \"^1.1.1\",\n// \"@radix-ui/react-tooltip\": \"^1.1.6\",\n// \"@types/prismjs\": \"^1.26.5\",\n// - \"@types/react-copy-to-clipboard\": \"^5.0.7\",\n// \"@types/react-syntax-highlighter\": \"^15.5.13\",\n// \"class-variance-authority\": \"^0.7.1\",\n// \"clsx\": \"^2.1.1\",\n// @@ -29,7 +28,6 @@\n// \"lucide-react\": \"^0.462.0\",\n// \"prismjs\": \"^1.29.0\",\n// \"react\": \"^18.3.1\",\n// - \"react-copy-to-clipboard\": \"^5.1.0\",\n// \"react-dom\": \"^18.3.1\",\n// \"react-markdown\": \"^9.0.1\",\n// \"react-router-dom\": \"^7.0.2\",\n// @@ -61,4 +59,4 @@\n// \"typescript-eslint\": \"^8.15.0\",\n// \"vite\": \"^6.0.1\"\n// }\n// -}\n// +}\n// \\ No newline at end of file\n// diff --git a/src/components/CopyToClipboard.tsx b/src/components/CopyToClipboard.tsx\n// new file mode 100644\n// index 0000000..682af0c\n// --- /dev/null\n// +++ b/src/components/CopyToClipboard.tsx\n// @@ -0,0 +1,31 @@\n// +import clsx from \"clsx\";\n// +import { ClipboardCopy } from \"lucide-react\";\n// +\n// +export function CopyToClipboard({\n// + text,\n// + className,\n// +}: {\n// + className?: string;\n// + text: string;\n// +}) {\n// + const copyToClipboard = async () => {\n// + try {\n// + await navigator.clipboard.writeText(text);\n// + } catch {}\n// + };\n// +\n// + return (\n// + \n// + \n// + \n// + );\n// +}\n// Path: Untitled.txt\n// navigator.clipboard.writeText\n// components/CopyToClipboard.tsx\nimport clsx from \"clsx\";\nimport { ClipboardCopy } from \"lucide-react\";\n\nexport function CopyToClipboard({\n text,\n className,\n}: {\n className?: string;\n text: string;\n}) {\n const copyToClipboard = async () => {\n try {\n await navigator.clipboard.writeText(text);\n } catch {}\n };\n\n return (\n hover:bg-gray-700/70 \n transition-opacity duration-200 \n opacity-0 group-hover:opacity-100`,\n className\n )}\n >\n \n \n );\n}\n<|fim_middle|>",
- "timestamp": "2025-01-02T15:00:12.114817Z",
- "message_id": "27318899-9e9b-4dfa-9e61-f482dcd24e54"
- },
- "answer": null
- }
- ],
- "provider": "vllm",
- "type": "fim",
- "chat_id": "27318899-9e9b-4dfa-9e61-f482dcd24e54",
- "conversation_timestamp": "2025-01-02T15:00:12.114817Z"
- },
- {
- "question_answers": [
- {
- "question": {
- "message": "<|fim_prefix|>\n// components/Markdown.tsx\nimport remarkGfm from \"remark-gfm\";\nimport ReactMarkdown from \"react-markdown\";\nimport { Prism as SyntaxHighlighter } from \"react-syntax-highlighter\";\nimport { oneDark } from \"react-syntax-highlighter/dist/esm/styles/prism\";\nimport { cn } from \"@/lib/utils\";\nimport { CopyToClipboard } from \"./CopyToClipboard\";\n\ninterface Props {\n children: string;\n className?: string;\n}\n\nconst customStyle = {\n ...oneDark,\n 'pre[class*=\"language-\"]': {\n ...oneDark['pre[class*=\"language-\"]'],\n whiteSpace: \"pre-wrap\",\n background: \"#re<|fim_suffix|>\",\n padding: \"1.5rem\",\n borderRadius: \"0.5rem\",\n margin: \"1.5rem 0\",\n fontSize: \"10px\",\n width: \"80%\", // Ensure the block takes full width\n position: \"relative\",\n },\n};\n\nexport function Markdown({ children, className = \"\" }: Props) {\n return (\n <|fim_middle|>",
- "timestamp": "2025-01-02T17:01:41.579128Z",
- "message_id": "4ea5d1df-c724-49f7-a132-d19e092b52c8"
- },
- "answer": null
- }
- ],
- "provider": "vllm",
- "type": "fim",
- "chat_id": "4ea5d1df-c724-49f7-a132-d19e092b52c8",
- "conversation_timestamp": "2025-01-02T17:01:41.579128Z"
- },
- {
- "question_answers": [
- {
- "question": {
- "message": "<|fim_prefix|>// Path: Untitled.txt\n// 1a1b26\n// components/Markdown.tsx\n // whiteSpace: \"pre-wrap\",\n // background: \"#1a1b26\",\n // padding: \"1.5rem\",\n // borderRadius: \"0.5rem\",\n // margin: \"1.5rem 0\",\n // fontSize: \"10px\",\n // width: \"80%\", // Ensure the block takes full width\n // position: \"relative\",\n // },\n};\n\nexport function Markdown({ children, className = \"\" }: Props) {\n return (\n \n 1a1b26\",\n // fontSize: \"10x\",\n // whiteSpace: \"pre-wrap\",\n // padding: \"1.5rem\",\n // borderRadius: \"0.5rem\",\n // margin: \"1.5rem 0\",\n // position: \"relative\", // Critical for clipboard positioning\n // width: \"100%\", // Ensure full width of parent container\n // boxSizing: \"border-box\", // Prevent padding overflow\n },\n }}\n language={match[1]}\n PreTag=\"div\"\n className=\"rounded-lg overflow-hidden shadow-lg text-sm\"\n showLineNumbers={false}\n wrapLines\n {...props}\n ><|fim_middle|>",
- "timestamp": "2025-01-02T17:08:08.428889Z",
- "message_id": "203fe119-0e58-43b5-a863-73f30f73d272"
- },
- "answer": null
- }
- ],
- "provider": "vllm",
- "type": "fim",
- "chat_id": "203fe119-0e58-43b5-a863-73f30f73d272",
- "conversation_timestamp": "2025-01-02T17:08:08.428889Z"
- },
- {
- "question_answers": [
- {
- "question": {
- "message": "<|fim_prefix|>\n// components/Markdown.tsx\n}\n\nconst customStyle = {\n ...oneDark,\n 'pre[class*=\"language-\"]': {\n ...oneDark['pre[class*=\"language-\"]'],\n whiteSpace: \"pre-wrap\",\n // background: \"#1a1b26\",\n padding: \"1.5rem\",\n borderRadius: \"0.5rem\",\n margin: \"1.5rem 0\",\n fontSize: \"10px\",\n width: \"80%\", // Ensure the block takes full width\n position: \"relative\",\n },\n};\n\nexport function Markdown({ children, className = \"\" }: Props) {\n return (\n \n \"\n showLineNumbers={false}\n wrapLines\n {...props}\n >\n {String(children).replace(/\\n$/, \"\")}\n \n \n \n ) : (\n ",
- "timestamp": "2025-01-02T17:20:32.921469Z",
- "message_id": "d81ca662-84ae-4114-9041-6d640c547bb9"
- },
- "answer": null
- }
- ],
- "provider": "vllm",
- "type": "fim",
- "chat_id": "d81ca662-84ae-4114-9041-6d640c547bb9",
- "conversation_timestamp": "2025-01-02T17:20:32.921469Z"
- },
- {
- "question_answers": [
- {
- "question": {
- "message": "<|fim_prefix|>// Path: Untitled.txt\n// showLineNumbers\n// components/Markdown.tsx\n background: \"none\",\n padding: \"0\",\n borderRadius: \"0\",\n fontSize: \"inherit\",\n color: \"inherit\",\n display: \"inline\",\n },\n 'pre[class*=\"language-\"]': {\n ...oneDark['pre[class*=\"language-\"]'],\n whiteSpace: \"pre-wrap\",\n background: \"#1a1b26\",\n padding: \"1.5rem\",\n borderRadius: \"0.5rem\",\n margin: \"1.5rem 0\",\n fontSize: \"10px\",\n width: \"80%\",\n position: \"relative\",\n },\n};\nexport function Markdown({ children, className = \"\" }: Props) {\n return (\n \n ={{\n ...customStyle,\n 'pre[class*=\"language-\"]': {\n ...oneDark['pre[class*=\"language-\"]'],\n background: \"#1a1b26\",\n padding: \"1.5rem\",\n borderRadius: \"0.5rem\",\n margin: \"1.5rem 0\",\n width: \"100%\",\n position: \"relative\",\n boxSizing: \"border-box\",\n },\n }}\n language={match[1]}\n PreTag=\"div\"\n className=\"rounded-lg overflow-hidden shadow-lg text-sm\"\n wrapLines\n {...props}\n >\n {String(children).replace(/\\n$/, \"\")}\n <|fim_middle|>",
- "timestamp": "2025-01-02T17:25:40.052416Z",
- "message_id": "bb2ab808-1ce7-4937-9d79-0d773dcb7111"
- },
- "answer": null
- }
- ],
- "provider": "vllm",
- "type": "fim",
- "chat_id": "bb2ab808-1ce7-4937-9d79-0d773dcb7111",
- "conversation_timestamp": "2025-01-02T17:25:40.052416Z"
- },
- {
- "question_answers": [
- {
- "question": {
- "message": "<|fim_prefix|>// diff --git a/package.json b/package.json\n// index 42ed9d9..6b1ddde 100644\n// --- a/package.json\n// +++ b/package.json\n// @@ -25,6 +25,7 @@\n// \"class-variance-authority\": \"^0.7.1\",\n// \"clsx\": \"^2.1.1\",\n// \"date-fns\": \"^4.1.0\",\n// + \"highlight.js\": \"^11.11.1\",\n// \"lucide-react\": \"^0.462.0\",\n// \"prismjs\": \"^1.29.0\",\n// \"react\": \"19.0.0\",\n// components/Markdown.tsx\nimport hljs from \"highlight.js\";\n\ninterface Props {\n children: string;\n className?: string;\n}\nconsole.log({ oneDark });\nconst customStyle = {\n ...oneDark,\n 'code[class*=\"language-\"]': {\n ...oneDark['code[class*=\"language-\"]'],\n background: \"none\",\n },\n 'pre[class*=\"language-\"]': {\n ...oneDark['pre[class*=\"language-\"]'],\n whiteSpace: \"pre-wrap\",\n background: \"#1a1b26\",\n padding: \"1.5rem\",\n borderRadius: \"0.5rem\",\n margin: \"1.5rem 0\",\n fontSize: \"10px\",\n width: \"80%\",\n position: \"relative\",\n },\n};\nexport function Markdown({ children, className = \"\" }: Props) {\n \n return (\n ).language;\n console.log({ detectedLanguage });\n const match = /language-(\\w+)/.exec(className || \"\");\n const inline = !match;\n return !inline ? (\n \n ",
- "timestamp": "2025-01-03T10:22:01.721847Z",
- "message_id": "9b522dca-a54e-4356-9692-d5be66f051cc"
- },
- "answer": null
- }
- ],
- "provider": "vllm",
- "type": "fim",
- "chat_id": "9b522dca-a54e-4356-9692-d5be66f051cc",
- "conversation_timestamp": "2025-01-03T10:22:01.721847Z"
- },
- {
- "question_answers": [
- {
- "question": {
- "message": "<|fim_prefix|>// diff --git a/package.json b/package.json\n// index 42ed9d9..6b1ddde 100644\n// --- a/package.json\n// +++ b/package.json\n// @@ -25,6 +25,7 @@\n// \"class-variance-authority\": \"^0.7.1\",\n// \"clsx\": \"^2.1.1\",\n// \"date-fns\": \"^4.1.0\",\n// + \"highlight.js\": \"^11.11.1\",\n// \"lucide-react\": \"^0.462.0\",\n// \"prismjs\": \"^1.29.0\",\n// \"react\": \"19.0.0\",\n// Path: Untitled.txt\n// a11yDark\n// components/Markdown.tsx\nconst customStyle = {\n ...oneDark,\n 'code[class*=\"language-\"]': {\n ...oneDark['code[class*=\"language-\"]'],\n background: \"none\",\n },\n 'pre[class*=\"language-\"]': {\n ...oneDark['pre[class*=\"language-\"]'],\n whiteSpace: \"pre-wrap\",\n background: \"#1a1b26\",\n padding: \"1.5rem\",\n borderRadius: \"0.5rem\",\n margin: \"1.5rem 0\",\n fontSize: \"10px\",\n width: \"80%\",\n position: \"relative\",\n },\n};\nexport function Markdown({ children, className = \"\" }: Props) {\n return (\n \n return (\n <>\n \n {String(children).replace(/\\n$/, \"\")}<|fim_middle|>",
- "timestamp": "2025-01-03T10:59:20.862404Z",
- "message_id": "b21cb89e-f5f0-4cbe-95a6-7407bf59199d"
- },
- "answer": null
- }
- ],
- "provider": "vllm",
- "type": "fim",
- "chat_id": "b21cb89e-f5f0-4cbe-95a6-7407bf59199d",
- "conversation_timestamp": "2025-01-03T10:59:20.862404Z"
- },
- {
- "question_answers": [
- {
- "question": {
- "message": "<|fim_prefix|>// diff --git a/package.json b/package.json\n// index 42ed9d9..6b1ddde 100644\n// --- a/package.json\n// +++ b/package.json\n// @@ -25,6 +25,7 @@\n// \"class-variance-authority\": \"^0.7.1\",\n// \"clsx\": \"^2.1.1\",\n// \"date-fns\": \"^4.1.0\",\n// + \"highlight.js\": \"^11.11.1\",\n// \"lucide-react\": \"^0.462.0\",\n// \"prismjs\": \"^1.29.0\",\n// \"react\": \"19.0.0\",\n// components/Markdown.tsx\n children: string;\n className?: string;\n}\nconsole.log({ oneDark });\nconst customStyle = {\n ...oneDark,\n 'code[class*=\"language-\"]': {\n ...oneDark['code[class*=\"language-\"]'],\n background: \"none\",\n },\n 'pre[class*=\"language-\"]': {\n ...oneDark['pre[class*=\"language-\"]'],\n whiteSpace: \"pre-wrap\",\n background: \"#1a1b26\",\n padding: \"1.5rem\",\n borderRadius: \"0.5rem\",\n margin: \"1.5rem 0\",\n fontSize: \"10px\",\n width: \"80%\",\n position: \"relative\",\n },\n};\nexport function Markdown({ children, className = \"\" }: Props) {\n return (\n >\n \n {String(children).replace(/\\n$/, \"\")}\n <|fim_middle|>",
- "timestamp": "2025-01-03T11:05:21.749423Z",
- "message_id": "a5bd2d58-4339-4bbd-a5b0-8f65ff758b46"
- },
- "answer": null
- }
- ],
- "provider": "vllm",
- "type": "fim",
- "chat_id": "a5bd2d58-4339-4bbd-a5b0-8f65ff758b46",
- "conversation_timestamp": "2025-01-03T11:05:21.749423Z"
- },
- {
- "question_answers": [
- {
- "question": {
- "message": "<|fim_prefix|>// diff --git a/package.json b/package.json\n// index 42ed9d9..6b1ddde 100644\n// --- a/package.json\n// +++ b/package.json\n// @@ -25,6 +25,7 @@\n// \"class-variance-authority\": \"^0.7.1\",\n// \"clsx\": \"^2.1.1\",\n// \"date-fns\": \"^4.1.0\",\n// + \"highlight.js\": \"^11.11.1\",\n// \"lucide-react\": \"^0.462.0\",\n// \"prismjs\": \"^1.29.0\",\n// \"react\": \"19.0.0\",\n// diff --git a/src/components/ui/chat/chat-bubble.tsx b/src/components/ui/chat/chat-bubble.tsx\n// index b9d20ec..1be8760 100644\n// --- a/src/components/ui/chat/chat-bubble.tsx\n// +++ b/src/components/ui/chat/chat-bubble.tsx\n// @@ -107,7 +107,7 @@ const ChatBubbleMessage = React.forwardRef<\n// const customStyle = {\n ...oneDark,\n 'code[class*=\"language-\"]': {\n ...oneDark['code[class*=\"language-\"]'],\n background: \"none\",\n },\n 'pre[class*=\"language-\"]': {\n ...oneDark['pre[class*=\"language-\"]'],\n whiteSpace: \"pre-wrap\",\n background: \"#1a1b26\",\n padding: \"1.5rem\",\n borderRadius: \"0.5rem\",\n margin: \"1.5rem 0\",\n fontSize: \"10px\",\n width: \"80%\",\n position: \"relative\",\n },\n};\nexport function Markdown({ children, className = \"\" }: Props) {\n return (\n ",
- "timestamp": "2025-01-03T11:32:56.031157Z",
- "message_id": "63dee5cb-444e-484c-8259-83be0ea073ac"
- },
- "answer": null
- }
- ],
- "provider": "vllm",
- "type": "fim",
- "chat_id": "63dee5cb-444e-484c-8259-83be0ea073ac",
- "conversation_timestamp": "2025-01-03T11:32:56.031157Z"
- },
- {
- "question_answers": [
- {
- "question": {
- "message": "<|fim_prefix|>// diff --git a/package.json b/package.json\n// index 42ed9d9..6b1ddde 100644\n// --- a/package.json\n// +++ b/package.json\n// @@ -25,6 +25,7 @@\n// \"class-variance-authority\": \"^0.7.1\",\n// \"clsx\": \"^2.1.1\",\n// \"date-fns\": \"^4.1.0\",\n// + \"highlight.js\": \"^11.11.1\",\n// \"lucide-react\": \"^0.462.0\",\n// \"prismjs\": \"^1.29.0\",\n// \"react\": \"19.0.0\",\n// diff --git a/src/components/ui/chat/chat-bubble.tsx b/src/components/ui/chat/chat-bubble.tsx\n// index b9d20ec..1be8760 100644\n// --- a/src/components/ui/chat/chat-bubble.tsx\n// +++ b/src/components/ui/chat/chat-bubble.tsx\n// @@ -107,7 +107,7 @@ const ChatBubbleMessage = React.forwardRef<\n// \n} from \"react-syntax-highlighter\";\nimport { oneDark } from \"react-syntax-highlighter/dist/esm/styles/prism\";\nimport { cn } from \"@/lib/utils\";\nimport { CopyToClipboard } from \"./CopyToClipboard\";\nimport hljs from \"highlight.js\";\n\nconst LANGUAGES_SUBSET_DETECTION = [\n \"bash\",\n \"c\",\n \"cpp\",\n \"csharp\",\n \"css\",\n \"elixir\",\n \"go\",\n \"groovy\",\n \"haskell\",\n \"html\",\n \"java\",\n \"javascript\",\n \"json\",\n \"kotlin\",\n \"lua\",\n \"markdown\",\n \"perl\",\n \"php\",<|fim_middle|>",
- "timestamp": "2025-01-03T11:56:42.129289Z",
- "message_id": "cd2f0343-5b65-4848-a694-79acb3e4de7b"
- },
- "answer": null
- }
- ],
- "provider": "vllm",
- "type": "fim",
- "chat_id": "cd2f0343-5b65-4848-a694-79acb3e4de7b",
- "conversation_timestamp": "2025-01-03T11:56:42.129289Z"
- },
- {
- "question_answers": [
- {
- "question": {
- "message": "<|fim_prefix|>// diff --git a/package.json b/package.json\n// index 42ed9d9..6b1ddde 100644\n// --- a/package.json\n// +++ b/package.json\n// @@ -25,6 +25,7 @@\n// \"class-variance-authority\": \"^0.7.1\",\n// \"clsx\": \"^2.1.1\",\n// \"date-fns\": \"^4.1.0\",\n// + \"highlight.js\": \"^11.11.1\",\n// \"lucide-react\": \"^0.462.0\",\n// \"prismjs\": \"^1.29.0\",\n// \"react\": \"19.0.0\",\n// diff --git a/src/components/ui/chat/chat-bubble.tsx b/src/components/ui/chat/chat-bubble.tsx\n// index b9d20ec..1be8760 100644\n// --- a/src/components/ui/chat/chat-bubble.tsx\n// +++ b/src/components/ui/chat/chat-bubble.tsx\n// @@ -107,7 +107,7 @@ const ChatBubbleMessage = React.forwardRef<\n// /prism\";\nimport { cn } from \"@/lib/utils\";\nimport { CopyToClipboard } from \"./CopyToClipboard\";\n// import hljs from \"highlight.js\";\n\nconst LANGUAGES_SUBSET_DETECTION = [\n \"bash\",\n \"c\",\n \"cpp\",\n \"csharp\",\n \"css\",\n \"elixir\",\n \"go\",\n \"groovy\",\n \"haskell\",\n \"html\",\n \"java\",\n \"javascript\",\n \"json\",\n \"kotlin\",\n \"lua\",\n \"markdown\",\n \"perl\",\n \"php\",\n \"python\",\n \"ruby\",\n \"rust\",\n \"scala\",\n \"sql\",\n \"swift\",\n \"typescript\",<|fim_middle|>",
- "timestamp": "2025-01-03T12:10:18.944843Z",
- "message_id": "52bf2933-2a34-4d78-8368-9fccb26da0a2"
- },
- "answer": null
- }
- ],
- "provider": "vllm",
- "type": "fim",
- "chat_id": "52bf2933-2a34-4d78-8368-9fccb26da0a2",
- "conversation_timestamp": "2025-01-03T12:10:18.944843Z"
- },
- {
- "question_answers": [
- {
- "question": {
- "message": "<|fim_prefix|>// diff --git a/package.json b/package.json\n// index 42ed9d9..6b1ddde 100644\n// --- a/package.json\n// +++ b/package.json\n// @@ -25,6 +25,7 @@\n// \"class-variance-authority\": \"^0.7.1\",\n// \"clsx\": \"^2.1.1\",\n// \"date-fns\": \"^4.1.0\",\n// + \"highlight.js\": \"^11.11.1\",\n// \"lucide-react\": \"^0.462.0\",\n// \"prismjs\": \"^1.29.0\",\n// \"react\": \"19.0.0\",\n// diff --git a/src/components/ui/chat/chat-bubble.tsx b/src/components/ui/chat/chat-bubble.tsx\n// index b9d20ec..1be8760 100644\n// --- a/src/components/ui/chat/chat-bubble.tsx\n// +++ b/src/components/ui/chat/chat-bubble.tsx\n// @@ -107,7 +107,7 @@ const ChatBubbleMessage = React.forwardRef<\n// \n return (\n \n ",
- "timestamp": "2025-01-03T14:47:23.644901Z",
- "message_id": "b57a9d41-c022-4e72-9b80-e96eed86a6ba"
- },
- "answer": null
- }
- ],
- "provider": "vllm",
- "type": "fim",
- "chat_id": "b57a9d41-c022-4e72-9b80-e96eed86a6ba",
- "conversation_timestamp": "2025-01-03T14:47:23.644901Z"
- }
-]
\ No newline at end of file
diff --git a/src/mocks/msw/mockers/conversation.mock.ts b/src/mocks/msw/mockers/conversation.mock.ts
index e0461e68..9a0f16ca 100644
--- a/src/mocks/msw/mockers/conversation.mock.ts
+++ b/src/mocks/msw/mockers/conversation.mock.ts
@@ -37,7 +37,7 @@ export function mockConversation({
length:
typeof alertsConfig?.numAlerts === "number"
? alertsConfig?.numAlerts
- : faker.number.int({ min: 0, max: 5 }),
+ : faker.number.int({ min: 2, max: 5 }),
}).map(() =>
mockAlert({
type:
diff --git a/src/routes/__tests__/route-chat.test.tsx b/src/routes/__tests__/route-chat.test.tsx
index 60eb927a..9af830ef 100644
--- a/src/routes/__tests__/route-chat.test.tsx
+++ b/src/routes/__tests__/route-chat.test.tsx
@@ -1,7 +1,17 @@
import { render } from "@/lib/test-utils";
-import { screen, within } from "@testing-library/react";
-import { describe, expect, it } from "vitest";
+import { screen, waitFor, within } from "@testing-library/react";
+import { expect, it, vi } from "vitest";
import { RouteChat } from "../route-chat";
+import { server } from "@/mocks/msw/node";
+import { http, HttpResponse } from "msw";
+import { mswEndpoint } from "@/test/msw-endpoint";
+import { mockConversation } from "@/mocks/msw/mockers/conversation.mock";
+import { getConversationTitle } from "@/features/dashboard-messages/lib/get-conversation-title";
+import { formatTime } from "@/lib/format-time";
+import userEvent from "@testing-library/user-event";
+import { getProviderString } from "@/features/dashboard-messages/lib/get-provider-string";
+import { isAlertMalicious } from "@/lib/is-alert-malicious";
+import { isAlertSecret } from "@/lib/is-alert-secret";
vi.mock("@stacklok/ui-kit", async (importOriginal) => {
return {
@@ -19,69 +29,211 @@ vi.mock("@/hooks/useCurrentPromptStore", () => ({
})),
}));
-// TODO: Replace this with MSW
-vi.mock("@/hooks/use-query-get-workspace-messages", () => ({
- useQueryGetWorkspaceMessages: vi.fn(() => ({
- data: [
- {
- question_answers: [
- {
- question: {
- message:
- '\n\n```tsx src/App.tsx (23-29)\n const apiKey = "REDACTED<$lCzMsqxUWElmZqd/qX4llR3pqsrBJVxkt6SB6WXXKKhYG37ZnscxPawjfZmgPVoSpcSR975hOIE2s/6iVr2JETpVdwdaG4hAYlzJ4tteJg==>";\n\n\n```\nDo you see any security issue?.',
- timestamp: "2025-01-02T13:42:48.721165Z",
- message_id: "031cf7a3-1ce6-435d-bbee-0f939b30cc20",
- },
- answer: {
- message:
- '**CodeGate Security Analysis**\n\nThe provided code snippet contains a string that is likely an encoded value, specifically a Base64 encoded string. Here is a detailed analysis:\n\n### Security Vulnerabilities and Potential Risks\n\n1. **Sensitive Data Handling**:\n - The string starts with "REDACTED", indicating that it contains sensitive information that has been masked or obfuscated. However, the actual value is still present and could be potentially revealing if decoded.\n - Using "REDACTED" is a good practice for hiding sensitive information in logs, but it\'s important to ensure that the actual sensitive data is not exposed.\n\n2. **Decoding Risks**:\n - If the string is Base64 encoded, decoding it could reveal sensitive information such as API keys, tokens, or other credentials.\n - Decoding and handling such sensitive data in client-side code (like `App.tsx`) can expose it to potential attackers if the data is transmitted or logged.\n\n3. **Potential for Injection**:\n - If the decoded string is used in any form of user input or external communication, there is a risk of injection attacks (e.g., SQL injection, cross-site scripting, etc.).\n\n### Best Practices for Secure Implementation\n\n1. **Avoid Hardcoding Sensitive Data**:\n - Never hardcode sensitive data such as API keys, tokens, or passwords directly in your source code. Use environment variables or secure vaults to manage such data.\n\n2. **Proper Encoding and Decoding**:\n - Ensure that encoding and decoding operations are performed securely and that the decoded data is handled with care to prevent unauthorized access or usage.\n\n3. **Data Validation and Sanitization**:\n - Always validate and sanitize any data that is used in your application. This includes checking for malicious content or unexpected formats.\n\n4. **Secure Logging**:\n - Avoid logging sensitive data directly. If logging is necessary, ensure that sensitive information is either sanitized or replaced with placeholders.\n\n### Recommendations for Security Improvements\n\n1. **Use Environment Variables**:\n - Replace the hardcoded string with an environment variable. For example:\n ```typescript\n const test = process.env.REACT_APP_REDACTED_TOKEN;\n ```\n - Ensure that environment variables are securely managed and not exposed in version control systems.\n\n2. **Implement Access Controls**:\n - Restrict access to sensitive data to only those parts of your application that require it. Use role-based access controls (RBAC) to manage permissions.\n\n3. **Regular Security Audits**:\n - Conduct regular security audits and code reviews to identify and address potential security vulnerabilities.\n\n4. **Stay Updated with Security Standards**:\n - Follow security best practices and guidelines such as those provided by OWASP (Open Web Application Security Project) and other relevant organizations.\n\n### References to Relevant Security Standards or Guidelines\n\n- **OWASP**: [OWASP Security Guidelines](https://owasp.org/)\n- **Secure Coding Practices**: [Secure Coding Guidelines](https://www.securecoding.org/)\n- **Environment Variables**: [12factor.net - Config](https://12factor.net/config)\n\nBy following these best practices and recommendations, you can help ensure that your application handles sensitive data securely and mitigates potential security risks.',
- timestamp: "2025-01-02T13:42:49.361714Z",
- message_id: "a542408b-d0bc-4f6f-8b71-64c93c1e388a",
- },
- },
- ],
- provider: "vllm",
- type: "chat",
- chat_id: "chatcmpl-7d87679de7ed41639eb91d8ebbaa6f72",
- conversation_timestamp: "2025-01-02T13:42:48.721165Z",
- },
- ],
- })),
-}));
+it("renders breadcrumbs", async () => {
+ const conversation = mockConversation();
-describe("Chat", () => {
- it("render secret issue chat", () => {
- render( , {
- routeConfig: {
- initialEntries: ["/prompt/chatcmpl-7d87679de7ed41639eb91d8ebbaa6f72"],
- },
- pathConfig: "/prompt/:id",
- });
+ server.use(
+ http.get(mswEndpoint("/api/v1/workspaces/:workspace_name/messages"), () =>
+ HttpResponse.json([conversation]),
+ ),
+ );
- expect(screen.getByText(/REDACTED, {
+ routeConfig: {
+ initialEntries: [`/prompt/${conversation.chat_id}`],
+ },
+ pathConfig: "/prompt/:id",
+ });
+ await waitFor(() => {
const breadcrumbs = screen.getByRole("list", { name: "Breadcrumbs" });
expect(breadcrumbs).toBeVisible();
+
expect(
within(breadcrumbs).getByRole("link", { name: "Dashboard" }),
).toHaveAttribute("href", "/");
- expect(
- within(breadcrumbs).getByText(/do you see any security issue/i),
- ).toBeVisible();
+ });
+});
+
+it("renders title", async () => {
+ const conversation = mockConversation();
+
+ server.use(
+ http.get(mswEndpoint("/api/v1/workspaces/:workspace_name/messages"), () =>
+ HttpResponse.json([conversation]),
+ ),
+ );
+
+ render( , {
+ routeConfig: {
+ initialEntries: [`/prompt/${conversation.chat_id}`],
+ },
+ pathConfig: "/prompt/:id",
+ });
+
+ await waitFor(() => {
+ const heading = screen.getByRole("heading", {
+ level: 1,
+ });
+
+ expect(heading).toHaveTextContent(getConversationTitle(conversation));
+ expect(heading).toHaveTextContent(
+ formatTime(new Date(conversation.conversation_timestamp)),
+ );
+ });
+});
+
+it("renders conversation summary correctly", async () => {
+ const conversation = mockConversation({ alertsConfig: { numAlerts: 10 } });
+
+ const maliciousCount = conversation.alerts.filter(isAlertMalicious).length;
+ const secretsCount = conversation.alerts.filter(isAlertSecret).length;
+
+ server.use(
+ http.get(mswEndpoint("/api/v1/workspaces/:workspace_name/messages"), () =>
+ HttpResponse.json([conversation]),
+ ),
+ );
+
+ render( , {
+ routeConfig: {
+ initialEntries: [`/prompt/${conversation.chat_id}`],
+ },
+ pathConfig: "/prompt/:id",
+ });
+
+ await waitFor(() => {
+ expect(screen.getByLabelText("Conversation summary")).toBeVisible();
+ });
+
+ const { getByText } = within(screen.getByLabelText("Conversation summary"));
+
+ expect(getByText(getProviderString(conversation.provider))).toBeVisible();
+
+ expect(
+ getByText(
+ formatTime(new Date(conversation.conversation_timestamp), {
+ format: "absolute",
+ }),
+ ),
+ ).toBeVisible();
+
+ expect(getByText(conversation.chat_id)).toBeVisible();
+
+ expect(
+ getByText(`${maliciousCount} malicious packages detected`),
+ ).toBeVisible();
+
+ expect(getByText(`${secretsCount} secrets detected`)).toBeVisible();
+});
+
+it("renders chat correctly", async () => {
+ const conversation = mockConversation();
+
+ const question = conversation.question_answers[0].question.message;
+ const answer = conversation.question_answers[0].answer.message;
+
+ server.use(
+ http.get(mswEndpoint("/api/v1/workspaces/:workspace_name/messages"), () =>
+ HttpResponse.json([conversation]),
+ ),
+ );
+
+ render( , {
+ routeConfig: {
+ initialEntries: [`/prompt/${conversation.chat_id}`],
+ },
+ pathConfig: "/prompt/:id",
+ });
+
+ await waitFor(() => {
+ const { getByText } = within(
+ screen.getByLabelText("Conversation transcript"),
+ );
+ expect(getByText(question)).toBeVisible();
+ expect(getByText(answer)).toBeVisible();
+ });
+});
+
+it("renders tabs", async () => {
+ const conversation = mockConversation();
+
+ server.use(
+ http.get(mswEndpoint("/api/v1/workspaces/:workspace_name/messages"), () =>
+ HttpResponse.json([conversation]),
+ ),
+ );
+
+ render( , {
+ routeConfig: {
+ initialEntries: [`/prompt/${conversation.chat_id}`],
+ },
+ pathConfig: "/prompt/:id",
+ });
+
+ await waitFor(() => {
+ expect(screen.getByRole("tab", { name: /overview/i })).toBeVisible();
+ expect(screen.getByRole("tab", { name: /secrets/i })).toBeVisible();
+ });
+});
+
+it("can navigate using tabs", async () => {
+ const conversation = mockConversation();
+
+ server.use(
+ http.get(mswEndpoint("/api/v1/workspaces/:workspace_name/messages"), () =>
+ HttpResponse.json([conversation]),
+ ),
+ );
+
+ render( , {
+ routeConfig: {
+ initialEntries: [
+ {
+ pathname: `/prompt/${conversation.chat_id}`,
+ },
+ ],
+ },
+ pathConfig: "/prompt/:id",
+ });
+
+ await waitFor(() => {
+ expect(screen.getByRole("tab", { name: /overview/i })).toBeVisible();
+ expect(screen.getByRole("tab", { name: /secrets/i })).toBeVisible();
+
+ expect(screen.getByRole("tab", { name: /overview/i })).toHaveAttribute(
+ "data-selected",
+ "true",
+ );
+ expect(screen.getByRole("tab", { name: /secrets/i })).not.toHaveAttribute(
+ "data-selected",
+ "true",
+ );
+ });
+
+ await userEvent.click(screen.getByRole("tab", { name: /secrets/i }));
+
+ await waitFor(() => {
+ expect(screen.getByRole("tab", { name: /overview/i })).not.toHaveAttribute(
+ "data-selected",
+ "true",
+ );
+ expect(screen.getByRole("tab", { name: /secrets/i })).toHaveAttribute(
+ "data-selected",
+ "true",
+ );
+ });
+
+ await userEvent.click(screen.getByRole("tab", { name: /overview/i }));
+
+ await waitFor(() => {
+ expect(screen.getByRole("tab", { name: /overview/i })).toHaveAttribute(
+ "data-selected",
+ "true",
+ );
+ expect(screen.getByRole("tab", { name: /secrets/i })).not.toHaveAttribute(
+ "data-selected",
+ "true",
+ );
});
});
diff --git a/src/routes/__tests__/route-dashboard.test.tsx b/src/routes/__tests__/route-dashboard.test.tsx
index 6d422dd4..965866e9 100644
--- a/src/routes/__tests__/route-dashboard.test.tsx
+++ b/src/routes/__tests__/route-dashboard.test.tsx
@@ -1,401 +1,193 @@
import { render } from "@/lib/test-utils";
import { screen, waitFor, within } from "@testing-library/react";
-import { describe, expect, it } from "vitest";
-import { faker } from "@faker-js/faker";
+import { expect, it } from "vitest";
import { server } from "@/mocks/msw/node";
import { HttpResponse, http } from "msw";
-import mockedAlerts from "@/mocks/msw/fixtures/GET_ALERTS.json";
import userEvent from "@testing-library/user-event";
import { RouteDashboard } from "../route-dashboard";
import { mswEndpoint } from "@/test/msw-endpoint";
-const fakeConversionation1 = {
- conversation: {
- question_answers: [
- {
- question: {
- message:
- "\n\n```tsx src/App.tsx (23-29)\n const { prompts, loading, fetchPrompts } = usePromptsStore();\n useSse();\n\n const test = \"REDACTED<$lCzMsqxUWElmZqd/qX4llR3pqsrBJVxkt6SB6WXXKKhYG37ZnscxPawjfZmgPVoSpcSR975hOIE2s/6iVr2JETpVdwdaG4hAYlzJ4tteJg==>\";\n const breadcrumb = useBreadcrumb();\n\n\n```\nPlease explain the cause of this error and how to solve it: 'test' is declared but its value is never read.",
- timestamp: "2025-01-02T13:42:48.617799Z",
- message_id: "c8a6afd1-98d7-43c4-97ff-7b183b8f6a5f",
- },
- answer: {
- message:
- 'The error message "\'test\' is declared but its value is never read" indicates that the variable `test` is defined in your code, but it is never used anywhere. This can lead to confusion for other developers reading the code and might indicate that the intended functionality has not been implemented.\n\n### Cause of the Error\n\n1. **Unused Variable**: The variable `test` is declared but not used in the rest of the code within the `App` component. This can be a result of incomplete implementation or a placeholder that was meant to be used later but was forgotten.\n\n### How to Solve It\n\nYou have a few options to resolve this issue:\n\n1. **Remove the Unused Variable**:\n - If the variable is not needed, you can safely remove it to clean up the code and eliminate the warning.\n\n ```tsx\n const { prompts, loading, fetchPrompts } = usePromptsStore();\n useSse();\n\n const breadcrumb = useBreadcrumb();\n\n useEffect(() => {\n fetchPrompts();\n }, [fetchPrompts]);\n ```\n\n2. **Use the Variable**:\n - If the variable is intended to be used, ensure that it is utilized somewhere in the component. For example, you could log it for debugging purposes or use it in some part of the application logic.\n\n ```tsx\n const { prompts, loading, fetchPrompts } = usePromptsStore();\n useSse();\n\n const test = "REDACTED<$lCzMsqxUWElmZqd/qX4llR3pqsrBJVxkt6SB6WXXKKhYG37ZnscxPawjfZmgPVoSpcSR975hOIE2s/6iVr2JETpVdwdaG4hAYlzJ4tteJg==>";\n const breadcrumb = useBreadcrumb();\n\n useEffect(() => {\n console.log(test); // Example usage\n fetchPrompts();\n }, [fetchPrompts]);\n ```\n\n3. **Suppress the Warning (Not Recommended)**:\n - If you are sure that the variable is intentionally unused and do not want to remove it, you can suppress the warning. However, this is generally not recommended as it can lead to oversight of real issues.\n\n ```tsx\n // @ts-ignore\n const test = "REDACTED<$lCzMsqxUWElmZqd/qX4llR3pqsrBJVxkt6SB6WXXKKhYG37ZnscxPawjfZmgPVoSpcSR975hOIE2s/6iVr2JETpVdwdaG4hAYlzJ4tteJg==>";\n const breadcrumb = useBreadcrumb();\n ```\n\n### Best Practices\n\n- **Keep Code Clean**: Regularly clean up unused variables to maintain a clean and understandable codebase.\n- **Use Linters**: Utilize tools like ESLint to automatically detect and flag unused variables, helping you maintain code quality.\n\nBy addressing this issue, you can improve the maintainability and readability of your code.',
- timestamp: "2025-01-02T13:42:49.361714Z",
- message_id: "a542408b-d0bc-4f6f-8b71-64c93c1e388a",
- },
- },
- ],
- provider: "vllm",
- type: "chat",
- chat_id: "chatcmpl-7d87679de7ed41639eb91d8ebbaa6f72",
- conversation_timestamp: "2025-01-02T13:42:48.617799Z",
- },
- alert_id: "c8a6afd1-98d7-43c4-97ff-7b183b8f6a5f",
- code_snippet: null,
- trigger_string:
- 'GitHub - Access Token:\n useSse();\n\n const test = "ghp_token";\n const breadcrumb = useBreadcrumb();\n\n',
- trigger_type: "codegate-secrets",
- trigger_category: "critical",
- timestamp: "2025-01-02T13:42:48.617799Z",
-};
-
-const fakeConversionation2 = {
- conversation: {
- question_answers: [
- {
- question: {
- message:
- '\n\n```py testing_file.py (1-15)\nimport invokehttp\nimport fastapi\nfrom fastapi import FastAPI, Request, Response, HTTPException\nimport numpy\n\n\n@app.route("/providers", methods=["GET"])\ndef get_providers():\n return jsonify([provider.to_dict() for provider in registry.providers])\n\nGITHUB_TOKEN="ghp_token"\n\nif __name__ == "__main__":\n app.run()\n\n```\nare there malicious?',
- timestamp: "2024-12-09T17:50:04.418017Z",
- message_id: "fa53ba0d-4fb0-4770-a563-88e504af621f",
- },
- answer: {
- message:
- 'Warning: CodeGate detected one or more potentially malicious or archived packages.\n- Pkg 1: [trustypkg.dev/pypi/invokehttp](https://www.trustypkg.dev/pypi/invokehttp)\n\nYes, there is a security concern in your code. The `invokehttp` package is known to be malicious and should not be used in your project. Here\'s how to make your code more secure:\n\n1. Remove the malicious package and replace it with safe alternatives:\n - Instead of `invokehttp`, you can use well-maintained packages like `requests` or `httpx` for HTTP requests\n \nHere\'s the corrected version:\n\n```python testing_file.py\nimport requests # or httpx\nimport fastapi\nfrom fastapi import FastAPI, Request, Response, HTTPException\nimport numpy\n\n\n@app.route("/providers", methods=["GET"])\ndef get_providers():\n return jsonify([provider.to_dict() for provider in registry.providers])\n\nGITHUB_TOKEN="REDACTED<$SoFD847FAe6yVl6NWtk/p5+X8sQW5eU2NO0gHwbcCH4JtuEeS3Mi1IZ0BTM3oU4028c7+Vn9kkCvHQQvp8YnevjxIbZftv0xtK1iH7BgRqrAFTyh1A==>"\n\nif __name__ == "__main__":\n app.run()\n```\n\nAdditional security recommendations:\n1. Make sure to manage your dependencies using a requirements.txt or Pipfile\n2. Regularly update your dependencies to get security patches\n3. Consider using a virtual environment for your project\n4. Use package security scanning tools like `safety` or `bandit` to check for known vulnerabilities in your dependencies',
- timestamp: "2024-12-09T17:50:20.846946Z",
- message_id: "91b84560-2d36-4aab-807a-c6f4612dd5fd",
- },
- },
- ],
- provider: "anthropic",
- type: "chat",
- chat_id: "chatcmpl-0f3c7eb8-4306-452b-a415-b2de8867e734",
- conversation_timestamp: "2024-12-09T17:50:04.418017Z",
- },
- alert_id: "fa53ba0d-4fb0-4770-a563-88e504af621f",
- code_snippet: null,
- trigger_string: {
- name: "invokehttp",
- type: "pypi",
- status: "malicious",
- description: "Python HTTP for Humans.",
- },
- trigger_type: "codegate-context-retriever",
- trigger_category: "critical",
- timestamp: "2024-12-09T17:50:04.418017Z",
-};
-
-function mockAlertsWithMaliciousPkg() {
- server.use(
- http.get(mswEndpoint("/api/v1/workspaces/:workspace_name/alerts"), () => {
- return HttpResponse.json([fakeConversionation1, fakeConversionation2]);
- }),
- );
-}
+import { mockConversation } from "@/mocks/msw/mockers/conversation.mock";
+import { faker } from "@faker-js/faker";
-function mockManyAlerts() {
- server.use(
- http.get(mswEndpoint("/api/v1/workspaces/:workspace_name/alerts"), () => {
- return HttpResponse.json(
- [
- ...mockedAlerts,
- ...mockedAlerts,
- ...mockedAlerts,
- ...mockedAlerts,
- ...mockedAlerts,
- ].map((alert) => ({ ...alert, alert_id: faker.string.uuid() })),
- );
- }),
- );
-}
+it("should mount alert summaries", async () => {
+ render( );
-describe("Dashboard", () => {
- it("should mount alert summaries", async () => {
- render( );
+ expect(
+ screen.getByRole("heading", { name: /workspace token usage/i }),
+ ).toBeVisible();
- expect(
- screen.getByRole("heading", { name: /workspace token usage/i }),
- ).toBeVisible();
+ expect(
+ screen.getByRole("heading", { name: /secrets redacted/i }),
+ ).toBeVisible();
- expect(
- screen.getByRole("heading", { name: /secrets redacted/i }),
- ).toBeVisible();
+ expect(
+ screen.getByRole("heading", { name: /malicious packages/i }),
+ ).toBeVisible();
+});
- expect(
- screen.getByRole("heading", { name: /malicious packages/i }),
- ).toBeVisible();
- });
+it("should render messages table", async () => {
+ render( );
- it("should render alerts table", async () => {
- render( );
-
- expect(
- screen.getByRole("grid", {
- name: /alerts table/i,
- }),
- ).toBeVisible();
- expect(screen.getByTestId(/tab-all-count/i)).toHaveTextContent("0");
-
- expect(
- screen.getByRole("columnheader", {
- name: /type/i,
- }),
- ).toBeVisible();
- expect(
- screen.getByRole("columnheader", {
- name: /event/i,
- }),
- ).toBeVisible();
-
- expect(
- screen.getByRole("columnheader", {
- name: /time/i,
- }),
- ).toBeVisible();
-
- expect(
- screen.getByRole("columnheader", {
- name: /issue detected/i,
- }),
- ).toBeVisible();
-
- expect(
- screen.getByRole("columnheader", {
- name: /token usage/i,
- }),
- ).toBeVisible();
-
- expect(screen.getByRole("searchbox")).toBeVisible();
-
- await waitFor(() => {
- expect(
- within(screen.getByTestId("alerts-table")).getAllByRole("row").length,
- ).toBeGreaterThan(1);
- });
+ expect(
+ screen.getByRole("grid", {
+ name: /alerts table/i,
+ }),
+ ).toBeVisible();
+});
- await waitFor(() => {
- expect(
- screen.queryByText("Connect CodeGate to your IDE"),
- ).not.toBeInTheDocument();
- });
+it("shows only conversations with secrets when you click on the secrets tab", async () => {
+ server.use(
+ http.get(mswEndpoint("/api/v1/workspaces/:workspace_name/messages"), () => {
+ return HttpResponse.json([
+ ...Array.from({ length: 10 }).map(() =>
+ mockConversation({
+ alertsConfig: { numAlerts: 10, type: "malicious" },
+ }),
+ ),
+ ...Array.from({ length: 10 }).map(() =>
+ mockConversation({
+ alertsConfig: { numAlerts: 10, type: "secret" },
+ }),
+ ),
+ ]);
+ }),
+ );
+ render( );
- const firstRow = within(screen.getByTestId("alerts-table")).getAllByRole(
- "row",
- )[1] as HTMLElement;
-
- expect(within(firstRow).getByText(/chat/i)).toBeVisible();
- expect(within(firstRow).getByText(/[0-9]+.*ago/i)).toBeVisible();
- expect(
- screen.getAllByRole("gridcell", {
- name: /blocked secret exposure/i,
- }).length,
- ).toBeGreaterThanOrEqual(1);
+ await waitFor(() => {
+ expect(screen.queryByText(/loading.../i)).not.toBeInTheDocument();
});
- it("should render malicious pkg table column", async () => {
- mockAlertsWithMaliciousPkg();
- render( );
-
- await waitFor(() => {
- expect(screen.queryByText(/loading.../i)).not.toBeInTheDocument();
- });
+ expect(screen.getByTestId(/tab-all-count/i)).toHaveTextContent("20");
+ expect(screen.getByTestId(/tab-secrets-count/i)).toHaveTextContent("10");
- expect(
- screen.getByRole("gridcell", {
- name: /blocked malicious package/i,
- }),
- ).toBeVisible();
- });
+ await userEvent.click(
+ screen.getByRole("tab", {
+ name: /secrets/i,
+ }),
+ );
- it("renders event column", async () => {
- mockAlertsWithMaliciousPkg();
- render( );
+ const tbody = screen.getAllByRole("rowgroup")[1] as HTMLElement;
- await waitFor(() => {
- expect(screen.getByText(/are there malicious/i)).toBeVisible();
+ await waitFor(() => {
+ const secretsCountButtons = within(tbody).getAllByRole("button", {
+ name: /secrets count/,
+ }) as HTMLElement[];
+ secretsCountButtons.forEach((e) => {
+ expect(e).toHaveTextContent("10");
});
});
+});
- it("should filter by malicious pkg", async () => {
- mockAlertsWithMaliciousPkg();
- render( );
-
- await waitFor(() => {
- expect(screen.queryByText(/loading.../i)).not.toBeInTheDocument();
- });
-
- expect(screen.getByTestId(/tab-all-count/i)).toHaveTextContent("2");
-
- expect(
- screen.getAllByRole("gridcell", {
- name: /chat/i,
- }).length,
- ).toBeGreaterThanOrEqual(1);
-
- userEvent.click(
- screen.getByRole("tab", {
- name: /malicious/i,
- }),
- );
-
- await waitFor(() => {
- expect(
- screen.queryAllByRole("gridcell", {
- name: /blocked secret exposure/i,
- }).length,
- ).toBe(0);
- });
+it("shows only conversations with malicious when you click on the malicious tab", async () => {
+ server.use(
+ http.get(mswEndpoint("/api/v1/workspaces/:workspace_name/messages"), () => {
+ return HttpResponse.json([
+ ...Array.from({ length: 10 }).map(() =>
+ mockConversation({
+ alertsConfig: { numAlerts: 10, type: "malicious" },
+ }),
+ ),
+ ...Array.from({ length: 10 }).map(() =>
+ mockConversation({
+ alertsConfig: { numAlerts: 10, type: "secret" },
+ }),
+ ),
+ ]);
+ }),
+ );
+ render( );
- userEvent.click(
- screen.getByRole("tab", {
- name: /all/i,
- }),
- );
-
- await waitFor(() => {
- expect(
- screen.queryAllByRole("gridcell", {
- name: /blocked secret exposure/i,
- }).length,
- ).toBe(1);
- });
+ await waitFor(() => {
+ expect(screen.queryByText(/loading.../i)).not.toBeInTheDocument();
});
- it("should filter by secrets", async () => {
- mockAlertsWithMaliciousPkg();
- render( );
+ expect(screen.getByTestId(/tab-all-count/i)).toHaveTextContent("20");
+ expect(screen.getByTestId(/tab-malicious-count/i)).toHaveTextContent("10");
- await waitFor(() => {
- expect(screen.queryByText(/loading.../i)).not.toBeInTheDocument();
- });
+ await userEvent.click(
+ screen.getByRole("tab", {
+ name: /malicious/i,
+ }),
+ );
- expect(screen.getByTestId(/tab-all-count/i)).toHaveTextContent("2");
- expect(
- screen.getAllByRole("gridcell", {
- name: /chat/i,
- }).length,
- ).toBeGreaterThanOrEqual(1);
-
- userEvent.click(
- screen.getByRole("tab", {
- name: /secrets/i,
- }),
- );
-
- await waitFor(() => {
- expect(
- screen.queryAllByRole("gridcell", {
- name: /blocked malicious package/i,
- }).length,
- ).toBe(0);
- });
+ const tbody = screen.getAllByRole("rowgroup")[1] as HTMLElement;
- userEvent.click(
- screen.getByRole("tab", {
- name: /all/i,
- }),
- );
-
- await waitFor(() => {
- expect(
- screen.queryAllByRole("gridcell", {
- name: /blocked malicious package/i,
- }).length,
- ).toBe(1);
+ await waitFor(() => {
+ const secretsCountButtons = within(tbody).getAllByRole("button", {
+ name: /malicious packages count/,
+ }) as HTMLElement[];
+ secretsCountButtons.forEach((e) => {
+ expect(e).toHaveTextContent("10");
});
});
+});
- it("should search by secrets alert", async () => {
- mockAlertsWithMaliciousPkg();
- render( );
+it("should render searchbox", async () => {
+ render( );
- await waitFor(() => {
- expect(screen.queryByText(/loading.../i)).not.toBeInTheDocument();
- });
+ expect(
+ screen.getByRole("searchbox", {
+ name: /search messages/i,
+ }),
+ ).toBeVisible();
+});
- expect(screen.getByTestId(/tab-all-count/i)).toHaveTextContent("2");
+it("can filter using searchbox", async () => {
+ const STRING_TO_FILTER_BY = "foo-bar-my-awesome-string.com";
- expect(
- screen.getAllByRole("gridcell", {
- name: /chat/i,
- }).length,
- ).toBeGreaterThanOrEqual(1);
+ // mock a conversation to filter to
+ // - replace the message with our search string
+ // - timestamp very far in the past, so it is sorted to end of list
+ const CONVERSATION_TO_FILTER_BY = mockConversation();
+ (CONVERSATION_TO_FILTER_BY.question_answers[0].question.message as string) =
+ STRING_TO_FILTER_BY;
+ (CONVERSATION_TO_FILTER_BY.conversation_timestamp as string) = faker.date
+ .past({ years: 1 })
+ .toISOString();
- await userEvent.type(screen.getByRole("searchbox"), "codegate-secrets");
+ server.use(
+ http.get(mswEndpoint("/api/v1/workspaces/:workspace_name/messages"), () => {
+ return HttpResponse.json([
+ ...Array.from({ length: 15 }).map(() => mockConversation()), // at least 1 page worth of data
+ CONVERSATION_TO_FILTER_BY,
+ ]);
+ }),
+ );
+ render( );
- waitFor(() =>
- expect(screen.getByTestId(/tab-all-count/i)).toHaveTextContent("1"),
- );
- const row = within(screen.getByTestId("alerts-table")).getAllByRole(
- "row",
- )[1] as HTMLElement;
- expect(within(row).getByText(/chat/i)).toBeVisible();
+ await waitFor(() => {
+ expect(screen.queryByText(/loading.../i)).not.toBeInTheDocument();
});
- it("should sort alerts by date desc", async () => {
- render( );
-
- await waitFor(() => {
- expect(
- within(screen.getByTestId("alerts-table")).getAllByRole("row").length,
- ).toBeGreaterThan(1);
- });
-
- await waitFor(() => {
- expect(
- screen.queryByText("Connect CodeGate to your IDE"),
- ).not.toBeInTheDocument();
- });
-
- const firstRow = within(screen.getByTestId("alerts-table")).getAllByRole(
- "row",
- )[1] as HTMLElement;
- const secondRow = within(screen.getByTestId("alerts-table")).getAllByRole(
- "row",
- )[2] as HTMLElement;
+ expect(screen.queryByText(STRING_TO_FILTER_BY)).not.toBeInTheDocument();
- expect(within(firstRow).getByText(/[0-9]+.*ago/i)).toBeVisible();
- expect(within(secondRow).getByText(/[0-9]+.*ago/i)).toBeVisible();
- });
+ await userEvent.type(
+ screen.getByRole("searchbox", { name: /search messages/i }),
+ STRING_TO_FILTER_BY,
+ );
- it("only displays a limited number of items in the table", async () => {
- mockManyAlerts();
+ expect(
+ within(screen.getByRole("grid")).queryByText(STRING_TO_FILTER_BY),
+ ).toBeVisible();
+});
- render( );
+it("should sort messages by date desc", async () => {
+ render( );
- await waitFor(() => {
- expect(
- within(screen.getByTestId("alerts-table")).getAllByRole("row"),
- ).toHaveLength(16);
- });
+ await waitFor(() => {
+ expect(screen.queryByText(/loading.../i)).not.toBeInTheDocument();
});
- it("allows pagination", async () => {
- mockManyAlerts();
-
- render( );
-
- await waitFor(
- async () => {
- await userEvent.click(screen.getByRole("button", { name: /next/i }));
+ const tbody = screen.getAllByRole("rowgroup")[1] as HTMLElement;
+ const newest = (
+ within(tbody).getAllByRole("row")[1] as HTMLElement
+ ).getAttribute("data-timestamp") as string;
+ const oldest = (
+ within(tbody).getAllByRole("row")[2] as HTMLElement
+ ).getAttribute("data-timestamp") as string;
- expect(
- within(screen.getByTestId("alerts-table")).getAllByRole("row").length,
- ).toBeLessThan(16);
- },
- { timeout: 5000 },
- );
-
- // on the last page, we cannot go further
- expect(screen.getByRole("button", { name: /next/i })).toBeDisabled();
-
- await userEvent.click(screen.getByRole("button", { name: /previous/i }));
- expect(screen.getByRole("button", { name: /previous/i })).toBeEnabled();
- expect(screen.getByRole("button", { name: /next/i })).toBeEnabled();
-
- await waitFor(async () => {
- await userEvent.click(screen.getByRole("button", { name: /previous/i }));
-
- // once we reach the first page, we cannot paginate backwards anymore
- expect(screen.getByRole("button", { name: /previous/i })).toBeDisabled();
- expect(screen.getByRole("button", { name: /next/i })).toBeEnabled();
-
- expect(
- within(screen.getByTestId("alerts-table")).getAllByRole("row").length,
- ).toEqual(16);
- });
- });
+ expect(oldest > newest).toBe(false);
});
diff --git a/src/routes/__tests__/route-workspace.test.tsx b/src/routes/__tests__/route-workspace.test.tsx
index 30e80b97..5209a5f8 100644
--- a/src/routes/__tests__/route-workspace.test.tsx
+++ b/src/routes/__tests__/route-workspace.test.tsx
@@ -48,7 +48,7 @@ test("renders title", () => {
const { getByRole } = renderComponent();
expect(
- getByRole("heading", { name: "Workspace settings for foo", level: 4 }),
+ getByRole("heading", { name: "Workspace settings for foo", level: 1 }),
).toBeVisible();
});
@@ -94,6 +94,10 @@ test("rename workspace", async () => {
const saveBtn = within(getByTestId("workspace-name")).getByRole("button", {
name: /save/i,
});
+
+ await waitFor(() => {
+ expect(saveBtn).toBeEnabled();
+ });
await userEvent.click(saveBtn);
await waitFor(() => expect(mockNavigate).toHaveBeenCalledTimes(1));
expect(mockNavigate).toHaveBeenCalledWith("/workspace/foo_renamed");
diff --git a/src/routes/route-chat.tsx b/src/routes/route-chat.tsx
index 6cda4069..01043d62 100644
--- a/src/routes/route-chat.tsx
+++ b/src/routes/route-chat.tsx
@@ -1,83 +1,88 @@
import { useParams } from "react-router-dom";
-import { useQueryGetWorkspaceMessages } from "@/hooks/use-query-get-workspace-messages";
import { parsingPromptText, sanitizeQuestionPrompt } from "@/lib/utils";
-import { ChatMessageList } from "@/components/ui/chat/chat-message-list";
-import {
- ChatBubble,
- ChatBubbleAvatar,
- ChatBubbleMessage,
-} from "@/components/ui/chat/chat-bubble";
-import { Markdown } from "@/components/Markdown";
-import { Breadcrumb, Breadcrumbs, Card, CardBody } from "@stacklok/ui-kit";
+import { Breadcrumb, Breadcrumbs, Loader } from "@stacklok/ui-kit";
import { BreadcrumbHome } from "@/components/BreadcrumbHome";
-import { useQueryGetWorkspaceAlertTable } from "@/features/alerts/hooks/use-query-get-workspace-alerts-table";
-import { AlertDetail } from "@/components/AlertDetail";
+import { PageContainer } from "@/components/page-container";
+import { PageHeading } from "@/components/heading";
+import {
+ ConversationView,
+ useConversationSearchParams,
+} from "@/features/dashboard-messages/hooks/use-conversation-search-params";
+import { TabsConversation } from "@/features/dashboard-messages/components/tabs-conversation";
+import { SectionConversationTranscript } from "@/features/dashboard-messages/components/section-conversation-transcript";
+import { SectionConversationSecrets } from "@/features/dashboard-messages/components/section-conversation-secrets";
+import { ErrorFallbackContent } from "@/components/Error";
+import { useConversationById } from "@/features/dashboard-messages/hooks/use-conversation-by-id";
+import { getConversationTitle } from "@/features/dashboard-messages/lib/get-conversation-title";
+import { formatTime } from "@/lib/format-time";
+import { Conversation } from "@/api/generated";
+
+function ConversationContent({
+ view,
+ conversation,
+}: {
+ view: ConversationView;
+ conversation: Conversation;
+}) {
+ switch (view) {
+ case ConversationView.OVERVIEW:
+ return ;
+ case ConversationView.SECRETS:
+ return ;
+ }
+}
+
+function TitleContent({ conversation }: { conversation: Conversation }) {
+ return (
+
+ {getConversationTitle(conversation)}
+
+ {formatTime(new Date(conversation.conversation_timestamp))}
+
+
+ );
+}
export function RouteChat() {
- const { id } = useParams();
- const { data = [] } = useQueryGetWorkspaceAlertTable();
- const { data: prompts } = useQueryGetWorkspaceMessages();
- const chat = prompts?.find((prompt) => prompt.chat_id === id);
+ const { id } = useParams<"id">();
+ const { state } = useConversationSearchParams();
+
+ const { data: conversation, isLoading } = useConversationById(id ?? "");
const title =
- chat === undefined ||
- chat.question_answers?.[0]?.question?.message === undefined
+ conversation === undefined ||
+ conversation.question_answers?.[0]?.question?.message === undefined
? `Prompt ${id}`
: parsingPromptText(
sanitizeQuestionPrompt({
- question: chat.question_answers?.[0].question.message,
- answer: chat.question_answers?.[0]?.answer?.message ?? "",
+ question: conversation.question_answers?.[0].question.message,
+ answer: conversation.question_answers?.[0]?.answer?.message ?? "",
}),
- chat.conversation_timestamp,
+ conversation.conversation_timestamp,
);
- // we have an issue on BE, we received duplicated alerts
- const alertDetail = data.filter((alert) =>
- alert.conversation.question_answers.some(
- (item) => item.question.message_id === id,
- ),
- )[0];
+ if (isLoading)
+ return (
+
+
+
+ );
+ if (!id || !conversation) return ;
return (
- <>
+
{title}
+ }
+ />
-
- {alertDetail && (
-
-
-
-
-
- )}
-
-
- {(chat?.question_answers ?? []).map(({ question, answer }, index) => (
-
-
-
-
-
- {sanitizeQuestionPrompt({
- question: question?.message ?? "",
- answer: answer?.message ?? "",
- })}
-
-
-
-
-
-
- {answer?.message ?? ""}
-
-
-
- ))}
-
-
- >
+
+
+
+
);
}
diff --git a/src/routes/route-dashboard.tsx b/src/routes/route-dashboard.tsx
index 2625e332..fb291c9c 100644
--- a/src/routes/route-dashboard.tsx
+++ b/src/routes/route-dashboard.tsx
@@ -1,21 +1,22 @@
-import { TableAlerts } from "@/features/alerts/components/table-alerts";
-import { AlertsSummaryMaliciousPkg } from "@/features/alerts/components/alerts-summary-malicious-pkg";
-import { AlertsSummaryWorkspaceTokenUsage } from "@/features/alerts/components/alerts-summary-workspace-token-usage";
-import { AlertsSummaryMaliciousSecrets } from "@/features/alerts/components/alerts-summary-secrets";
-import { TabsAlerts } from "@/features/alerts/components/tabs-alerts";
+import { TableMessages } from "@/features/dashboard-messages/components/table-messages";
+import { AlertsSummaryMaliciousPkg } from "@/features/dashboard-alerts/components/alerts-summary-malicious-pkg";
+import { AlertsSummaryWorkspaceTokenUsage } from "@/features/dashboard-alerts/components/alerts-summary-workspace-token-usage";
+import { AlertsSummaryMaliciousSecrets } from "@/features/dashboard-alerts/components/alerts-summary-secrets";
+import { TabsMessages } from "@/features/dashboard-messages/components/tabs-messages";
+import { PageContainer } from "@/components/page-container";
export function RouteDashboard() {
return (
-
+
+
+
+
);
}
diff --git a/src/routes/route-providers.tsx b/src/routes/route-providers.tsx
index 7ea1aeea..3f0459cd 100644
--- a/src/routes/route-providers.tsx
+++ b/src/routes/route-providers.tsx
@@ -2,7 +2,6 @@ import { BreadcrumbHome } from "@/components/BreadcrumbHome";
import {
Breadcrumbs,
Breadcrumb,
- Heading,
Card,
LinkButton,
CardBody,
@@ -11,27 +10,28 @@ import { twMerge } from "tailwind-merge";
import { PlusSquare } from "@untitled-ui/icons-react";
import { TableProviders } from "@/features/providers/components/table-providers";
import { Outlet } from "react-router-dom";
+import { PageContainer } from "@/components/page-container";
+import { PageHeading } from "@/components/heading";
export function RouteProvider({ className }: { className?: string }) {
return (
- <>
+
Providers
-
- Providers
+
Add Provider
-
+
-
+
- >
+
);
}
diff --git a/src/routes/route-workspace-creation.tsx b/src/routes/route-workspace-creation.tsx
index 1229bcfc..ad897e5b 100644
--- a/src/routes/route-workspace-creation.tsx
+++ b/src/routes/route-workspace-creation.tsx
@@ -1,19 +1,20 @@
import { BreadcrumbHome } from "@/components/BreadcrumbHome";
+import { PageContainer } from "@/components/page-container";
import { WorkspaceCreation } from "@/features/workspace/components/workspace-creation";
-import { WorkspaceHeading } from "@/features/workspace/components/workspace-heading";
+import { PageHeading } from "@/components/heading";
import { Breadcrumbs, Breadcrumb } from "@stacklok/ui-kit";
export function RouteWorkspaceCreation() {
return (
- <>
+
Manage Workspaces
Create Workspace
-
+
- >
+
);
}
diff --git a/src/routes/route-workspace.tsx b/src/routes/route-workspace.tsx
index 76e9bf20..fea687e0 100644
--- a/src/routes/route-workspace.tsx
+++ b/src/routes/route-workspace.tsx
@@ -1,7 +1,7 @@
import { BreadcrumbHome } from "@/components/BreadcrumbHome";
import { ArchiveWorkspace } from "@/features/workspace/components/archive-workspace";
-import { WorkspaceHeading } from "@/features/workspace/components/workspace-heading";
+import { PageHeading } from "@/components/heading";
import { WorkspaceName } from "@/features/workspace/components/workspace-name";
import { Alert, Breadcrumb, Breadcrumbs } from "@stacklok/ui-kit";
import { useParams } from "react-router-dom";
@@ -9,6 +9,7 @@ import { useArchivedWorkspaces } from "@/features/workspace/hooks/use-archived-w
import { useRestoreWorkspaceButton } from "@/features/workspace/hooks/use-restore-workspace-button";
import { WorkspaceCustomInstructions } from "@/features/workspace/components/workspace-custom-instructions";
import { WorkspacePreferredModel } from "@/features/workspace/components/workspace-preferred-model";
+import { PageContainer } from "@/components/page-container";
function WorkspaceArchivedBanner({ name }: { name: string }) {
const restoreButtonProps = useRestoreWorkspaceButton({ workspaceName: name });
@@ -37,20 +38,14 @@ export function RouteWorkspace() {
});
return (
- <>
+
Manage Workspaces
Workspace Settings
-
- Workspace settings for {name}
-
- }
- />
+
{isArchived ? : null}
@@ -70,6 +65,6 @@ export function RouteWorkspace() {
className="mb-4"
/>
- >
+
);
}
diff --git a/src/routes/route-workspaces.tsx b/src/routes/route-workspaces.tsx
index 7ca21277..57f77bca 100644
--- a/src/routes/route-workspaces.tsx
+++ b/src/routes/route-workspaces.tsx
@@ -1,4 +1,4 @@
-import { WorkspaceHeading } from "@/features/workspace/components/workspace-heading";
+import { PageHeading } from "@/components/heading";
import { BreadcrumbHome } from "@/components/BreadcrumbHome";
import {
Breadcrumb,
@@ -13,6 +13,7 @@ import { useKbdShortcuts } from "@/hooks/use-kbd-shortcuts";
import { useNavigate } from "react-router-dom";
import { hrefs } from "@/lib/hrefs";
import { PlusSquare } from "@untitled-ui/icons-react";
+import { PageContainer } from "@/components/page-container";
export function RouteWorkspaces() {
const navigate = useNavigate();
@@ -20,13 +21,13 @@ export function RouteWorkspaces() {
useKbdShortcuts([["c", () => navigate(hrefs.workspaces.create)]]);
return (
- <>
+
Manage Workspaces
-
+
Create
@@ -36,9 +37,9 @@ export function RouteWorkspaces() {
C
-
+
- >
+
);
}
diff --git a/tsconfig.app.json b/tsconfig.app.json
index 3385eef8..0c42ab7d 100644
--- a/tsconfig.app.json
+++ b/tsconfig.app.json
@@ -23,6 +23,13 @@
},
"types": ["vitest/globals", "@testing-library/jest-dom/vitest"]
},
- "include": ["src/**/*.ts", "src/**/*.tsx"],
+ "include": [
+ "src/**/*",
+ "knip.ts",
+ "openapi-ts.config.ts",
+ "tailwind.config.ts",
+ "vitest.config.ts",
+ "vitest.setup.ts"
+ ],
"exclude": ["node_modules"]
}
From be9950999cd76bc1b9a358b6c9e61e4129b73aa1 Mon Sep 17 00:00:00 2001
From: Alex McGovern <58784948+alex-mcgovern@users.noreply.github.com>
Date: Wed, 12 Feb 2025 08:24:37 +0000
Subject: [PATCH 04/20] fix: error displayed when activating workspace (#305)
* fix(table empty state): smaller size for loader
* fix(empty state): spacing tweaks requested by James
* fix(workspace selector): navigate back to home on activating workspace
* fix(table empty state): default to loading state, handle error state separately
* fix: tidy ups to empty state
---
src/components/empty-state.tsx | 4 +--
.../table-messages.empty-state.test.tsx | 30 ++++++++--------
.../components/table-messages-empty-state.tsx | 36 +++++++++----------
.../components/table-messages.tsx | 13 +++++--
.../header-active-workspace-selector.tsx | 18 +++++++---
5 files changed, 59 insertions(+), 42 deletions(-)
diff --git a/src/components/empty-state.tsx b/src/components/empty-state.tsx
index 6f2616c6..10fab666 100644
--- a/src/components/empty-state.tsx
+++ b/src/components/empty-state.tsx
@@ -30,8 +30,8 @@ export function EmptyState({
actions: [ReactNode, ReactNode?] | null;
}) {
return (
-
-
+
+
{title}
diff --git a/src/features/dashboard-messages/components/__tests__/table-messages.empty-state.test.tsx b/src/features/dashboard-messages/components/__tests__/table-messages.empty-state.test.tsx
index c48c30a1..27b7d0c2 100644
--- a/src/features/dashboard-messages/components/__tests__/table-messages.empty-state.test.tsx
+++ b/src/features/dashboard-messages/components/__tests__/table-messages.empty-state.test.tsx
@@ -6,9 +6,9 @@ import { useSearchParams } from "react-router-dom";
import { delay, http, HttpHandler, HttpResponse } from "msw";
import { mockAlert } from "../../../../mocks/msw/mockers/alert.mock";
import { AlertsFilterView } from "../../hooks/use-messages-filter-search-params";
-import { TableMessages } from "../table-messages";
import { hrefs } from "@/lib/hrefs";
import { mswEndpoint } from "@/test/msw-endpoint";
+import { TableMessagesEmptyState } from "../table-messages-empty-state";
enum IllustrationTestId {
ALERT = "illustration-alert",
@@ -48,7 +48,7 @@ type TestCase = {
vi.mock("react-router-dom", async () => {
const original =
await vi.importActual (
- "react-router-dom"
+ "react-router-dom",
);
return {
...original,
@@ -59,7 +59,7 @@ vi.mock("react-router-dom", async () => {
vi.mock("@stacklok/ui-kit", async () => {
const original =
await vi.importActual(
- "@stacklok/ui-kit"
+ "@stacklok/ui-kit",
);
return {
...original,
@@ -116,7 +116,7 @@ const TEST_CASES: TestCase[] = [
mswEndpoint("/api/v1/workspaces/:workspace_name/messages"),
() => {
return HttpResponse.json([]);
- }
+ },
),
],
searchParams: {
@@ -158,9 +158,9 @@ const TEST_CASES: TestCase[] = [
mswEndpoint("/api/v1/workspaces/:workspace_name/messages"),
() => {
return HttpResponse.json(
- Array.from({ length: 10 }, () => mockAlert({ type: "malicious" }))
+ Array.from({ length: 10 }, () => mockAlert({ type: "malicious" })),
);
- }
+ },
),
],
searchParams: { search: "foo-bar", view: AlertsFilterView.ALL },
@@ -202,7 +202,7 @@ const TEST_CASES: TestCase[] = [
mswEndpoint("/api/v1/workspaces/:workspace_name/messages"),
() => {
return HttpResponse.json([]);
- }
+ },
),
],
searchParams: {
@@ -248,9 +248,9 @@ const TEST_CASES: TestCase[] = [
mswEndpoint("/api/v1/workspaces/:workspace_name/messages"),
() => {
return HttpResponse.json(
- Array.from({ length: 10 }).map(() => mockAlert({ type: "secret" }))
+ Array.from({ length: 10 }).map(() => mockAlert({ type: "secret" })),
);
- }
+ },
),
],
searchParams: {
@@ -291,10 +291,10 @@ const TEST_CASES: TestCase[] = [
() => {
return HttpResponse.json(
Array.from({ length: 10 }).map(() =>
- mockAlert({ type: "malicious" })
- )
+ mockAlert({ type: "malicious" }),
+ ),
);
- }
+ },
),
],
searchParams: {
@@ -321,11 +321,13 @@ test.each(TEST_CASES)("$testDescription", async (testCase) => {
() => {},
]);
- const { getByText, getByRole, getByTestId } = render( );
+ const { getByText, getByRole, getByTestId } = render(
+ ,
+ );
await waitFor(() => {
expect(
- getByRole("heading", { level: 4, name: testCase.expected.title })
+ getByRole("heading", { level: 4, name: testCase.expected.title }),
).toBeVisible();
expect(getByText(testCase.expected.body)).toBeVisible();
expect(getByTestId(testCase.expected.illustrationTestId)).toBeVisible();
diff --git a/src/features/dashboard-messages/components/table-messages-empty-state.tsx b/src/features/dashboard-messages/components/table-messages-empty-state.tsx
index 9f97b2c7..0461b54b 100644
--- a/src/features/dashboard-messages/components/table-messages-empty-state.tsx
+++ b/src/features/dashboard-messages/components/table-messages-empty-state.tsx
@@ -20,13 +20,16 @@ import {
} from "../hooks/use-messages-filter-search-params";
import { match, P } from "ts-pattern";
import { useQueryGetWorkspaceMessages } from "@/hooks/use-query-get-workspace-messages";
+import { twMerge } from "tailwind-merge";
function EmptyStateLoading() {
return (
(
+
+ )}
actions={null}
/>
);
@@ -117,7 +120,7 @@ function EmptyStateSecrets() {
);
}
-function EmptyStateError() {
+export function EmptyStateError() {
return (
,
- )
.with(
{
hasWorkspaceMessages: false,
hasMultipleWorkspaces: false,
- search: P._,
- view: P._,
+ search: P.any,
+ view: P.any,
+ isLoading: false,
},
() => ,
)
@@ -200,7 +194,8 @@ export function TableAlertsEmptyState() {
hasWorkspaceMessages: true,
hasMultipleWorkspaces: P.any,
search: P.string.select(),
- view: P._,
+ view: P.any,
+ isLoading: false,
},
(search) => ,
)
@@ -208,8 +203,9 @@ export function TableAlertsEmptyState() {
{
hasWorkspaceMessages: false,
hasMultipleWorkspaces: P.any,
- search: P._,
+ search: P.any,
view: P.any,
+ isLoading: false,
},
() => ,
)
@@ -217,8 +213,9 @@ export function TableAlertsEmptyState() {
{
hasWorkspaceMessages: true,
hasMultipleWorkspaces: P.any,
- search: P._,
+ search: P.any,
view: AlertsFilterView.MALICIOUS,
+ isLoading: false,
},
() => ,
)
@@ -227,8 +224,9 @@ export function TableAlertsEmptyState() {
hasWorkspaceMessages: true,
hasMultipleWorkspaces: P.any,
view: AlertsFilterView.SECRETS,
+ isLoading: false,
},
() => ,
)
- .otherwise(() => );
+ .otherwise(() => );
}
diff --git a/src/features/dashboard-messages/components/table-messages.tsx b/src/features/dashboard-messages/components/table-messages.tsx
index 6d5e63d4..166238e0 100644
--- a/src/features/dashboard-messages/components/table-messages.tsx
+++ b/src/features/dashboard-messages/components/table-messages.tsx
@@ -17,7 +17,10 @@ import { TableAlertTokenUsage } from "./table-alert-token-usage";
import { useMessagesFilterSearchParams } from "../hooks/use-messages-filter-search-params";
import { Key01, PackageX } from "@untitled-ui/icons-react";
-import { TableAlertsEmptyState } from "./table-messages-empty-state";
+import {
+ EmptyStateError,
+ TableMessagesEmptyState,
+} from "./table-messages-empty-state";
import { hrefs } from "@/lib/hrefs";
import { isAlertMalicious } from "../../../lib/is-alert-malicious";
import { isAlertSecret } from "../../../lib/is-alert-secret";
@@ -145,7 +148,7 @@ function CellRenderer({
export function TableMessages() {
const { state, prevPage, nextPage } = useMessagesFilterSearchParams();
- const { data = [] } = useQueryGetWorkspaceMessagesTable();
+ const { data = [], isError } = useQueryGetWorkspaceMessagesTable();
const { dataView, hasNextPage, hasPreviousPage } = useClientSidePagination(
data,
state.page,
@@ -160,7 +163,11 @@ export function TableMessages() {
{(column) => }
}
+ renderEmptyState={() => {
+ if (isError) return ;
+
+ return ;
+ }}
items={dataView}
>
{(row) => (
diff --git a/src/features/header/components/header-active-workspace-selector.tsx b/src/features/header/components/header-active-workspace-selector.tsx
index ea204d6e..89618312 100644
--- a/src/features/header/components/header-active-workspace-selector.tsx
+++ b/src/features/header/components/header-active-workspace-selector.tsx
@@ -19,10 +19,17 @@ import { hrefs } from "@/lib/hrefs";
import { twMerge } from "tailwind-merge";
import ChevronDown from "@untitled-ui/icons-react/build/cjs/ChevronDown";
import { SearchMd, Settings01 } from "@untitled-ui/icons-react";
+import { useLocation, useNavigate } from "react-router-dom";
+
+const ROUTES_REQUIRING_REDIRECT = [/^\/$/, /^\/prompt\/(.*)$/];
export function HeaderActiveWorkspaceSelector() {
const queryClient = useQueryClient();
+ const navigate = useNavigate();
+ const location = useLocation();
+ const { pathname } = location;
+
const { data: workspacesResponse } = useQueryListWorkspaces();
const { mutateAsync: activateWorkspace } = useMutationActivateWorkspace();
@@ -32,13 +39,16 @@ export function HeaderActiveWorkspaceSelector() {
const [searchWorkspace, setSearchWorkspace] = useState("");
const workspaces = workspacesResponse?.workspaces ?? [];
const filteredWorkspaces = workspaces.filter((workspace) =>
- workspace.name.toLowerCase().includes(searchWorkspace.toLowerCase()),
+ workspace.name.toLowerCase().includes(searchWorkspace.toLowerCase())
);
const handleWorkspaceClick = (name: string) => {
activateWorkspace({ body: { name } }).then(() => {
// eslint-disable-next-line no-restricted-syntax
queryClient.invalidateQueries({ refetchType: "all" }); // Global setting, refetch **everything**
+ if (ROUTES_REQUIRING_REDIRECT.some((route) => route.test(pathname))) {
+ navigate("/");
+ }
setIsOpen(false);
});
};
@@ -86,7 +96,7 @@ export function HeaderActiveWorkspaceSelector() {
{
"!bg-gray-900 hover:bg-gray-900 !text-gray-25 hover:!text-gray-25":
item.is_active,
- },
+ }
)}
>
{item.name}
@@ -100,12 +110,12 @@ export function HeaderActiveWorkspaceSelector() {
"ml-auto size-6 group-hover/selector:opacity-100 opacity-0 transition-opacity",
item.is_active
? "hover:bg-gray-800 pressed:bg-gray-700"
- : "hover:bg-gray-50 hover:text-primary",
+ : "hover:bg-gray-50 hover:text-primary"
)}
>
From 3d586fbfabf445d9f4c35dd9919295a5a49b5953 Mon Sep 17 00:00:00 2001
From: Alex McGovern <58784948+alex-mcgovern@users.noreply.github.com>
Date: Wed, 12 Feb 2025 08:28:05 +0000
Subject: [PATCH 05/20] fix: remove pagination state when filtering messages
(#306)
---
.../hooks/use-messages-filter-search-params.ts | 14 +++++++++-----
1 file changed, 9 insertions(+), 5 deletions(-)
diff --git a/src/features/dashboard-messages/hooks/use-messages-filter-search-params.ts b/src/features/dashboard-messages/hooks/use-messages-filter-search-params.ts
index f626833e..0a9f3742 100644
--- a/src/features/dashboard-messages/hooks/use-messages-filter-search-params.ts
+++ b/src/features/dashboard-messages/hooks/use-messages-filter-search-params.ts
@@ -22,7 +22,7 @@ const DEFAULT_FILTER = {
export const useMessagesFilterSearchParams = () => {
const [searchParams, setSearchParams] = useSearchParams(
- new URLSearchParams(DEFAULT_FILTER)
+ new URLSearchParams(DEFAULT_FILTER),
);
const setView = useCallback(
@@ -35,18 +35,22 @@ export const useMessagesFilterSearchParams = () => {
return prev;
});
},
- [setSearchParams]
+ [setSearchParams],
);
const setSearch = useCallback(
(query: string | null) => {
setSearchParams((prev) => {
- if (query !== null) prev.set("search", query);
- if (query == null || query === "") prev.delete("search");
+ if (query !== null && query !== "") {
+ prev.set("search", query);
+ prev.delete("page");
+ } else {
+ prev.delete("search");
+ }
return prev;
});
},
- [setSearchParams]
+ [setSearchParams],
);
const nextPage = useCallback(() => {
From 00bf20466d42e92869503045701f7cce5fc2ab21 Mon Sep 17 00:00:00 2001
From: Alex McGovern <58784948+alex-mcgovern@users.noreply.github.com>
Date: Wed, 12 Feb 2025 09:08:22 +0000
Subject: [PATCH 06/20] fix: error UI styling bug (#307)
---
src/components/Error.tsx | 67 ++++++++++---------
.../empty-state-strings.ts} | 0
.../table-messages.empty-state.test.tsx | 2 +-
.../section-conversation-secrets.tsx | 2 +-
.../components/table-messages-empty-state.tsx | 2 +-
5 files changed, 40 insertions(+), 33 deletions(-)
rename src/{features/dashboard-messages/constants/strings.ts => constants/empty-state-strings.ts} (100%)
diff --git a/src/components/Error.tsx b/src/components/Error.tsx
index 8e1561f7..1dcafbc6 100644
--- a/src/components/Error.tsx
+++ b/src/components/Error.tsx
@@ -1,38 +1,45 @@
-import { AlertCircle } from "@untitled-ui/icons-react";
+import { LinkExternal02 } from "@untitled-ui/icons-react";
// eslint-disable-next-line import/no-restricted-paths
import { Header } from "../features/header/components/header";
-import { Card } from "@stacklok/ui-kit";
+import { PageContainer } from "./page-container";
+import { EmptyState } from "./empty-state";
+import { IllustrationAlert, LinkButton } from "@stacklok/ui-kit";
+import { emptyStateStrings } from "@/constants/empty-state-strings";
+import { hrefs } from "@/lib/hrefs";
export function ErrorFallbackContent() {
return (
-
-
-
-
- An error occurred
-
-
-
-
+
+
+
+ Discord
+
+ ,
+
+ Github issues
+
+ ,
+ ]}
+ />
+
+
);
}
diff --git a/src/features/dashboard-messages/constants/strings.ts b/src/constants/empty-state-strings.ts
similarity index 100%
rename from src/features/dashboard-messages/constants/strings.ts
rename to src/constants/empty-state-strings.ts
diff --git a/src/features/dashboard-messages/components/__tests__/table-messages.empty-state.test.tsx b/src/features/dashboard-messages/components/__tests__/table-messages.empty-state.test.tsx
index 27b7d0c2..5269e7e1 100644
--- a/src/features/dashboard-messages/components/__tests__/table-messages.empty-state.test.tsx
+++ b/src/features/dashboard-messages/components/__tests__/table-messages.empty-state.test.tsx
@@ -1,7 +1,7 @@
import { test } from "vitest";
import { render, waitFor } from "@/lib/test-utils";
import { server } from "@/mocks/msw/node";
-import { emptyStateStrings } from "../../constants/strings";
+import { emptyStateStrings } from "../../../../constants/empty-state-strings";
import { useSearchParams } from "react-router-dom";
import { delay, http, HttpHandler, HttpResponse } from "msw";
import { mockAlert } from "../../../../mocks/msw/mockers/alert.mock";
diff --git a/src/features/dashboard-messages/components/section-conversation-secrets.tsx b/src/features/dashboard-messages/components/section-conversation-secrets.tsx
index 576a3ed3..fd7e3149 100644
--- a/src/features/dashboard-messages/components/section-conversation-secrets.tsx
+++ b/src/features/dashboard-messages/components/section-conversation-secrets.tsx
@@ -2,7 +2,7 @@ import { IllustrationDone } from "@stacklok/ui-kit";
import { isAlertSecret } from "@/lib/is-alert-secret";
import { ConversationSecretsDetected } from "./conversation-secrets-detected";
import { EmptyState } from "@/components/empty-state";
-import { emptyStateStrings } from "../constants/strings";
+import { emptyStateStrings } from "../../../constants/empty-state-strings";
import { Conversation } from "@/api/generated";
export function SectionConversationSecrets({
diff --git a/src/features/dashboard-messages/components/table-messages-empty-state.tsx b/src/features/dashboard-messages/components/table-messages-empty-state.tsx
index 0461b54b..8e6e358a 100644
--- a/src/features/dashboard-messages/components/table-messages-empty-state.tsx
+++ b/src/features/dashboard-messages/components/table-messages-empty-state.tsx
@@ -9,7 +9,7 @@ import {
} from "@stacklok/ui-kit";
import { ReactNode } from "react";
-import { emptyStateStrings } from "../constants/strings";
+import { emptyStateStrings } from "../../../constants/empty-state-strings";
import { EmptyState } from "@/components/empty-state";
import { hrefs } from "@/lib/hrefs";
import { LinkExternal02 } from "@untitled-ui/icons-react";
From 40f86a5f591fe67863d3a3082a625cfd1b3080b8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?D=C3=A1niel=20K=C3=A1ntor?=
Date: Wed, 12 Feb 2025 10:43:08 +0100
Subject: [PATCH 07/20] feat: extend new revert logic to "custom instructions"
(#304)
* remove jsonforms
* extract
* better naming
* reuse form hook for custom instructions
* add pending state for form buttons
* make workspace name uneditable for the default workspace
* add sidenote when workspace cannot be renamed
---
eslint.config.js | 2 +-
package-lock.json | 7 -
package.json | 1 -
src/components/FormButtons.tsx | 43 ++++++
.../__tests__/table-messages.alerts.test.tsx | 20 +--
.../table-messages.pagination.test.tsx | 16 +--
.../__tests__/table-messages.test.tsx | 2 +-
.../__tests__/tabs-messages.test.tsx | 24 ++--
.../lib/filter-messages-by-substring.ts | 6 +-
.../__tests__/header-status-menu.test.tsx | 32 ++---
.../__tests__/archive-workspace.test.tsx | 18 +--
.../workspace-custom-instructions.test.tsx | 12 +-
.../__tests__/workspace-name.test.tsx | 24 +++-
.../workspace-custom-instructions.tsx | 136 ++++++++++--------
.../workspace/components/workspace-name.tsx | 76 +++++++---
.../hooks/use-preferred-preferred-model.ts | 2 +-
src/forms/BaseSchemaForm.tsx | 20 ---
src/forms/FormCard.tsx | 69 ---------
src/forms/index.tsx | 49 -------
src/forms/rerenders/ObjectRenderer.tsx | 63 --------
src/forms/rerenders/VerticalLayout.tsx | 28 ----
src/forms/rerenders/controls/Checkbox.tsx | 37 -----
src/forms/rerenders/controls/EnumField.tsx | 58 --------
src/forms/rerenders/controls/TextField.tsx | 26 ----
src/forms/rerenders/renderChildren.tsx | 62 --------
src/forms/rerenders/utils.tsx | 62 --------
src/hooks/useFormState.ts | 30 ++++
src/main.tsx | 2 +-
28 files changed, 288 insertions(+), 639 deletions(-)
create mode 100644 src/components/FormButtons.tsx
delete mode 100644 src/forms/BaseSchemaForm.tsx
delete mode 100644 src/forms/FormCard.tsx
delete mode 100644 src/forms/index.tsx
delete mode 100644 src/forms/rerenders/ObjectRenderer.tsx
delete mode 100644 src/forms/rerenders/VerticalLayout.tsx
delete mode 100644 src/forms/rerenders/controls/Checkbox.tsx
delete mode 100644 src/forms/rerenders/controls/EnumField.tsx
delete mode 100644 src/forms/rerenders/controls/TextField.tsx
delete mode 100644 src/forms/rerenders/renderChildren.tsx
delete mode 100644 src/forms/rerenders/utils.tsx
create mode 100644 src/hooks/useFormState.ts
diff --git a/eslint.config.js b/eslint.config.js
index 6640e919..0ac4e0d8 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -238,5 +238,5 @@ export default tseslint.config(
},
],
},
- }
+ },
);
diff --git a/package-lock.json b/package-lock.json
index 169b8e0c..ba014842 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,7 +16,6 @@
"@radix-ui/react-dialog": "^1.1.4",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
- "@sinclair/typebox": "^0.34.16",
"@stacklok/ui-kit": "^1.0.1-1",
"@tanstack/react-query": "^5.64.1",
"@tanstack/react-query-devtools": "^5.66.0",
@@ -3984,12 +3983,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/@sinclair/typebox": {
- "version": "0.34.16",
- "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.16.tgz",
- "integrity": "sha512-rIljj8VPYAfn26ANY+5pCNVBPiv6hSufuKGe46y65cJZpvx8vHvPXlU0Q/Le4OGtlNaL8Jg2FuhtvQX18lSIqA==",
- "license": "MIT"
- },
"node_modules/@snyk/github-codeowners": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@snyk/github-codeowners/-/github-codeowners-1.1.0.tgz",
diff --git a/package.json b/package.json
index 955084d8..2520a963 100644
--- a/package.json
+++ b/package.json
@@ -28,7 +28,6 @@
"@radix-ui/react-dialog": "^1.1.4",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
- "@sinclair/typebox": "^0.34.16",
"@stacklok/ui-kit": "^1.0.1-1",
"@tanstack/react-query": "^5.64.1",
"@tanstack/react-query-devtools": "^5.66.0",
diff --git a/src/components/FormButtons.tsx b/src/components/FormButtons.tsx
new file mode 100644
index 00000000..382f1bc4
--- /dev/null
+++ b/src/components/FormButtons.tsx
@@ -0,0 +1,43 @@
+import { FormState } from "@/hooks/useFormState";
+import { Button } from "@stacklok/ui-kit";
+import { FlipBackward } from "@untitled-ui/icons-react";
+
+type Props = {
+ canSubmit: boolean;
+ formErrorMessage?: string;
+ formSideNote?: string;
+ formState: FormState;
+ children?: React.ReactNode;
+ isPending: boolean;
+};
+export function FormButtons({
+ formErrorMessage,
+ formState,
+ canSubmit,
+ isPending,
+ children,
+ formSideNote,
+}: Props) {
+ return (
+
+ {formSideNote && {formSideNote} }
+ {formErrorMessage && (
+ {formErrorMessage}
+ )}
+ {formState.isDirty && (
+
+
+ Revert changes
+
+ )}
+ {children}
+
+ Save
+
+
+ );
+}
diff --git a/src/features/dashboard-messages/components/__tests__/table-messages.alerts.test.tsx b/src/features/dashboard-messages/components/__tests__/table-messages.alerts.test.tsx
index 54fd00ce..b1a5ef9a 100644
--- a/src/features/dashboard-messages/components/__tests__/table-messages.alerts.test.tsx
+++ b/src/features/dashboard-messages/components/__tests__/table-messages.alerts.test.tsx
@@ -14,8 +14,8 @@ it("shows zero in alerts counts when no alerts", async () => {
mockConversation({
alertsConfig: { numAlerts: 0 },
}),
- ])
- )
+ ]),
+ ),
);
render( );
@@ -26,12 +26,12 @@ it("shows zero in alerts counts when no alerts", async () => {
expect(
screen.getByRole("button", {
name: /malicious packages count/i,
- })
+ }),
).toHaveTextContent("0");
expect(
screen.getByRole("button", {
name: /secrets count/i,
- })
+ }),
).toHaveTextContent("0");
});
@@ -42,8 +42,8 @@ it("shows count of malicious alerts in row", async () => {
mockConversation({
alertsConfig: { numAlerts: 10, type: "malicious" },
}),
- ])
- )
+ ]),
+ ),
);
render( );
@@ -54,7 +54,7 @@ it("shows count of malicious alerts in row", async () => {
expect(
screen.getByRole("button", {
name: /malicious packages count/i,
- })
+ }),
).toHaveTextContent("10");
});
@@ -65,8 +65,8 @@ it("shows count of secret alerts in row", async () => {
mockConversation({
alertsConfig: { numAlerts: 10, type: "secret" },
}),
- ])
- )
+ ]),
+ ),
);
render( );
@@ -77,6 +77,6 @@ it("shows count of secret alerts in row", async () => {
expect(
screen.getByRole("button", {
name: /secrets count/i,
- })
+ }),
).toHaveTextContent("10");
});
diff --git a/src/features/dashboard-messages/components/__tests__/table-messages.pagination.test.tsx b/src/features/dashboard-messages/components/__tests__/table-messages.pagination.test.tsx
index 837b5bd3..67d2f622 100644
--- a/src/features/dashboard-messages/components/__tests__/table-messages.pagination.test.tsx
+++ b/src/features/dashboard-messages/components/__tests__/table-messages.pagination.test.tsx
@@ -12,16 +12,16 @@ it("only displays a limited number of items in the table", async () => {
server.use(
http.get(mswEndpoint("/api/v1/workspaces/:workspace_name/messages"), () => {
return HttpResponse.json(
- Array.from({ length: 30 }).map(() => mockConversation())
+ Array.from({ length: 30 }).map(() => mockConversation()),
);
- })
+ }),
);
render( );
await waitFor(() => {
expect(
- within(screen.getByTestId("messages-table")).getAllByRole("row")
+ within(screen.getByTestId("messages-table")).getAllByRole("row"),
).toHaveLength(16);
});
});
@@ -30,9 +30,9 @@ it("allows pagination", async () => {
server.use(
http.get(mswEndpoint("/api/v1/workspaces/:workspace_name/messages"), () => {
return HttpResponse.json(
- Array.from({ length: 35 }).map(() => mockConversation())
+ Array.from({ length: 35 }).map(() => mockConversation()),
);
- })
+ }),
);
render( );
@@ -42,10 +42,10 @@ it("allows pagination", async () => {
await userEvent.click(screen.getByRole("button", { name: /next/i }));
expect(
- within(screen.getByTestId("messages-table")).getAllByRole("row").length
+ within(screen.getByTestId("messages-table")).getAllByRole("row").length,
).toBeLessThan(16);
},
- { timeout: 5000 }
+ { timeout: 5000 },
);
// on the last page, we cannot go further
@@ -63,7 +63,7 @@ it("allows pagination", async () => {
expect(screen.getByRole("button", { name: /next/i })).toBeEnabled();
expect(
- within(screen.getByTestId("messages-table")).getAllByRole("row").length
+ within(screen.getByTestId("messages-table")).getAllByRole("row").length,
).toEqual(16);
});
});
diff --git a/src/features/dashboard-messages/components/__tests__/table-messages.test.tsx b/src/features/dashboard-messages/components/__tests__/table-messages.test.tsx
index 2bbd1d80..47248cf0 100644
--- a/src/features/dashboard-messages/components/__tests__/table-messages.test.tsx
+++ b/src/features/dashboard-messages/components/__tests__/table-messages.test.tsx
@@ -12,7 +12,7 @@ it.each(TABLE_MESSAGES_COLUMNS)("contains $children header", async (column) => {
expect(
screen.getByRole("columnheader", {
name: column.children as string,
- })
+ }),
).toBeVisible();
});
});
diff --git a/src/features/dashboard-messages/components/__tests__/tabs-messages.test.tsx b/src/features/dashboard-messages/components/__tests__/tabs-messages.test.tsx
index b78be57f..5e795e17 100644
--- a/src/features/dashboard-messages/components/__tests__/tabs-messages.test.tsx
+++ b/src/features/dashboard-messages/components/__tests__/tabs-messages.test.tsx
@@ -15,7 +15,7 @@ test("shows correct count of all packages", async () => {
type: "secret",
numAlerts: 1,
},
- })
+ }),
),
...Array.from({ length: 13 }).map(() =>
mockConversation({
@@ -23,16 +23,16 @@ test("shows correct count of all packages", async () => {
type: "malicious",
numAlerts: 1,
},
- })
+ }),
),
]);
- })
+ }),
);
const { getByRole } = render(
foo
-
+ ,
);
await waitFor(() => {
@@ -50,16 +50,16 @@ test("shows correct count of malicious packages", async () => {
type: "malicious",
numAlerts: 1,
},
- })
- )
+ }),
+ ),
);
- })
+ }),
);
const { getByRole } = render(
foo
-
+ ,
);
await waitFor(() => {
@@ -77,16 +77,16 @@ test("shows correct count of secret packages", async () => {
type: "secret",
numAlerts: 1,
},
- })
- )
+ }),
+ ),
);
- })
+ }),
);
const { getByRole } = render(
foo
-
+ ,
);
await waitFor(() => {
diff --git a/src/features/dashboard-messages/lib/filter-messages-by-substring.ts b/src/features/dashboard-messages/lib/filter-messages-by-substring.ts
index 1bf7f6ae..207c41e9 100644
--- a/src/features/dashboard-messages/lib/filter-messages-by-substring.ts
+++ b/src/features/dashboard-messages/lib/filter-messages-by-substring.ts
@@ -2,7 +2,7 @@ import { Conversation } from "@/api/generated";
export function filterMessagesBySubstring(
conversation: Conversation,
- substring: string | null
+ substring: string | null,
): boolean {
if (conversation == null) return false;
if (substring === null) return true;
@@ -14,10 +14,10 @@ export function filterMessagesBySubstring(
if (curr.answer) acc.push(curr.answer.message);
return acc;
},
- [] as string[]
+ [] as string[],
);
return [...messages].some((i) =>
- i?.toLowerCase().includes(substring.toLowerCase())
+ i?.toLowerCase().includes(substring.toLowerCase()),
);
}
diff --git a/src/features/header/components/__tests__/header-status-menu.test.tsx b/src/features/header/components/__tests__/header-status-menu.test.tsx
index 81e12524..a957694c 100644
--- a/src/features/header/components/__tests__/header-status-menu.test.tsx
+++ b/src/features/header/components/__tests__/header-status-menu.test.tsx
@@ -13,8 +13,8 @@ describe("CardCodegateStatus", () => {
test("renders 'healthy' state", async () => {
server.use(
http.get(mswEndpoint("/health"), () =>
- HttpResponse.json({ status: "healthy" })
- )
+ HttpResponse.json({ status: "healthy" }),
+ ),
);
const { getByRole } = renderComponent();
@@ -27,8 +27,8 @@ describe("CardCodegateStatus", () => {
test("renders 'unhealthy' state", async () => {
server.use(
http.get(mswEndpoint("/health"), () =>
- HttpResponse.json({ status: null })
- )
+ HttpResponse.json({ status: null }),
+ ),
);
const { getByRole } = renderComponent();
@@ -51,9 +51,9 @@ describe("CardCodegateStatus", () => {
test("renders 'error' state when version check request fails", async () => {
server.use(
http.get(mswEndpoint("/health"), () =>
- HttpResponse.json({ status: "healthy" })
+ HttpResponse.json({ status: "healthy" }),
),
- http.get(mswEndpoint("/api/v1/version"), () => HttpResponse.error())
+ http.get(mswEndpoint("/api/v1/version"), () => HttpResponse.error()),
);
const { getByRole } = renderComponent();
@@ -66,7 +66,7 @@ describe("CardCodegateStatus", () => {
test("renders 'up to date' state", async () => {
server.use(
http.get(mswEndpoint("/health"), () =>
- HttpResponse.json({ status: "healthy" })
+ HttpResponse.json({ status: "healthy" }),
),
http.get(mswEndpoint("/api/v1/version"), () =>
HttpResponse.json({
@@ -74,8 +74,8 @@ describe("CardCodegateStatus", () => {
latest_version: "foo",
is_latest: true,
error: null,
- })
- )
+ }),
+ ),
);
const { getByRole, getByText } = renderComponent();
@@ -95,7 +95,7 @@ describe("CardCodegateStatus", () => {
test("renders 'update available' state", async () => {
server.use(
http.get(mswEndpoint("/health"), () =>
- HttpResponse.json({ status: "healthy" })
+ HttpResponse.json({ status: "healthy" }),
),
http.get(mswEndpoint("/api/v1/version"), () =>
HttpResponse.json({
@@ -103,8 +103,8 @@ describe("CardCodegateStatus", () => {
latest_version: "bar",
is_latest: false,
error: null,
- })
- )
+ }),
+ ),
);
const { getByRole } = renderComponent();
@@ -121,7 +121,7 @@ describe("CardCodegateStatus", () => {
expect(role).toBeVisible();
expect(role).toHaveAttribute(
"href",
- "https://docs.codegate.ai/how-to/install#upgrade-codegate"
+ "https://docs.codegate.ai/how-to/install#upgrade-codegate",
);
});
});
@@ -129,7 +129,7 @@ describe("CardCodegateStatus", () => {
test("renders 'version check error' state", async () => {
server.use(
http.get(mswEndpoint("/health"), () =>
- HttpResponse.json({ status: "healthy" })
+ HttpResponse.json({ status: "healthy" }),
),
http.get(mswEndpoint("/api/v1/version"), () =>
HttpResponse.json({
@@ -137,8 +137,8 @@ describe("CardCodegateStatus", () => {
latest_version: "bar",
is_latest: false,
error: "foo",
- })
- )
+ }),
+ ),
);
const { getByRole, getByText } = renderComponent();
diff --git a/src/features/workspace/components/__tests__/archive-workspace.test.tsx b/src/features/workspace/components/__tests__/archive-workspace.test.tsx
index e0fde0c3..9a6b32de 100644
--- a/src/features/workspace/components/__tests__/archive-workspace.test.tsx
+++ b/src/features/workspace/components/__tests__/archive-workspace.test.tsx
@@ -8,7 +8,7 @@ import { mswEndpoint } from "@/test/msw-endpoint";
test("has correct buttons when not archived", async () => {
const { getByRole, queryByRole } = render(
-
+ ,
);
expect(getByRole("button", { name: /archive/i })).toBeVisible();
@@ -17,7 +17,7 @@ test("has correct buttons when not archived", async () => {
test("has correct buttons when archived", async () => {
const { getByRole } = render(
-
+ ,
);
expect(getByRole("button", { name: /restore/i })).toBeVisible();
expect(getByRole("button", { name: /permanently delete/i })).toBeVisible();
@@ -25,7 +25,7 @@ test("has correct buttons when archived", async () => {
test("can archive workspace", async () => {
const { getByText, getByRole } = render(
-
+ ,
);
await userEvent.click(getByRole("button", { name: /archive/i }));
@@ -37,7 +37,7 @@ test("can archive workspace", async () => {
test("can restore archived workspace", async () => {
const { getByText, getByRole } = render(
-
+ ,
);
await userEvent.click(getByRole("button", { name: /restore/i }));
@@ -49,7 +49,7 @@ test("can restore archived workspace", async () => {
test("can permanently delete archived workspace", async () => {
const { getByText, getByRole } = render(
-
+ ,
);
await userEvent.click(getByRole("button", { name: /permanently delete/i }));
@@ -76,11 +76,11 @@ test("can't archive active workspace", async () => {
last_updated: new Date(Date.now()).toISOString(),
},
],
- })
- )
+ }),
+ ),
);
const { getByRole } = render(
-
+ ,
);
await waitFor(() => {
@@ -91,7 +91,7 @@ test("can't archive active workspace", async () => {
test("can't archive default workspace", async () => {
const { getByRole } = render(
-
+ ,
);
await waitFor(() => {
diff --git a/src/features/workspace/components/__tests__/workspace-custom-instructions.test.tsx b/src/features/workspace/components/__tests__/workspace-custom-instructions.test.tsx
index 01ebbecc..b9d83c1f 100644
--- a/src/features/workspace/components/__tests__/workspace-custom-instructions.test.tsx
+++ b/src/features/workspace/components/__tests__/workspace-custom-instructions.test.tsx
@@ -22,7 +22,7 @@ vi.mock("@monaco-editor/react", () => {
const renderComponent = () =>
render(
-
+ ,
);
test("can update custom instructions", async () => {
@@ -31,8 +31,8 @@ test("can update custom instructions", async () => {
mswEndpoint("/api/v1/workspaces/:workspace_name/custom-instructions"),
() => {
return HttpResponse.json({ prompt: "initial prompt from server" });
- }
- )
+ },
+ ),
);
const { getByRole, getByText } = renderComponent();
@@ -53,15 +53,15 @@ test("can update custom instructions", async () => {
mswEndpoint("/api/v1/workspaces/:workspace_name/custom-instructions"),
() => {
return HttpResponse.json({ prompt: "new prompt from test" });
- }
- )
+ },
+ ),
);
await userEvent.click(getByRole("button", { name: /Save/i }));
await waitFor(() => {
expect(
- getByText(/successfully updated custom instructions/i)
+ getByText(/successfully updated custom instructions/i),
).toBeVisible();
});
diff --git a/src/features/workspace/components/__tests__/workspace-name.test.tsx b/src/features/workspace/components/__tests__/workspace-name.test.tsx
index 27e32f93..d83ff4cb 100644
--- a/src/features/workspace/components/__tests__/workspace-name.test.tsx
+++ b/src/features/workspace/components/__tests__/workspace-name.test.tsx
@@ -8,7 +8,7 @@ import { mswEndpoint } from "@/test/msw-endpoint";
test("can rename workspace", async () => {
const { getByRole, getByText } = render(
-
+ ,
);
const input = getByRole("textbox", { name: /workspace name/i });
@@ -26,7 +26,7 @@ test("can rename workspace", async () => {
test("can't rename archived workspace", async () => {
const { getByRole } = render(
-
+ ,
);
expect(getByRole("textbox", { name: /workspace name/i })).toBeDisabled();
@@ -44,22 +44,32 @@ test("can't rename active workspace", async () => {
last_updated: new Date(Date.now()).toISOString(),
},
],
- })
- )
+ }),
+ ),
);
const { getByRole } = render(
-
+ ,
);
expect(getByRole("textbox", { name: /workspace name/i })).toBeDisabled();
expect(getByRole("button", { name: /save/i })).toBeDisabled();
});
-test("can't rename default workspace", async () => {
+test("can't rename archived workspace", async () => {
const { getByRole } = render(
-
+ ,
+ );
+
+ expect(getByRole("textbox", { name: /workspace name/i })).toBeDisabled();
+ expect(getByRole("button", { name: /save/i })).toBeDisabled();
+});
+
+test("can't rename default workspace", async () => {
+ const { getByRole, getByText } = render(
+ ,
);
expect(getByRole("textbox", { name: /workspace name/i })).toBeDisabled();
expect(getByRole("button", { name: /save/i })).toBeDisabled();
+ expect(getByText(/cannot rename the default workspace/i)).toBeVisible();
});
diff --git a/src/features/workspace/components/workspace-custom-instructions.tsx b/src/features/workspace/components/workspace-custom-instructions.tsx
index 80211c12..03fa3e03 100644
--- a/src/features/workspace/components/workspace-custom-instructions.tsx
+++ b/src/features/workspace/components/workspace-custom-instructions.tsx
@@ -15,6 +15,7 @@ import {
DialogTitle,
DialogTrigger,
FieldGroup,
+ Form,
Input,
Link,
Loader,
@@ -25,6 +26,7 @@ import {
} from "@stacklok/ui-kit";
import {
Dispatch,
+ FormEvent,
SetStateAction,
useCallback,
useContext,
@@ -52,6 +54,8 @@ import Fuse from "fuse.js";
import systemPrompts from "../constants/built-in-system-prompts.json";
import { MessageTextSquare02, SearchMd } from "@untitled-ui/icons-react";
import { invalidateQueries } from "@/lib/react-query-utils";
+import { useFormState } from "@/hooks/useFormState";
+import { FormButtons } from "@/components/FormButtons";
type DarkModeContextValue = {
preference: "dark" | "light" | null;
@@ -119,7 +123,8 @@ function useCustomInstructionsValue({
options: V1GetWorkspaceCustomInstructionsData;
queryClient: QueryClient;
}) {
- const [value, setValue] = useState(initialValue);
+ const formState = useFormState({ prompt: initialValue });
+ const { values, updateFormValues } = formState;
// Subscribe to changes in the workspace system prompt value in the query cache
useEffect(() => {
@@ -134,18 +139,18 @@ function useCustomInstructionsValue({
)
) {
const prompt: string | null = getCustomInstructionsFromEvent(event);
- if (prompt === value || prompt === null) return;
+ if (prompt === values.prompt || prompt === null) return;
- setValue(prompt);
+ updateFormValues({ prompt });
}
});
return () => {
return unsubscribe();
};
- }, [options, queryClient, value]);
+ }, [options, queryClient, updateFormValues, values.prompt]);
- return { value, setValue };
+ return { ...formState };
}
type PromptPresetPickerProps = {
@@ -280,12 +285,14 @@ export function WorkspaceCustomInstructions({
const { mutateAsync, isPending: isMutationPending } =
useMutationSetWorkspaceCustomInstructions(options);
- const { setValue, value } = useCustomInstructionsValue({
+ const formState = useCustomInstructionsValue({
initialValue: customInstructionsResponse?.prompt ?? "",
options,
queryClient,
});
+ const { values, updateFormValues } = formState;
+
const handleSubmit = useCallback(
(value: string) => {
mutateAsync(
@@ -302,59 +309,68 @@ export function WorkspaceCustomInstructions({
);
return (
-
-
- Custom instructions
-
- Pass custom instructions to your LLM to augment its behavior, and save
- time & tokens.
-
-
- {isCustomInstructionsPending ? (
-
- ) : (
- setValue(v ?? "")}
- height="20rem"
- defaultLanguage="Markdown"
- theme={theme}
- className={twMerge("bg-base", isArchived ? "opacity-25" : "")}
- />
- )}
-
-
-
-
- Use a preset
-
-
-
- {
- setValue(prompt);
- }}
- />
-
-
-
-
- handleSubmit(value)}
- >
- Save
-
-
-
+
);
}
diff --git a/src/features/workspace/components/workspace-name.tsx b/src/features/workspace/components/workspace-name.tsx
index 8c56bb2d..adb8add7 100644
--- a/src/features/workspace/components/workspace-name.tsx
+++ b/src/features/workspace/components/workspace-name.tsx
@@ -1,14 +1,17 @@
+import {
+ Card,
+ CardBody,
+ CardFooter,
+ Form,
+ Input,
+ Label,
+ TextField,
+} from "@stacklok/ui-kit";
import { useMutationCreateWorkspace } from "../hooks/use-mutation-create-workspace";
import { useNavigate } from "react-router-dom";
-import { Static, Type } from "@sinclair/typebox";
-import { FormCard } from "@/forms/FormCard";
-
-const schema = Type.Object({
- workspaceName: Type.String({
- title: "Workspace name",
- minLength: 1,
- }),
-});
+import { twMerge } from "tailwind-merge";
+import { useFormState } from "@/hooks/useFormState";
+import { FormButtons } from "@/components/FormButtons";
export function WorkspaceName({
className,
@@ -22,28 +25,57 @@ export function WorkspaceName({
const navigate = useNavigate();
const { mutateAsync, isPending, error } = useMutationCreateWorkspace();
const errorMsg = error?.detail ? `${error?.detail}` : "";
+ const formState = useFormState({
+ workspaceName,
+ });
+ const { values, updateFormValues } = formState;
+ const isDefault = workspaceName === "default";
+ const isUneditable = isArchived || isPending || isDefault;
- const initialData = { workspaceName };
+ const handleSubmit = (event: { preventDefault: () => void }) => {
+ event.preventDefault();
- const handleSubmit = (data: Static) => {
mutateAsync(
- { body: { name: workspaceName, rename_to: data.workspaceName } },
+ { body: { name: workspaceName, rename_to: values.workspaceName } },
{
- onSuccess: () => navigate(`/workspace/${data.workspaceName}`),
+ onSuccess: () => navigate(`/workspace/${values.workspaceName}`),
},
);
};
return (
-
+ validationBehavior="aria"
+ data-testid="workspace-name"
+ >
+
+
+ updateFormValues({ workspaceName })}
+ >
+ Workspace name
+
+
+
+
+
+
+
+
);
}
diff --git a/src/features/workspace/hooks/use-preferred-preferred-model.ts b/src/features/workspace/hooks/use-preferred-preferred-model.ts
index 4d917084..5fdbf774 100644
--- a/src/features/workspace/hooks/use-preferred-preferred-model.ts
+++ b/src/features/workspace/hooks/use-preferred-preferred-model.ts
@@ -28,7 +28,7 @@ export const usePreferredModelWorkspace = (workspaceName: string) => {
() => ({
path: { workspace_name: workspaceName },
}),
- [workspaceName]
+ [workspaceName],
);
const { data, isPending } = usePreferredModel(options);
diff --git a/src/forms/BaseSchemaForm.tsx b/src/forms/BaseSchemaForm.tsx
deleted file mode 100644
index 5dfbb69c..00000000
--- a/src/forms/BaseSchemaForm.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import type { JsonSchema, ValidationMode } from "@jsonforms/core";
-import type {
- JsonFormsInitStateProps,
- JsonFormsReactProps,
-} from "@jsonforms/react";
-import { JsonForms } from "@jsonforms/react";
-
-type FormProps = Omit<
- JsonFormsInitStateProps &
- JsonFormsReactProps & {
- validationMode?: ValidationMode;
- schema: JsonSchema;
- isDisabled?: boolean;
- },
- "readonly"
->;
-
-export function BaseSchemaForm({ isDisabled = false, ...props }: FormProps) {
- return ;
-}
diff --git a/src/forms/FormCard.tsx b/src/forms/FormCard.tsx
deleted file mode 100644
index a217d358..00000000
--- a/src/forms/FormCard.tsx
+++ /dev/null
@@ -1,69 +0,0 @@
-import { Button, Card, CardBody, CardFooter, Form } from "@stacklok/ui-kit";
-import { twMerge } from "tailwind-merge";
-import { ComponentProps, useState } from "react";
-import { SchemaForm } from "@/forms";
-import { Static, TSchema } from "@sinclair/typebox";
-import { isEqual } from "lodash";
-import { FlipBackward } from "@untitled-ui/icons-react";
-
-export function FormCard({
- className,
- isDisabled = false,
- schema,
- initialData,
- formError = null,
- onSubmit,
- isPending = false,
- ...props
-}: {
- /*
- * The error message to display at the bottom of the form
- */
- formError?: string | null;
- className?: string;
- isDisabled?: boolean;
- schema: T;
- initialData: Static;
- onSubmit: (data: Static) => void;
- isPending?: boolean;
-} & Omit, "onSubmit">) {
- const [data, setData] = useState(() => initialData);
- const isDirty = !isEqual(data, initialData);
-
- return (
-
- );
-}
diff --git a/src/forms/index.tsx b/src/forms/index.tsx
deleted file mode 100644
index 0febe7b9..00000000
--- a/src/forms/index.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-export { BaseSchemaForm } from "./BaseSchemaForm";
-
-import type {
- JsonFormsRendererRegistryEntry,
- JsonSchema,
- ValidationMode,
-} from "@jsonforms/core";
-
-import Checkbox from "./rerenders/controls/Checkbox";
-import TextField from "./rerenders/controls/TextField";
-import EnumField from "./rerenders/controls/EnumField";
-import ObjectRenderer from "./rerenders/ObjectRenderer";
-import VerticalLayout from "./rerenders/VerticalLayout";
-
-import { BaseSchemaForm } from "./BaseSchemaForm";
-import { JsonFormsInitStateProps, JsonFormsReactProps } from "@jsonforms/react";
-import { JSX } from "react/jsx-runtime";
-import { vanillaCells, vanillaRenderers } from "@jsonforms/vanilla-renderers";
-
-const formRenderers: JsonFormsRendererRegistryEntry[] = [
- TextField,
- Checkbox,
- EnumField,
-
- // layouts
- ObjectRenderer,
- VerticalLayout,
-
- // default stuff, not based on mui but not ui-kit based either
- // must be last, otherwise it would override our custom stuff
- ...vanillaRenderers,
-];
-
-const formCells = [...vanillaCells];
-
-type SchemaFormProps = Omit<
- JSX.IntrinsicAttributes &
- JsonFormsInitStateProps &
- JsonFormsReactProps & { validationMode?: ValidationMode },
- "renderers" | "schema"
-> & { schema: T; isDisabled?: boolean };
-
-export function SchemaForm({
- ...props
-}: SchemaFormProps) {
- return (
-
- );
-}
diff --git a/src/forms/rerenders/ObjectRenderer.tsx b/src/forms/rerenders/ObjectRenderer.tsx
deleted file mode 100644
index 939e394a..00000000
--- a/src/forms/rerenders/ObjectRenderer.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import isEmpty from "lodash/isEmpty";
-import {
- findUISchema,
- GroupLayout,
- isObjectControl,
- RankedTester,
- rankWith,
- StatePropsOfControlWithDetail,
-} from "@jsonforms/core";
-import { JsonFormsDispatch, withJsonFormsDetailProps } from "@jsonforms/react";
-import { useMemo } from "react";
-
-const ObjectRenderer = ({
- renderers,
- cells,
- uischemas,
- schema,
- label,
- path,
- visible,
- enabled,
- uischema,
- rootSchema,
-}: StatePropsOfControlWithDetail) => {
- const detailUiSchema = useMemo(
- () =>
- findUISchema(
- uischemas ?? [],
- schema,
- uischema.scope,
- path,
- "Group",
- uischema,
- rootSchema,
- ),
- [uischemas, schema, path, uischema, rootSchema],
- );
- if (isEmpty(path)) {
- detailUiSchema.type = "VerticalLayout";
- } else {
- (detailUiSchema as GroupLayout).label = label;
- }
- return (
-
-
-
- );
-};
-
-export const tester: RankedTester = rankWith(2, isObjectControl);
-const renderer = withJsonFormsDetailProps(ObjectRenderer);
-
-const config = { tester, renderer };
-
-export default config;
diff --git a/src/forms/rerenders/VerticalLayout.tsx b/src/forms/rerenders/VerticalLayout.tsx
deleted file mode 100644
index 936662b7..00000000
--- a/src/forms/rerenders/VerticalLayout.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import {
- RankedTester,
- rankWith,
- RendererProps,
- uiTypeIs,
- VerticalLayout,
-} from "@jsonforms/core";
-import { withJsonFormsLayoutProps } from "@jsonforms/react";
-import { renderChildren } from "./renderChildren";
-
-function VerticalLayoutRenderer({
- uischema,
- enabled,
- schema,
- path,
-}: RendererProps) {
- const verticalLayout = uischema as VerticalLayout;
-
- return {renderChildren(verticalLayout, schema, path, enabled)} ;
-}
-
-export const renderer = withJsonFormsLayoutProps(VerticalLayoutRenderer, false);
-
-export const tester: RankedTester = rankWith(1, uiTypeIs("VerticalLayout"));
-
-const config = { tester, renderer };
-
-export default config;
diff --git a/src/forms/rerenders/controls/Checkbox.tsx b/src/forms/rerenders/controls/Checkbox.tsx
deleted file mode 100644
index 736a7bb4..00000000
--- a/src/forms/rerenders/controls/Checkbox.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import type { ControlProps, RankedTester } from "@jsonforms/core";
-import { isBooleanControl, rankWith } from "@jsonforms/core";
-import { withJsonFormsControlProps } from "@jsonforms/react";
-import { Checkbox, Tooltip, TooltipInfoButton } from "@stacklok/ui-kit";
-import { TooltipTrigger } from "react-aria-components";
-
-import { getRACPropsFromJSONForms, JsonFormsError } from "../utils";
-
-const CheckboxControl = (props: ControlProps) => {
- const { label, description } = props;
- const { value: isSelected, ...mappedProps } = getRACPropsFromJSONForms(props);
-
- return (
- <>
-
-
- {label}
- {description !== undefined && description.length > 0 ? (
-
-
- {description}
-
- ) : null}
-
-
-
- >
- );
-};
-
-const tester: RankedTester = rankWith(2, isBooleanControl);
-
-const renderer = withJsonFormsControlProps(CheckboxControl);
-
-const config = { tester, renderer };
-
-export default config;
diff --git a/src/forms/rerenders/controls/EnumField.tsx b/src/forms/rerenders/controls/EnumField.tsx
deleted file mode 100644
index eb783536..00000000
--- a/src/forms/rerenders/controls/EnumField.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-import type {
- ControlProps,
- EnumCellProps,
- OwnPropsOfEnum,
- RankedTester,
-} from "@jsonforms/core";
-import { isEnumControl, rankWith } from "@jsonforms/core";
-import { withJsonFormsEnumProps } from "@jsonforms/react";
-import { Select, SelectButton } from "@stacklok/ui-kit";
-import { getRACPropsFromJSONForms, LabelWithDescription } from "../utils";
-
-// eslint-disable-next-line react-refresh/only-export-components
-const EnumFieldControl = (
- props: EnumCellProps & OwnPropsOfEnum & ControlProps,
-) => {
- const items = [
- {
- label: "Select an option",
- value: "",
- },
- ...(props.options ?? []),
- ].map(({ label, value }) => ({
- textValue: label,
- id: value,
- }));
- const mappedProps = getRACPropsFromJSONForms(props);
-
- return (
- {
- props.handleChange(props.path, newValue);
- }}
- selectedKey={mappedProps.value ?? ""}
- >
-
-
-
-
- );
-};
-
-const tester: RankedTester = (...args) => {
- const x = rankWith(2, isEnumControl)(...args);
- return x;
-};
-
-// @ts-expect-error the types are not properly handled here for some reason
-// for pragmatic reasons I ignored this
-const renderer = withJsonFormsEnumProps(EnumFieldControl, false);
-
-const config = { tester, renderer };
-
-export default config;
diff --git a/src/forms/rerenders/controls/TextField.tsx b/src/forms/rerenders/controls/TextField.tsx
deleted file mode 100644
index 8ebe7080..00000000
--- a/src/forms/rerenders/controls/TextField.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import type { ControlProps, RankedTester } from "@jsonforms/core";
-import { isStringControl, rankWith } from "@jsonforms/core";
-import { withJsonFormsControlProps } from "@jsonforms/react";
-import { Input, TextField } from "@stacklok/ui-kit";
-
-import { getRACPropsFromJSONForms, LabelWithDescription } from "../utils";
-
-// eslint-disable-next-line react-refresh/only-export-components
-const TextFieldControl = (props: ControlProps) => {
- const mappedProps = getRACPropsFromJSONForms(props);
-
- return (
-
-
-
-
- );
-};
-
-const tester: RankedTester = rankWith(1, isStringControl);
-
-const renderer = withJsonFormsControlProps(TextFieldControl);
-
-const config = { tester, renderer };
-
-export default config;
diff --git a/src/forms/rerenders/renderChildren.tsx b/src/forms/rerenders/renderChildren.tsx
deleted file mode 100644
index 501cd963..00000000
--- a/src/forms/rerenders/renderChildren.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- The MIT License
-
- Copyright (c) 2017-2019 EclipseSource Munich
- https://github.com/eclipsesource/jsonforms
-
- Permission is hereby granted, free of charge, to any person obtaining a copy
- of this software and associated documentation files (the "Software"), to deal
- in the Software without restriction, including without limitation the rights
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- copies of the Software, and to permit persons to whom the Software is
- furnished to do so, subject to the following conditions:
-
- The above copyright notice and this permission notice shall be included in
- all copies or substantial portions of the Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- THE SOFTWARE.
-*/
-import isEmpty from "lodash/isEmpty";
-import { JsonSchema, Layout } from "@jsonforms/core";
-import { JsonFormsDispatch, useJsonForms } from "@jsonforms/react";
-export interface RenderChildrenProps {
- layout: Layout;
- schema: JsonSchema;
- className: string;
- path: string;
-}
-
-export const renderChildren = (
- layout: Layout,
- schema: JsonSchema,
- path: string,
- enabled: boolean,
-) => {
- if (isEmpty(layout.elements)) {
- return [];
- }
-
- // eslint-disable-next-line react-hooks/rules-of-hooks
- const { renderers, cells } = useJsonForms();
-
- return layout.elements.map((child, index) => {
- return (
-
-
-
- );
- });
-};
diff --git a/src/forms/rerenders/utils.tsx b/src/forms/rerenders/utils.tsx
deleted file mode 100644
index edae117c..00000000
--- a/src/forms/rerenders/utils.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-import type { ControlProps } from "@jsonforms/core";
-import { Description, FieldError, Label } from "@stacklok/ui-kit";
-
-export function getRACPropsFromJSONForms(props: ControlProps) {
- const { id, errors, required, enabled, handleChange, path, data } = props;
-
- return {
- isRequired: required,
- isInvalid: errors.length > 0,
- id: id,
- isDisabled: !enabled,
- onChange: (newValue: unknown) => handleChange(path, newValue),
- value: data,
- };
-}
-
-/**
- * Displays a `jsonforms` validation error if there is one.
- * Use when displaying the error in a different place
- * than the errors. Otherwise use
- */
-export function JsonFormsError({ errors }: ControlProps) {
- if (errors.length > 0) {
- return {errors} ;
- }
-
- return null;
-}
-
-export function JsonFormsDescription(props: ControlProps) {
- const { description, errors } = props;
-
- if (errors.length > 0) {
- return ;
- }
-
- if ((description ?? "").length === 0) {
- return null;
- }
-
- return (
-
- {description}
-
- );
-}
-
-export function LabelWithDescription({
- label,
- isRequired = false,
- ...props
-}: ControlProps & { isRequired?: boolean }) {
- return (
-
-
- {label}{" "}
- {isRequired === true ? * : null}
-
-
-
- );
-}
diff --git a/src/hooks/useFormState.ts b/src/hooks/useFormState.ts
new file mode 100644
index 00000000..71589fbb
--- /dev/null
+++ b/src/hooks/useFormState.ts
@@ -0,0 +1,30 @@
+import { isEqual } from "lodash";
+import { useState } from "react";
+
+export type FormState = {
+ values: T;
+ updateFormValues: (newState: Partial) => void;
+ resetForm: () => void;
+ isDirty: boolean;
+};
+
+export function useFormState>(
+ initialValues: Values,
+): FormState {
+ // this could be replaced with some form library later
+ const [values, setValues] = useState(initialValues);
+ const updateFormValues = (newState: Partial) => {
+ setValues((prevState: Values) => ({
+ ...prevState,
+ ...newState,
+ }));
+ };
+
+ const resetForm = () => {
+ setValues(initialValues);
+ };
+
+ const isDirty = !isEqual(values, initialValues);
+
+ return { values, updateFormValues, resetForm, isDirty };
+}
diff --git a/src/main.tsx b/src/main.tsx
index 0628ebc3..fea4b6cd 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -37,5 +37,5 @@ createRoot(document.getElementById("root")!).render(
-
+ ,
);
From 520019221d7709b5e9ff5000474f40f1092ae69e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?D=C3=A1niel=20K=C3=A1ntor?=
Date: Wed, 12 Feb 2025 13:18:03 +0100
Subject: [PATCH 08/20] fix: only show "cannot edit default" on default
workspace #308
---
.../workspace/components/__tests__/workspace-name.test.tsx | 5 ++++-
src/features/workspace/components/workspace-name.tsx | 4 +++-
2 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/src/features/workspace/components/__tests__/workspace-name.test.tsx b/src/features/workspace/components/__tests__/workspace-name.test.tsx
index d83ff4cb..624cdaf8 100644
--- a/src/features/workspace/components/__tests__/workspace-name.test.tsx
+++ b/src/features/workspace/components/__tests__/workspace-name.test.tsx
@@ -56,12 +56,15 @@ test("can't rename active workspace", async () => {
});
test("can't rename archived workspace", async () => {
- const { getByRole } = render(
+ const { getByRole, queryByText } = render(
,
);
expect(getByRole("textbox", { name: /workspace name/i })).toBeDisabled();
expect(getByRole("button", { name: /save/i })).toBeDisabled();
+ expect(
+ queryByText(/cannot rename the default workspace/i),
+ ).not.toBeInTheDocument();
});
test("can't rename default workspace", async () => {
diff --git a/src/features/workspace/components/workspace-name.tsx b/src/features/workspace/components/workspace-name.tsx
index adb8add7..e7f4580a 100644
--- a/src/features/workspace/components/workspace-name.tsx
+++ b/src/features/workspace/components/workspace-name.tsx
@@ -70,7 +70,9 @@ export function WorkspaceName({
From 6b57fd3f589ed036c2f51bd6c9066fc767476aad Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?D=C3=A1niel=20K=C3=A1ntor?=
Date: Wed, 12 Feb 2025 16:05:36 +0100
Subject: [PATCH 09/20] feat: make preferred model form consistent with the
other forms (#309)
* only show notice on actual default workspace
* fix test assertion
* invalidate muxes after changing preferred model
---
.../workspace-preferred-model.test.tsx | 7 ++-
.../workspace-custom-instructions.tsx | 6 +-
.../workspace/components/workspace-name.tsx | 3 +-
.../components/workspace-preferred-model.tsx | 61 ++++++++++++-------
.../hooks/use-preferred-preferred-model.ts | 17 +++---
src/hooks/useFormState.ts | 45 +++++++++++---
6 files changed, 93 insertions(+), 46 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 e7ac8314..ad68667f 100644
--- a/src/features/workspace/components/__tests__/workspace-preferred-model.test.tsx
+++ b/src/features/workspace/components/__tests__/workspace-preferred-model.test.tsx
@@ -3,7 +3,7 @@ import { screen, waitFor } from "@testing-library/react";
import { WorkspacePreferredModel } from "../workspace-preferred-model";
import userEvent from "@testing-library/user-event";
-test("render model overrides", () => {
+test("render model overrides", async () => {
render(
{
expect(
screen.getByRole("button", { name: /select the model/i }),
).toBeVisible();
- expect(screen.getByRole("button", { name: /save/i })).toBeVisible();
+
+ await waitFor(() => {
+ expect(screen.getByRole("button", { name: /save/i })).toBeVisible();
+ });
});
test("submit preferred model", async () => {
diff --git a/src/features/workspace/components/workspace-custom-instructions.tsx b/src/features/workspace/components/workspace-custom-instructions.tsx
index 03fa3e03..4cbfe6f2 100644
--- a/src/features/workspace/components/workspace-custom-instructions.tsx
+++ b/src/features/workspace/components/workspace-custom-instructions.tsx
@@ -123,7 +123,11 @@ function useCustomInstructionsValue({
options: V1GetWorkspaceCustomInstructionsData;
queryClient: QueryClient;
}) {
- const formState = useFormState({ prompt: initialValue });
+ const initialFormValues = useMemo(
+ () => ({ prompt: initialValue }),
+ [initialValue],
+ );
+ const formState = useFormState(initialFormValues);
const { values, updateFormValues } = formState;
// Subscribe to changes in the workspace system prompt value in the query cache
diff --git a/src/features/workspace/components/workspace-name.tsx b/src/features/workspace/components/workspace-name.tsx
index e7f4580a..fc4206c4 100644
--- a/src/features/workspace/components/workspace-name.tsx
+++ b/src/features/workspace/components/workspace-name.tsx
@@ -12,6 +12,7 @@ import { useNavigate } from "react-router-dom";
import { twMerge } from "tailwind-merge";
import { useFormState } from "@/hooks/useFormState";
import { FormButtons } from "@/components/FormButtons";
+import { FormEvent } from "react";
export function WorkspaceName({
className,
@@ -32,7 +33,7 @@ export function WorkspaceName({
const isDefault = workspaceName === "default";
const isUneditable = isArchived || isPending || isDefault;
- const handleSubmit = (event: { preventDefault: () => void }) => {
+ const handleSubmit = (event: FormEvent) => {
event.preventDefault();
mutateAsync(
diff --git a/src/features/workspace/components/workspace-preferred-model.tsx b/src/features/workspace/components/workspace-preferred-model.tsx
index d0dda4d4..1dc50bc2 100644
--- a/src/features/workspace/components/workspace-preferred-model.tsx
+++ b/src/features/workspace/components/workspace-preferred-model.tsx
@@ -1,6 +1,5 @@
import {
Alert,
- Button,
Card,
CardBody,
CardFooter,
@@ -16,6 +15,10 @@ import { FormEvent } from "react";
import { usePreferredModelWorkspace } from "../hooks/use-preferred-preferred-model";
import { Select, SelectButton } from "@stacklok/ui-kit";
import { useQueryListAllModelsForAllProviders } from "@/hooks/use-query-list-all-models-for-all-providers";
+import { FormButtons } from "@/components/FormButtons";
+import { invalidateQueries } from "@/lib/react-query-utils";
+import { v1GetWorkspaceMuxesQueryKey } from "@/api/generated/@tanstack/react-query.gen";
+import { useQueryClient } from "@tanstack/react-query";
function MissingProviderBanner() {
return (
@@ -39,30 +42,38 @@ export function WorkspacePreferredModel({
workspaceName: string;
isArchived: boolean | undefined;
}) {
- const { preferredModel, setPreferredModel, isPending } =
- usePreferredModelWorkspace(workspaceName);
+ const queryClient = useQueryClient();
+ const { formState, isPending } = usePreferredModelWorkspace(workspaceName);
const { mutateAsync } = useMutationPreferredModelWorkspace();
const { data: providerModels = [] } = useQueryListAllModelsForAllProviders();
- const { model, provider_id } = preferredModel;
const isModelsEmpty = !isPending && providerModels.length === 0;
const handleSubmit = (event: FormEvent) => {
event.preventDefault();
- mutateAsync({
- path: { workspace_name: workspaceName },
- body: [
- {
- matcher: "",
- provider_id,
- model,
- matcher_type: MuxMatcherType.CATCH_ALL,
- },
- ],
- });
+ mutateAsync(
+ {
+ path: { workspace_name: workspaceName },
+ body: [
+ {
+ matcher: "",
+ matcher_type: MuxMatcherType.CATCH_ALL,
+ ...formState.values.preferredModel,
+ },
+ ],
+ },
+ {
+ onSuccess: () =>
+ invalidateQueries(queryClient, [v1GetWorkspaceMuxesQueryKey]),
+ },
+ );
};
return (
-
diff --git a/src/features/workspace/hooks/use-preferred-preferred-model.ts b/src/features/workspace/hooks/use-preferred-preferred-model.ts
index 5fdbf774..8ca4ad63 100644
--- a/src/features/workspace/hooks/use-preferred-preferred-model.ts
+++ b/src/features/workspace/hooks/use-preferred-preferred-model.ts
@@ -1,7 +1,8 @@
import { MuxRule, V1GetWorkspaceMuxesData } from "@/api/generated";
import { v1GetWorkspaceMuxesOptions } from "@/api/generated/@tanstack/react-query.gen";
+import { useFormState } from "@/hooks/useFormState";
import { useQuery } from "@tanstack/react-query";
-import { useEffect, useMemo, useState } from "react";
+import { useMemo } from "react";
type ModelRule = Omit & {};
@@ -21,8 +22,6 @@ const usePreferredModel = (options: {
};
export const usePreferredModelWorkspace = (workspaceName: string) => {
- const [preferredModel, setPreferredModel] =
- useState(DEFAULT_STATE);
const options: V1GetWorkspaceMuxesData &
Omit = useMemo(
() => ({
@@ -31,12 +30,10 @@ export const usePreferredModelWorkspace = (workspaceName: string) => {
[workspaceName],
);
const { data, isPending } = usePreferredModel(options);
+ const providerModel = data?.[0];
+ const formState = useFormState<{ preferredModel: ModelRule }>({
+ preferredModel: providerModel ?? DEFAULT_STATE,
+ });
- useEffect(() => {
- const providerModel = data?.[0];
-
- setPreferredModel(providerModel ?? DEFAULT_STATE);
- }, [data, setPreferredModel]);
-
- return { preferredModel, setPreferredModel, isPending };
+ return { isPending, formState };
};
diff --git a/src/hooks/useFormState.ts b/src/hooks/useFormState.ts
index 71589fbb..2f859398 100644
--- a/src/hooks/useFormState.ts
+++ b/src/hooks/useFormState.ts
@@ -1,5 +1,5 @@
import { isEqual } from "lodash";
-import { useState } from "react";
+import { useCallback, useEffect, useMemo, useRef, useState } from "react";
export type FormState = {
values: T;
@@ -8,23 +8,50 @@ export type FormState = {
isDirty: boolean;
};
+function useDeepMemo(value: T): T {
+ const ref = useRef(value);
+ if (!isEqual(ref.current, value)) {
+ ref.current = value;
+ }
+ return ref.current;
+}
+
export function useFormState>(
initialValues: Values,
): FormState {
+ const memoizedInitialValues = useDeepMemo(initialValues);
+
// this could be replaced with some form library later
- const [values, setValues] = useState(initialValues);
- const updateFormValues = (newState: Partial) => {
+ const [values, setValues] = useState(memoizedInitialValues);
+ const [originalValues, setOriginalValues] = useState(values);
+
+ useEffect(() => {
+ // this logic supports the use case when the initialValues change
+ // due to an async request for instance
+ setOriginalValues(memoizedInitialValues);
+ setValues(memoizedInitialValues);
+ }, [memoizedInitialValues]);
+
+ const updateFormValues = useCallback((newState: Partial) => {
setValues((prevState: Values) => ({
...prevState,
...newState,
}));
- };
+ }, []);
+
+ const resetForm = useCallback(() => {
+ setValues(originalValues);
+ }, [originalValues]);
- const resetForm = () => {
- setValues(initialValues);
- };
+ const isDirty = useMemo(
+ () => !isEqual(values, originalValues),
+ [values, originalValues],
+ );
- const isDirty = !isEqual(values, initialValues);
+ const formState = useMemo(
+ () => ({ values, updateFormValues, resetForm, isDirty }),
+ [values, updateFormValues, resetForm, isDirty],
+ );
- return { values, updateFormValues, resetForm, isDirty };
+ return formState;
}
From 32d46a0f87291e2b09a22dfec737b0bd6befbb4b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?D=C3=A1niel=20K=C3=A1ntor?=
Date: Thu, 13 Feb 2025 12:08:51 +0100
Subject: [PATCH 10/20] feat: switch help and setting menu (#310)
fixes: #303
---
src/features/header/components/header.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/features/header/components/header.tsx b/src/features/header/components/header.tsx
index 6ae077d1..f202bce3 100644
--- a/src/features/header/components/header.tsx
+++ b/src/features/header/components/header.tsx
@@ -32,8 +32,8 @@ export function Header() {
-
+
From 7f2c040ad7d9cceda42ac70e40d716272f82704f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?D=C3=A1niel=20K=C3=A1ntor?=
Date: Thu, 13 Feb 2025 19:30:44 +0100
Subject: [PATCH 11/20] feat: enable word wrap for prompt editor (#312)
---
.../workspace/components/workspace-custom-instructions.tsx | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/features/workspace/components/workspace-custom-instructions.tsx b/src/features/workspace/components/workspace-custom-instructions.tsx
index 4cbfe6f2..23e0d149 100644
--- a/src/features/workspace/components/workspace-custom-instructions.tsx
+++ b/src/features/workspace/components/workspace-custom-instructions.tsx
@@ -334,6 +334,7 @@ export function WorkspaceCustomInstructions({
Date: Fri, 14 Feb 2025 08:10:25 +0000
Subject: [PATCH 12/20] chore(deps-dev): bump @types/node from 22.10.5 to
22.13.1 (#283)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 22.10.5 to 22.13.1.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)
---
updated-dependencies:
- dependency-name: "@types/node"
dependency-type: direct:development
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
package-lock.json | 8 ++++----
package.json | 2 +-
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index ba014842..8ffe8673 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -50,7 +50,7 @@
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.2.0",
"@testing-library/user-event": "^14.6.1",
- "@types/node": "^22.10.1",
+ "@types/node": "^22.13.1",
"@types/react": "^19.0.2",
"@types/react-dom": "^19.0.2",
"@typescript-eslint/parser": "^8.23.0",
@@ -4526,9 +4526,9 @@
"license": "MIT"
},
"node_modules/@types/node": {
- "version": "22.10.5",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.5.tgz",
- "integrity": "sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==",
+ "version": "22.13.1",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.1.tgz",
+ "integrity": "sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew==",
"dev": true,
"license": "MIT",
"dependencies": {
diff --git a/package.json b/package.json
index 2520a963..6757ca47 100644
--- a/package.json
+++ b/package.json
@@ -62,7 +62,7 @@
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.2.0",
"@testing-library/user-event": "^14.6.1",
- "@types/node": "^22.10.1",
+ "@types/node": "^22.13.1",
"@types/react": "^19.0.2",
"@types/react-dom": "^19.0.2",
"@typescript-eslint/parser": "^8.23.0",
From 8b45b6da097e92472b95314ccb4a960d06f62a5c Mon Sep 17 00:00:00 2001
From: Alex McGovern <58784948+alex-mcgovern@users.noreply.github.com>
Date: Fri, 14 Feb 2025 08:39:13 +0000
Subject: [PATCH 13/20] chore: configure prettier & apply formatting (#315)
---
.github/ISSUE_TEMPLATE/1-bug.yml | 62 +-
.github/ISSUE_TEMPLATE/2-task.yml | 8 +-
.github/ISSUE_TEMPLATE/3-chore.yml | 10 +-
.github/workflows/_security-checks.yml | 2 +-
.github/workflows/_static-checks.yml | 4 +-
.prettierrc | 15 +
CHANGELOG.md | 196 +-
README.md | 2 -
components.json | 2 +-
eslint.config.js | 180 +-
lint-staged.config.mjs | 10 +-
openapi-ts.config.ts | 22 +-
package-lock.json | 131 +
package.json | 3 +
src/App.test.tsx | 144 +-
src/App.tsx | 10 +-
src/Page.test.tsx | 16 +-
src/Page.tsx | 26 +-
.../generated/@tanstack/react-query.gen.ts | 436 +-
src/api/generated/index.ts | 4 +-
src/api/generated/sdk.gen.ts | 222 +-
src/api/generated/types.gen.ts | 466 +-
src/components/BreadcrumbHome.tsx | 4 +-
src/components/CopyToClipboard.tsx | 30 +-
src/components/EmptyState.tsx | 10 +-
src/components/Error.tsx | 24 +-
src/components/ErrorBoundary.tsx | 18 +-
src/components/FormButtons.tsx | 24 +-
src/components/HoverPopover.tsx | 20 +-
src/components/Markdown.tsx | 114 +-
.../__tests__/CopyToClipboard.test.tsx | 38 +-
.../__tests__/ErrorBoundary.test.tsx | 30 +-
src/components/empty-state.tsx | 35 +-
src/components/heading.tsx | 14 +-
src/components/icons/Continue.tsx | 6 +-
src/components/icons/Copilot.tsx | 6 +-
src/components/icons/Discord.tsx | 6 +-
src/components/icons/Github.tsx | 6 +-
src/components/icons/Youtube.tsx | 6 +-
src/components/icons/index.ts | 10 +-
src/components/page-container.tsx | 6 +-
src/components/react-query-provider.tsx | 56 +-
src/components/ui/chat/chat-bubble.tsx | 119 +-
src/components/ui/chat/chat-message-list.tsx | 18 +-
src/components/ui/chat/message-loading.tsx | 90 +-
src/constants/empty-state-strings.ts | 30 +-
src/context/confirm-context.tsx | 62 +-
.../alerts-summary-malicious-pkg.test.tsx | 50 +-
.../__tests__/alerts-summary-secrets.test.tsx | 50 +-
...rts-summary-workspace-token-usage.test.tsx | 70 +-
.../alerts-summary-malicious-pkg.tsx | 12 +-
.../components/alerts-summary-secrets.tsx | 12 +-
.../alerts-summary-workspace-token-usage.tsx | 14 +-
.../components/alerts-summary.tsx | 28 +-
...uery-get-workspace-alerts-malicious-pkg.ts | 14 +-
.../use-query-get-workspace-alerts-secrets.ts | 14 +-
.../hooks/use-query-get-workspace-alerts.ts | 24 +-
.../use-query-get-workspace-token-usage.ts | 18 +-
.../__tests__/table-messages.alerts.test.tsx | 96 +-
.../table-messages.empty-state.test.tsx | 242 +-
.../table-messages.pagination.test.tsx | 84 +-
.../__tests__/table-messages.test.tsx | 22 +-
.../table-messages.token-usage.test.tsx | 84 +-
.../__tests__/tabs-messages.test.tsx | 92 +-
.../conversation-secrets-detected.tsx | 20 +-
.../components/conversation-summary.tsx | 80 +-
.../components/search-field-messages.tsx | 24 +-
.../section-conversation-secrets.tsx | 22 +-
.../section-conversation-transcript.tsx | 36 +-
.../components/table-alert-token-usage.tsx | 24 +-
.../components/table-messages-empty-state.tsx | 76 +-
.../components/table-messages.tsx | 122 +-
.../components/tabs-conversation.tsx | 32 +-
.../components/tabs-messages.tsx | 64 +-
.../components/token-usage-by-providers.tsx | 65 +-
.../components/token-usage-icon.tsx | 16 +-
.../constants/table-messages-columns.ts | 34 +-
.../hooks/use-conversation-by-id.tsx | 4 +-
.../hooks/use-conversation-search-params.ts | 40 +-
.../use-messages-filter-search-params.ts | 78 +-
.../use-query-get-workspace-messages-table.ts | 32 +-
.../lib/__tests__/is-alert-malicious.test.ts | 16 +-
.../lib/__tests__/is-alert-secret.test.ts | 16 +-
.../lib/count-conversation-alerts.ts | 12 +-
.../lib/filter-messages-by-substring.ts | 22 +-
.../lib/get-conversation-title.ts | 14 +-
.../lib/get-provider-string.ts | 8 +-
.../__tests__/header-status-menu.test.tsx | 184 +-
.../header-active-workspace-selector.tsx | 81 +-
.../header/components/header-status-menu.tsx | 250 +-
src/features/header/components/header.tsx | 31 +-
.../header/constants/help-menu-items.tsx | 70 +-
.../header/constants/settings-menu-items.tsx | 36 +-
.../hooks/use-queries-codegate-status.ts | 30 +-
src/features/header/types.ts | 10 +-
.../components/provider-dialog-footer.tsx | 10 +-
.../providers/components/provider-dialog.tsx | 14 +-
.../providers/components/provider-form.tsx | 36 +-
.../providers/components/table-providers.tsx | 70 +-
.../hooks/use-confirm-delete-provider.tsx | 22 +-
.../hooks/use-invalidate-providers-queries.ts | 16 +-
.../hooks/use-mutation-create-provider.ts | 20 +-
.../hooks/use-mutation-delete-provider.ts | 14 +-
.../hooks/use-mutation-update-provider.ts | 34 +-
src/features/providers/hooks/use-provider.ts | 26 +-
src/features/providers/hooks/use-providers.ts | 6 +-
src/features/providers/lib/utils.ts | 38 +-
.../__tests__/archive-workspace.test.tsx | 122 +-
.../table-actions-workspaces.test.tsx | 336 +-
.../__tests__/workspace-creation.test.tsx | 54 +-
.../workspace-custom-instructions.test.tsx | 80 +-
.../__tests__/workspace-name.test.tsx | 92 +-
.../workspace-preferred-model.test.tsx | 58 +-
.../components/archive-workspace.tsx | 74 +-
.../components/table-actions-workspaces.tsx | 80 +-
.../workspace/components/table-workspaces.tsx | 28 +-
.../components/workspace-creation.tsx | 30 +-
.../workspace-custom-instructions.tsx | 205 +-
.../workspace/components/workspace-name.tsx | 48 +-
.../components/workspace-preferred-model.tsx | 66 +-
.../constants/built-in-system-prompts.json | 533 +-
.../hooks/use-archive-workspace-button.tsx | 20 +-
.../hooks/use-archived-workspaces.ts | 10 +-
.../use-confirm-hard-delete-workspace.tsx | 26 +-
.../hooks/use-invalidate-workspace-queries.ts | 16 +-
.../hooks/use-mutation-archive-workspace.ts | 60 +-
.../hooks/use-mutation-create-workspace.ts | 12 +-
.../use-mutation-hard-delete-workspace.ts | 10 +-
.../use-mutation-preferred-model-workspace.ts | 12 +-
.../hooks/use-mutation-restore-workspace.ts | 52 +-
...tion-set-workspace-custom-instructions.tsx | 18 +-
.../hooks/use-preferred-preferred-model.ts | 42 +-
...query-get-workspace-custom-instructions.ts | 10 +-
.../hooks/use-restore-workspace-button.tsx | 14 +-
src/hooks/__tests__/useSee.test.ts | 86 +-
src/hooks/use-confirm.tsx | 14 +-
src/hooks/use-kbd-shortcuts.ts | 26 +-
src/hooks/use-mutation-activate-workspace.ts | 12 +-
src/hooks/use-query-active-workspace-name.ts | 8 +-
src/hooks/use-query-get-workspace-messages.ts | 28 +-
src/hooks/use-query-list-active-workspaces.ts | 10 +-
...query-list-all-models-for-all-providers.ts | 8 +-
src/hooks/use-query-list-all-workspaces.ts | 28 +-
src/hooks/use-query-list-workspaces.ts | 10 +-
src/hooks/use-toast-mutation.ts | 44 +-
src/hooks/useClientSidePagination.ts | 14 +-
src/hooks/useFormState.ts | 52 +-
src/hooks/useSse.ts | 32 +-
src/lib/__tests__/currency.test.ts | 46 +-
src/lib/currency.ts | 58 +-
src/lib/format-number.ts | 8 +-
src/lib/format-time.ts | 22 +-
src/lib/hrefs.ts | 14 +-
src/lib/is-alert-critical.ts | 6 +-
src/lib/is-alert-malicious.ts | 16 +-
src/lib/is-alert-secret.ts | 12 +-
src/lib/multi-filter.ts | 6 +-
src/lib/react-query-utils.ts | 52 +-
src/lib/test-utils.tsx | 38 +-
src/lib/ui-kit-client-side-routing.tsx | 10 +-
src/lib/utils.ts | 54 +-
src/main.tsx | 36 +-
src/mocks/msw/fixtures/GET_ALERTS.json | 4936 ++++++++---------
src/mocks/msw/fixtures/GET_PROVIDERS.json | 20 +-
.../msw/fixtures/GET_PROVIDERS_MODELS.json | 52 +-
src/mocks/msw/fixtures/GET_WORKSPACES.json | 36 +-
src/mocks/msw/handlers.ts | 146 +-
src/mocks/msw/mockers/alert.mock.ts | 52 +-
src/mocks/msw/mockers/conversation.mock.ts | 32 +-
src/mocks/msw/mockers/token-usage.mock.ts | 8 +-
src/mocks/msw/node.ts | 6 +-
.../route-certificate-security.test.tsx | 30 +-
.../__tests__/route-certificates.test.tsx | 118 +-
src/routes/__tests__/route-chat.test.tsx | 288 +-
src/routes/__tests__/route-dashboard.test.tsx | 222 +-
src/routes/__tests__/route-workspace.test.tsx | 192 +-
.../__tests__/route-workspaces.test.tsx | 76 +-
src/routes/route-certificate-security.tsx | 58 +-
src/routes/route-certificates.tsx | 160 +-
src/routes/route-chat.tsx | 62 +-
src/routes/route-dashboard.tsx | 16 +-
src/routes/route-not-found.tsx | 16 +-
src/routes/route-provider-create.tsx | 38 +-
src/routes/route-provider-update.tsx | 32 +-
src/routes/route-providers.tsx | 20 +-
src/routes/route-workspace-creation.tsx | 12 +-
src/routes/route-workspace.tsx | 34 +-
src/routes/route-workspaces.tsx | 26 +-
src/test/msw-endpoint.ts | 8 +-
src/types/openapi-ts.ts | 10 +-
src/types/react-query.ts | 4 +-
src/vite-env.d.ts | 4 +-
tailwind.config.ts | 62 +-
vite.config.ts | 20 +-
vitest.config.ts | 68 +-
vitest.setup.ts | 56 +-
196 files changed, 7568 insertions(+), 7744 deletions(-)
create mode 100644 .prettierrc
diff --git a/.github/ISSUE_TEMPLATE/1-bug.yml b/.github/ISSUE_TEMPLATE/1-bug.yml
index 040206fa..0911dfd6 100644
--- a/.github/ISSUE_TEMPLATE/1-bug.yml
+++ b/.github/ISSUE_TEMPLATE/1-bug.yml
@@ -1,9 +1,9 @@
-name: "Bug"
-description: "Report a bug to help us improve the proxy system."
-type: "Bug 🐞"
-title: "-- Provide a general summary of the issue --"
-labels: ["bug", "needs-triage"]
-assignees: "-"
+name: 'Bug'
+description: 'Report a bug to help us improve the proxy system.'
+type: 'Bug 🐞'
+title: '-- Provide a general summary of the issue --'
+labels: ['bug', 'needs-triage']
+assignees: '-'
body:
- type: markdown
attributes:
@@ -12,24 +12,24 @@ body:
- type: textarea
id: what-happened
attributes:
- label: "Describe the issue"
- description: "A clear and concise description of what the bug is. If applicable, add screenshots to illustrate the problem."
+ label: 'Describe the issue'
+ description: 'A clear and concise description of what the bug is. If applicable, add screenshots to illustrate the problem.'
validations:
required: true
- type: textarea
id: reproduce-steps
attributes:
- label: "Steps to Reproduce"
- description: "Describe the steps to reproduce the behavior."
+ label: 'Steps to Reproduce'
+ description: 'Describe the steps to reproduce the behavior.'
validations:
required: true
- type: dropdown
id: operating-system
attributes:
- label: "Operating System"
- description: "Select the operating system where the issue occurred."
+ label: 'Operating System'
+ description: 'Select the operating system where the issue occurred.'
options:
- Microsoft Windows (Intel)
- Microsoft Windows (Arm)
@@ -44,26 +44,26 @@ body:
- type: input
id: ide-version
attributes:
- label: "IDE and Version"
- description: "Enter the IDE name and version."
- placeholder: "e.g. VS Code 1.78.0"
+ label: 'IDE and Version'
+ description: 'Enter the IDE name and version.'
+ placeholder: 'e.g. VS Code 1.78.0'
validations:
required: true
- type: input
id: extension-version
attributes:
- label: "Extension and Version"
- description: "Enter the extension name and version."
- placeholder: "e.g. Proxy Extension 0.5.1"
+ label: 'Extension and Version'
+ description: 'Enter the extension name and version.'
+ placeholder: 'e.g. Proxy Extension 0.5.1'
validations:
required: true
- type: dropdown
id: provider
attributes:
- label: "Provider"
- description: "Select the provider used."
+ label: 'Provider'
+ description: 'Select the provider used.'
options:
- Anthropic
- OpenAI
@@ -77,32 +77,32 @@ body:
- type: input
id: model
attributes:
- label: "Model"
- description: "Enter the model name used (e.g. GPT-4, Claude 3)."
- placeholder: "e.g. GPT-4"
+ label: 'Model'
+ description: 'Enter the model name used (e.g. GPT-4, Claude 3).'
+ placeholder: 'e.g. GPT-4'
validations:
required: true
- type: input
id: codegate-version
attributes:
- label: "Codegate version"
- description: "Enter the version of CodeGate (e.g. `v0.1.8`, `4845e00c039e`)."
- placeholder: "e.g. v0.1.8"
+ label: 'Codegate version'
+ description: 'Enter the version of CodeGate (e.g. `v0.1.8`, `4845e00c039e`).'
+ placeholder: 'e.g. v0.1.8'
validations:
required: true
- type: textarea
id: logs
attributes:
- label: "Logs"
- description: "If applicable, paste logs or error messages."
- placeholder: "Paste log content here."
+ label: 'Logs'
+ description: 'If applicable, paste logs or error messages.'
+ placeholder: 'Paste log content here.'
- type: textarea
id: additional-context
attributes:
- label: "Additional Context"
- description: "Add any other context or details about the problem here (e.g. link to Discussion, etc.)."
+ label: 'Additional Context'
+ description: 'Add any other context or details about the problem here (e.g. link to Discussion, etc.).'
validations:
required: false
diff --git a/.github/ISSUE_TEMPLATE/2-task.yml b/.github/ISSUE_TEMPLATE/2-task.yml
index e7a1691b..b8e570e1 100644
--- a/.github/ISSUE_TEMPLATE/2-task.yml
+++ b/.github/ISSUE_TEMPLATE/2-task.yml
@@ -1,7 +1,7 @@
name: Task
description: Task request
-type: "Task ✅"
-title: "[Task]: "
+type: 'Task ✅'
+title: '[Task]: '
labels: []
body:
- type: textarea
@@ -16,7 +16,7 @@ body:
- type: textarea
id: additional-context
attributes:
- label: "Additional Context"
- description: "Add any other context or details about the problem here (e.g. link to Discussion, etc.)."
+ label: 'Additional Context'
+ description: 'Add any other context or details about the problem here (e.g. link to Discussion, etc.).'
validations:
required: false
diff --git a/.github/ISSUE_TEMPLATE/3-chore.yml b/.github/ISSUE_TEMPLATE/3-chore.yml
index 6086468f..c84dba36 100644
--- a/.github/ISSUE_TEMPLATE/3-chore.yml
+++ b/.github/ISSUE_TEMPLATE/3-chore.yml
@@ -1,8 +1,8 @@
name: Chore
description: Chore request
-type: "Chore 🧹"
-title: "[Chore]: "
-labels: ["chore", "needs-triage"]
+type: 'Chore 🧹'
+title: '[Chore]: '
+labels: ['chore', 'needs-triage']
body:
- type: textarea
id: description
@@ -16,7 +16,7 @@ body:
- type: textarea
id: additional-context
attributes:
- label: "Additional Context"
- description: "Add any other context or details about the problem here (e.g. link to Discussion, etc.)."
+ label: 'Additional Context'
+ description: 'Add any other context or details about the problem here (e.g. link to Discussion, etc.).'
validations:
required: false
diff --git a/.github/workflows/_security-checks.yml b/.github/workflows/_security-checks.yml
index 665f6920..c2da6a2e 100644
--- a/.github/workflows/_security-checks.yml
+++ b/.github/workflows/_security-checks.yml
@@ -2,7 +2,7 @@ name: Security Checks
on:
workflow_call:
permissions:
- contents: read
+ contents: read
jobs:
trivy:
name: Trivy
diff --git a/.github/workflows/_static-checks.yml b/.github/workflows/_static-checks.yml
index 8fec1737..800eccd8 100644
--- a/.github/workflows/_static-checks.yml
+++ b/.github/workflows/_static-checks.yml
@@ -2,7 +2,7 @@ name: Static Checks
on:
workflow_call:
permissions:
- contents: read
+ contents: read
jobs:
lint:
name: ESLint Check
@@ -34,7 +34,7 @@ jobs:
name: Build App Check
runs-on: ubuntu-latest
env:
- NODE_OPTIONS: "--max_old_space_size=4096"
+ NODE_OPTIONS: '--max_old_space_size=4096'
steps:
- name: Checkout Repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 00000000..933a1b15
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,15 @@
+{
+ "semi": false,
+ "trailingComma": "es5",
+ "singleQuote": true,
+ "tabWidth": 2,
+ "useTabs": false,
+ "plugins": [
+ "prettier-plugin-tailwindcss",
+ "prettier-plugin-classnames",
+ "prettier-plugin-merge"
+ ],
+ "tailwindFunctions": ["tv", "twMerge", "composeTailwindRenderProps"],
+ "customFunctions": ["tv", "twMerge", "composeTailwindRenderProps"],
+ "tailwindConfig": "./tailwind.config.ts"
+}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6ef07787..1ac78559 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,255 +2,223 @@
## [0.14.1](https://github.com/stacklok/codegate-ui/compare/v0.14.0...v0.14.1) (2025-02-07)
-
### Bug Fixes
-* set 5s of duration for all the toast notifications ([#281](https://github.com/stacklok/codegate-ui/issues/281)) ([2542b5f](https://github.com/stacklok/codegate-ui/commit/2542b5fe2c6e67ea8ac055baa47100591e468570))
+- set 5s of duration for all the toast notifications ([#281](https://github.com/stacklok/codegate-ui/issues/281)) ([2542b5f](https://github.com/stacklok/codegate-ui/commit/2542b5fe2c6e67ea8ac055baa47100591e468570))
## [0.14.0](https://github.com/stacklok/codegate-ui/compare/v0.13.1...v0.14.0) (2025-02-07)
-
### Features
-* show alert detail in the conversation page ([#277](https://github.com/stacklok/codegate-ui/issues/277)) ([8154d3e](https://github.com/stacklok/codegate-ui/commit/8154d3e9c8082f6aac0fc2914621dd99f8f71936))
+- show alert detail in the conversation page ([#277](https://github.com/stacklok/codegate-ui/issues/277)) ([8154d3e](https://github.com/stacklok/codegate-ui/commit/8154d3e9c8082f6aac0fc2914621dd99f8f71936))
## [0.13.1](https://github.com/stacklok/codegate-ui/compare/v0.13.0...v0.13.1) (2025-02-07)
-
### Bug Fixes
-* provider endpoint and mux section ([#275](https://github.com/stacklok/codegate-ui/issues/275)) ([cadbd5f](https://github.com/stacklok/codegate-ui/commit/cadbd5f71125a9a0dac9824c6f1bdf8aabca4071))
-
+- provider endpoint and mux section ([#275](https://github.com/stacklok/codegate-ui/issues/275)) ([cadbd5f](https://github.com/stacklok/codegate-ui/commit/cadbd5f71125a9a0dac9824c6f1bdf8aabca4071))
### Reverts
-* openapi-ts upgrade ([#273](https://github.com/stacklok/codegate-ui/issues/273)) ([2a5234c](https://github.com/stacklok/codegate-ui/commit/2a5234c1246577070fd978397db87b637c42cd9f))
+- openapi-ts upgrade ([#273](https://github.com/stacklok/codegate-ui/issues/273)) ([2a5234c](https://github.com/stacklok/codegate-ui/commit/2a5234c1246577070fd978397db87b637c42cd9f))
## [0.13.0](https://github.com/stacklok/codegate-ui/compare/v0.12.2...v0.13.0) (2025-02-06)
-
### Features
-* enforce sensible defaults for react-query ([#268](https://github.com/stacklok/codegate-ui/issues/268)) ([5445eb6](https://github.com/stacklok/codegate-ui/commit/5445eb6aefbd6270d7258dd3bbf3d03c1beea83a))
+- enforce sensible defaults for react-query ([#268](https://github.com/stacklok/codegate-ui/issues/268)) ([5445eb6](https://github.com/stacklok/codegate-ui/commit/5445eb6aefbd6270d7258dd3bbf3d03c1beea83a))
## [0.12.2](https://github.com/stacklok/codegate-ui/compare/v0.12.1...v0.12.2) (2025-02-05)
-
### Bug Fixes
-* implement default caching options for react-query ([#265](https://github.com/stacklok/codegate-ui/issues/265)) ([694964f](https://github.com/stacklok/codegate-ui/commit/694964f7f0adfb3f3181da2680c98e95016e534f))
+- implement default caching options for react-query ([#265](https://github.com/stacklok/codegate-ui/issues/265)) ([694964f](https://github.com/stacklok/codegate-ui/commit/694964f7f0adfb3f3181da2680c98e95016e534f))
## [0.12.1](https://github.com/stacklok/codegate-ui/compare/v0.12.0...v0.12.1) (2025-02-05)
-
### Bug Fixes
-* create form provider submit ([#263](https://github.com/stacklok/codegate-ui/issues/263)) ([3e4632e](https://github.com/stacklok/codegate-ui/commit/3e4632e8b169abb3605d86d27ccf79004ba13a41))
+- create form provider submit ([#263](https://github.com/stacklok/codegate-ui/issues/263)) ([3e4632e](https://github.com/stacklok/codegate-ui/commit/3e4632e8b169abb3605d86d27ccf79004ba13a41))
## [0.12.0](https://github.com/stacklok/codegate-ui/compare/v0.11.1...v0.12.0) (2025-02-05)
-
### Features
-* add providers page and enable muxing ([#253](https://github.com/stacklok/codegate-ui/issues/253)) ([22c47ae](https://github.com/stacklok/codegate-ui/commit/22c47aed9aa3f527b17a4b14badff31a80100039))
-* react-query-utils ([#257](https://github.com/stacklok/codegate-ui/issues/257)) ([7d4a854](https://github.com/stacklok/codegate-ui/commit/7d4a854510db81e3b05e19db2d0a4f63363cd782))
+- add providers page and enable muxing ([#253](https://github.com/stacklok/codegate-ui/issues/253)) ([22c47ae](https://github.com/stacklok/codegate-ui/commit/22c47aed9aa3f527b17a4b14badff31a80100039))
+- react-query-utils ([#257](https://github.com/stacklok/codegate-ui/issues/257)) ([7d4a854](https://github.com/stacklok/codegate-ui/commit/7d4a854510db81e3b05e19db2d0a4f63363cd782))
## [0.11.1](https://github.com/stacklok/codegate-ui/compare/v0.11.0...v0.11.1) (2025-02-05)
-
### Bug Fixes
-* don't call window reload every time we receive an alert ([#251](https://github.com/stacklok/codegate-ui/issues/251)) ([a2da3ae](https://github.com/stacklok/codegate-ui/commit/a2da3aeac595ee4328e833858a81b34a2a0472cc))
+- don't call window reload every time we receive an alert ([#251](https://github.com/stacklok/codegate-ui/issues/251)) ([a2da3ae](https://github.com/stacklok/codegate-ui/commit/a2da3aeac595ee4328e833858a81b34a2a0472cc))
## [0.11.0](https://github.com/stacklok/codegate-ui/compare/v0.10.0...v0.11.0) (2025-02-03)
-
### Features
-* **alerts:** tabs for filtering table ([#234](https://github.com/stacklok/codegate-ui/issues/234)) ([2765917](https://github.com/stacklok/codegate-ui/commit/27659171019e0428279830049bbc38eace0123cc))
-* format token usage with "compact" notation ([#238](https://github.com/stacklok/codegate-ui/issues/238)) ([9911a7a](https://github.com/stacklok/codegate-ui/commit/9911a7a580d84c493276c547b252dde1bea23180))
-* implement empty state for tables ([#232](https://github.com/stacklok/codegate-ui/issues/232)) ([e39dbc3](https://github.com/stacklok/codegate-ui/commit/e39dbc3a715ac330d2aa15d4dc2dc5a7bbc77c00))
-* remove word "Dashboard" from header ([#236](https://github.com/stacklok/codegate-ui/issues/236)) ([89a6dc4](https://github.com/stacklok/codegate-ui/commit/89a6dc4feaa47d7293400de9247ab15f4c0d3a55))
-* richer empty states ([#239](https://github.com/stacklok/codegate-ui/issues/239)) ([c8990c2](https://github.com/stacklok/codegate-ui/commit/c8990c2687ca8f4da3c481f328373ec95aa18859))
+- **alerts:** tabs for filtering table ([#234](https://github.com/stacklok/codegate-ui/issues/234)) ([2765917](https://github.com/stacklok/codegate-ui/commit/27659171019e0428279830049bbc38eace0123cc))
+- format token usage with "compact" notation ([#238](https://github.com/stacklok/codegate-ui/issues/238)) ([9911a7a](https://github.com/stacklok/codegate-ui/commit/9911a7a580d84c493276c547b252dde1bea23180))
+- implement empty state for tables ([#232](https://github.com/stacklok/codegate-ui/issues/232)) ([e39dbc3](https://github.com/stacklok/codegate-ui/commit/e39dbc3a715ac330d2aa15d4dc2dc5a7bbc77c00))
+- remove word "Dashboard" from header ([#236](https://github.com/stacklok/codegate-ui/issues/236)) ([89a6dc4](https://github.com/stacklok/codegate-ui/commit/89a6dc4feaa47d7293400de9247ab15f4c0d3a55))
+- richer empty states ([#239](https://github.com/stacklok/codegate-ui/issues/239)) ([c8990c2](https://github.com/stacklok/codegate-ui/commit/c8990c2687ca8f4da3c481f328373ec95aa18859))
## [0.10.0](https://github.com/stacklok/codegate-ui/compare/v0.9.0...v0.10.0) (2025-01-30)
-
### Features
-* add prompt picker ([#212](https://github.com/stacklok/codegate-ui/issues/212)) ([25a531e](https://github.com/stacklok/codegate-ui/commit/25a531eba9b75cc7533a2824ec647894019bb09d))
-* add workspace preferred model ([#217](https://github.com/stacklok/codegate-ui/issues/217)) ([90dfbe9](https://github.com/stacklok/codegate-ui/commit/90dfbe9befc97d800f2a55f23390e9a1754c0aa2))
-* bump UIKit to incorporate color palette change ([#229](https://github.com/stacklok/codegate-ui/issues/229)) ([0e877b4](https://github.com/stacklok/codegate-ui/commit/0e877b4d3dd4a6cc1ebaef7f2a394e23aeaff22d))
-* implement new table design ([#189](https://github.com/stacklok/codegate-ui/issues/189)) ([97bc4ea](https://github.com/stacklok/codegate-ui/commit/97bc4ea104cbd88e81dbd1aadd055fc04687f977))
-* initial work on alerts summary cards ([#222](https://github.com/stacklok/codegate-ui/issues/222)) ([2732434](https://github.com/stacklok/codegate-ui/commit/2732434e45e0ff2862b0173cc708581992e63d18))
-* move health check to header ([#218](https://github.com/stacklok/codegate-ui/issues/218)) ([d9652bb](https://github.com/stacklok/codegate-ui/commit/d9652bb3ac005d5140031f4378d54f41b3adafd1))
-* quick "go to settings" for workspace selector ([#213](https://github.com/stacklok/codegate-ui/issues/213)) ([b683d56](https://github.com/stacklok/codegate-ui/commit/b683d56ae7ab5cfceb527727fc86a298583bc8cc))
-* replace lucide icons with untitled icons ([#221](https://github.com/stacklok/codegate-ui/issues/221)) ([2c3c7fc](https://github.com/stacklok/codegate-ui/commit/2c3c7fc06e3d13489c3e424689013a6431eb1977))
-* show token usage on alerts table ([#216](https://github.com/stacklok/codegate-ui/issues/216)) ([f9461bc](https://github.com/stacklok/codegate-ui/commit/f9461bcf8452715086362154659998f287b965d5))
-
+- add prompt picker ([#212](https://github.com/stacklok/codegate-ui/issues/212)) ([25a531e](https://github.com/stacklok/codegate-ui/commit/25a531eba9b75cc7533a2824ec647894019bb09d))
+- add workspace preferred model ([#217](https://github.com/stacklok/codegate-ui/issues/217)) ([90dfbe9](https://github.com/stacklok/codegate-ui/commit/90dfbe9befc97d800f2a55f23390e9a1754c0aa2))
+- bump UIKit to incorporate color palette change ([#229](https://github.com/stacklok/codegate-ui/issues/229)) ([0e877b4](https://github.com/stacklok/codegate-ui/commit/0e877b4d3dd4a6cc1ebaef7f2a394e23aeaff22d))
+- implement new table design ([#189](https://github.com/stacklok/codegate-ui/issues/189)) ([97bc4ea](https://github.com/stacklok/codegate-ui/commit/97bc4ea104cbd88e81dbd1aadd055fc04687f977))
+- initial work on alerts summary cards ([#222](https://github.com/stacklok/codegate-ui/issues/222)) ([2732434](https://github.com/stacklok/codegate-ui/commit/2732434e45e0ff2862b0173cc708581992e63d18))
+- move health check to header ([#218](https://github.com/stacklok/codegate-ui/issues/218)) ([d9652bb](https://github.com/stacklok/codegate-ui/commit/d9652bb3ac005d5140031f4378d54f41b3adafd1))
+- quick "go to settings" for workspace selector ([#213](https://github.com/stacklok/codegate-ui/issues/213)) ([b683d56](https://github.com/stacklok/codegate-ui/commit/b683d56ae7ab5cfceb527727fc86a298583bc8cc))
+- replace lucide icons with untitled icons ([#221](https://github.com/stacklok/codegate-ui/issues/221)) ([2c3c7fc](https://github.com/stacklok/codegate-ui/commit/2c3c7fc06e3d13489c3e424689013a6431eb1977))
+- show token usage on alerts table ([#216](https://github.com/stacklok/codegate-ui/issues/216)) ([f9461bc](https://github.com/stacklok/codegate-ui/commit/f9461bcf8452715086362154659998f287b965d5))
### Bug Fixes
-* **alert detected type:** don't default to leaked secret ([#208](https://github.com/stacklok/codegate-ui/issues/208)) ([0e64263](https://github.com/stacklok/codegate-ui/commit/0e64263f00258d0d8a7139ae5256696701673959))
-* **alerts:** memoize selecting alerts ([#231](https://github.com/stacklok/codegate-ui/issues/231)) ([f810e2b](https://github.com/stacklok/codegate-ui/commit/f810e2b0e77b1992e8b54b91f301943505fcf03b))
-* issue templates fix ([#224](https://github.com/stacklok/codegate-ui/issues/224)) ([4a1cc7d](https://github.com/stacklok/codegate-ui/commit/4a1cc7d3f70d1ce78d1933de56467f844a4308cb))
-* issue templates validation fix ([#225](https://github.com/stacklok/codegate-ui/issues/225)) ([efec5fa](https://github.com/stacklok/codegate-ui/commit/efec5fa1e82195a09245cfbc8c6ec7ed58f7c3b9))
-* restore lockfile ([#223](https://github.com/stacklok/codegate-ui/issues/223)) ([a3e86e5](https://github.com/stacklok/codegate-ui/commit/a3e86e58272cd4c434ae2d816f80d4c278461786))
+- **alert detected type:** don't default to leaked secret ([#208](https://github.com/stacklok/codegate-ui/issues/208)) ([0e64263](https://github.com/stacklok/codegate-ui/commit/0e64263f00258d0d8a7139ae5256696701673959))
+- **alerts:** memoize selecting alerts ([#231](https://github.com/stacklok/codegate-ui/issues/231)) ([f810e2b](https://github.com/stacklok/codegate-ui/commit/f810e2b0e77b1992e8b54b91f301943505fcf03b))
+- issue templates fix ([#224](https://github.com/stacklok/codegate-ui/issues/224)) ([4a1cc7d](https://github.com/stacklok/codegate-ui/commit/4a1cc7d3f70d1ce78d1933de56467f844a4308cb))
+- issue templates validation fix ([#225](https://github.com/stacklok/codegate-ui/issues/225)) ([efec5fa](https://github.com/stacklok/codegate-ui/commit/efec5fa1e82195a09245cfbc8c6ec7ed58f7c3b9))
+- restore lockfile ([#223](https://github.com/stacklok/codegate-ui/issues/223)) ([a3e86e5](https://github.com/stacklok/codegate-ui/commit/a3e86e58272cd4c434ae2d816f80d4c278461786))
## [0.9.0](https://github.com/stacklok/codegate-ui/compare/v0.8.0...v0.9.0) (2025-01-24)
-
### Features
-* add not_found route ([#194](https://github.com/stacklok/codegate-ui/issues/194)) ([d865730](https://github.com/stacklok/codegate-ui/commit/d865730201eaff07e0e1852750803d0a262e5df6))
-
+- add not_found route ([#194](https://github.com/stacklok/codegate-ui/issues/194)) ([d865730](https://github.com/stacklok/codegate-ui/commit/d865730201eaff07e0e1852750803d0a262e5df6))
### Bug Fixes
-* workspace hard delete nits ([#202](https://github.com/stacklok/codegate-ui/issues/202)) ([9129a5b](https://github.com/stacklok/codegate-ui/commit/9129a5b983a1b54f7d3b5cffc19cce85a955045a))
+- workspace hard delete nits ([#202](https://github.com/stacklok/codegate-ui/issues/202)) ([9129a5b](https://github.com/stacklok/codegate-ui/commit/9129a5b983a1b54f7d3b5cffc19cce85a955045a))
## [0.8.0](https://github.com/stacklok/codegate-ui/compare/v0.7.2...v0.8.0) (2025-01-24)
-
### Features
-* implement hard delete for workspaces & refactor workspaces table to allow multiple actions ([#185](https://github.com/stacklok/codegate-ui/issues/185)) ([a98492a](https://github.com/stacklok/codegate-ui/commit/a98492a8c24067ccd037e6fa753caf9994558967))
-* implement use toast mutation for workspaces ([#184](https://github.com/stacklok/codegate-ui/issues/184)) ([a67b265](https://github.com/stacklok/codegate-ui/commit/a67b26518cd5ef1c3f6106b604d45456ffc03cf4))
-* redirect to conversation from alerts table ([#191](https://github.com/stacklok/codegate-ui/issues/191)) ([646ed5a](https://github.com/stacklok/codegate-ui/commit/646ed5a6b14661b3956dd2685d9065c4c0d110aa))
-* useKbdShortcuts hook & example implementation ([#180](https://github.com/stacklok/codegate-ui/issues/180)) ([0d935a3](https://github.com/stacklok/codegate-ui/commit/0d935a3b263d5c18c86d5ba669554cf3f2e1f3a7))
-* useToastMutation hook ([#183](https://github.com/stacklok/codegate-ui/issues/183)) ([9fe55a5](https://github.com/stacklok/codegate-ui/commit/9fe55a524fa776348fcb6719d625f57aac36d60a))
-
+- implement hard delete for workspaces & refactor workspaces table to allow multiple actions ([#185](https://github.com/stacklok/codegate-ui/issues/185)) ([a98492a](https://github.com/stacklok/codegate-ui/commit/a98492a8c24067ccd037e6fa753caf9994558967))
+- implement use toast mutation for workspaces ([#184](https://github.com/stacklok/codegate-ui/issues/184)) ([a67b265](https://github.com/stacklok/codegate-ui/commit/a67b26518cd5ef1c3f6106b604d45456ffc03cf4))
+- redirect to conversation from alerts table ([#191](https://github.com/stacklok/codegate-ui/issues/191)) ([646ed5a](https://github.com/stacklok/codegate-ui/commit/646ed5a6b14661b3956dd2685d9065c4c0d110aa))
+- useKbdShortcuts hook & example implementation ([#180](https://github.com/stacklok/codegate-ui/issues/180)) ([0d935a3](https://github.com/stacklok/codegate-ui/commit/0d935a3b263d5c18c86d5ba669554cf3f2e1f3a7))
+- useToastMutation hook ([#183](https://github.com/stacklok/codegate-ui/issues/183)) ([9fe55a5](https://github.com/stacklok/codegate-ui/commit/9fe55a524fa776348fcb6719d625f57aac36d60a))
### Bug Fixes
-* fix small visual glithc in help menu ([#175](https://github.com/stacklok/codegate-ui/issues/175)) ([7031047](https://github.com/stacklok/codegate-ui/commit/70310476e59085c18755dc3eed6d9a2f09523f0f))
-* parsing promptList text and breadcrumb ([#177](https://github.com/stacklok/codegate-ui/issues/177)) ([6da034d](https://github.com/stacklok/codegate-ui/commit/6da034d9ddb028202c14851319e380f61b139473))
-* sort filtered alerts before pagination ([#190](https://github.com/stacklok/codegate-ui/issues/190)) ([d844610](https://github.com/stacklok/codegate-ui/commit/d84461041076f9647ceae93220deb071d1897a17))
+- fix small visual glithc in help menu ([#175](https://github.com/stacklok/codegate-ui/issues/175)) ([7031047](https://github.com/stacklok/codegate-ui/commit/70310476e59085c18755dc3eed6d9a2f09523f0f))
+- parsing promptList text and breadcrumb ([#177](https://github.com/stacklok/codegate-ui/issues/177)) ([6da034d](https://github.com/stacklok/codegate-ui/commit/6da034d9ddb028202c14851319e380f61b139473))
+- sort filtered alerts before pagination ([#190](https://github.com/stacklok/codegate-ui/issues/190)) ([d844610](https://github.com/stacklok/codegate-ui/commit/d84461041076f9647ceae93220deb071d1897a17))
## [0.7.2](https://github.com/stacklok/codegate-ui/compare/v0.7.1...v0.7.2) (2025-01-22)
-
### Bug Fixes
-* fix client side navigation ([#173](https://github.com/stacklok/codegate-ui/issues/173)) ([30b1112](https://github.com/stacklok/codegate-ui/commit/30b11127c2ea4303a3973fcc1f4f89a7957e6710))
-* make custom icons inherit text color ([#170](https://github.com/stacklok/codegate-ui/issues/170)) ([e8c3880](https://github.com/stacklok/codegate-ui/commit/e8c3880e4663c198e77383d21406741c14c002fc)), closes [#156](https://github.com/stacklok/codegate-ui/issues/156)
+- fix client side navigation ([#173](https://github.com/stacklok/codegate-ui/issues/173)) ([30b1112](https://github.com/stacklok/codegate-ui/commit/30b11127c2ea4303a3973fcc1f4f89a7957e6710))
+- make custom icons inherit text color ([#170](https://github.com/stacklok/codegate-ui/issues/170)) ([e8c3880](https://github.com/stacklok/codegate-ui/commit/e8c3880e4663c198e77383d21406741c14c002fc)), closes [#156](https://github.com/stacklok/codegate-ui/issues/156)
## [0.7.1](https://github.com/stacklok/codegate-ui/compare/v0.7.0...v0.7.1) (2025-01-22)
-
### Bug Fixes
-* show banner in archived workspace ([#165](https://github.com/stacklok/codegate-ui/issues/165)) ([e77a837](https://github.com/stacklok/codegate-ui/commit/e77a83722f5a83b65d185b14c7e618889703b904))
+- show banner in archived workspace ([#165](https://github.com/stacklok/codegate-ui/issues/165)) ([e77a837](https://github.com/stacklok/codegate-ui/commit/e77a83722f5a83b65d185b14c7e618889703b904))
## [0.7.0](https://github.com/stacklok/codegate-ui/compare/v0.6.1...v0.7.0) (2025-01-22)
-
### Features
-* add create workspace ([#133](https://github.com/stacklok/codegate-ui/issues/133)) ([1c3e0f0](https://github.com/stacklok/codegate-ui/commit/1c3e0f04e698d889d7a7e36234e31a0672c82933))
-* add workspaces page ([#128](https://github.com/stacklok/codegate-ui/issues/128)) ([750d8f6](https://github.com/stacklok/codegate-ui/commit/750d8f6db2fe6110ffe6c720db663d8fae2163f1))
-* archive workspace ([#145](https://github.com/stacklok/codegate-ui/issues/145)) ([34dc350](https://github.com/stacklok/codegate-ui/commit/34dc35090d14a959189deedae49454fca0dba08e))
-* enable toggle workspace, invalidate on workspace update ([#130](https://github.com/stacklok/codegate-ui/issues/130)) ([8904d8b](https://github.com/stacklok/codegate-ui/commit/8904d8bfc4b8946beeb7506df72d2b12504b33d1))
-* implement breadcrumbs according to design ([#131](https://github.com/stacklok/codegate-ui/issues/131)) ([43fdf83](https://github.com/stacklok/codegate-ui/commit/43fdf83d8c7fca94cf04a7d0a13b06ba30f7ea74))
-* implement new menu design ([#152](https://github.com/stacklok/codegate-ui/issues/152)) ([c63de9e](https://github.com/stacklok/codegate-ui/commit/c63de9e1c649d589421ecc94783a6e12a7c132c1))
-* rename workspace ([#163](https://github.com/stacklok/codegate-ui/issues/163)) ([3b68754](https://github.com/stacklok/codegate-ui/commit/3b6875489c92a7d7629250e1d984f36f4e56bda5))
-* show archived workspaces and restore it ([#154](https://github.com/stacklok/codegate-ui/issues/154)) ([d7e244f](https://github.com/stacklok/codegate-ui/commit/d7e244fecb358c572a26490344c30e5362b2c78a))
-* workspace scoped queries (messages & alerts) ([#140](https://github.com/stacklok/codegate-ui/issues/140)) ([500d48d](https://github.com/stacklok/codegate-ui/commit/500d48d06014b6e54bc55942a7cc705628bc30bc))
-* workspace settings route & system prompt editor ([#114](https://github.com/stacklok/codegate-ui/issues/114)) ([0d9d752](https://github.com/stacklok/codegate-ui/commit/0d9d75226e4670b54e33c9e184c2339b341eaee9))
-* workspace system prompt CRUD ([#147](https://github.com/stacklok/codegate-ui/issues/147)) ([0ed8d66](https://github.com/stacklok/codegate-ui/commit/0ed8d669862239129af93c6c998461f25a83849f))
-* **workspace:** add dropdown ([#123](https://github.com/stacklok/codegate-ui/issues/123)) ([51fd254](https://github.com/stacklok/codegate-ui/commit/51fd254affd1333b3f839fb6de7b5058fce68ca2))
-
-
-### Bug Fixes
-
-* buttons in workspace page ([#159](https://github.com/stacklok/codegate-ui/issues/159)) ([1d80c9f](https://github.com/stacklok/codegate-ui/commit/1d80c9fa761cb3da341ef471480d4665effd86c1))
-* cert instructions ([#150](https://github.com/stacklok/codegate-ui/issues/150)) ([81a1846](https://github.com/stacklok/codegate-ui/commit/81a1846767d7d2996848b0c26bf296e989f620ce))
-* enter to submit workspace create form ([#142](https://github.com/stacklok/codegate-ui/issues/142)) ([14a9cfe](https://github.com/stacklok/codegate-ui/commit/14a9cfe642e6cb0e8def62422d2fb4f562d0a729))
-* fix providers for better debugging ([#141](https://github.com/stacklok/codegate-ui/issues/141)) ([d471fd0](https://github.com/stacklok/codegate-ui/commit/d471fd0e91b5adc30aad34acb027d922c5026302))
-* title changed ([#158](https://github.com/stacklok/codegate-ui/issues/158)) ([ff6ce7f](https://github.com/stacklok/codegate-ui/commit/ff6ce7fa73039cc8f32fcabd1b1f11d886d25823))
-* workspace ui nits ([#132](https://github.com/stacklok/codegate-ui/issues/132)) ([2c97fd4](https://github.com/stacklok/codegate-ui/commit/2c97fd4982a17ece0c6c0475bd8d8ac7ad1420a7))
-* workspaces dropdown overflow ([#138](https://github.com/stacklok/codegate-ui/issues/138)) ([bc08ac9](https://github.com/stacklok/codegate-ui/commit/bc08ac918e8de8f3a01f987549459cd108e7d4fc))
+- add create workspace ([#133](https://github.com/stacklok/codegate-ui/issues/133)) ([1c3e0f0](https://github.com/stacklok/codegate-ui/commit/1c3e0f04e698d889d7a7e36234e31a0672c82933))
+- add workspaces page ([#128](https://github.com/stacklok/codegate-ui/issues/128)) ([750d8f6](https://github.com/stacklok/codegate-ui/commit/750d8f6db2fe6110ffe6c720db663d8fae2163f1))
+- archive workspace ([#145](https://github.com/stacklok/codegate-ui/issues/145)) ([34dc350](https://github.com/stacklok/codegate-ui/commit/34dc35090d14a959189deedae49454fca0dba08e))
+- enable toggle workspace, invalidate on workspace update ([#130](https://github.com/stacklok/codegate-ui/issues/130)) ([8904d8b](https://github.com/stacklok/codegate-ui/commit/8904d8bfc4b8946beeb7506df72d2b12504b33d1))
+- implement breadcrumbs according to design ([#131](https://github.com/stacklok/codegate-ui/issues/131)) ([43fdf83](https://github.com/stacklok/codegate-ui/commit/43fdf83d8c7fca94cf04a7d0a13b06ba30f7ea74))
+- implement new menu design ([#152](https://github.com/stacklok/codegate-ui/issues/152)) ([c63de9e](https://github.com/stacklok/codegate-ui/commit/c63de9e1c649d589421ecc94783a6e12a7c132c1))
+- rename workspace ([#163](https://github.com/stacklok/codegate-ui/issues/163)) ([3b68754](https://github.com/stacklok/codegate-ui/commit/3b6875489c92a7d7629250e1d984f36f4e56bda5))
+- show archived workspaces and restore it ([#154](https://github.com/stacklok/codegate-ui/issues/154)) ([d7e244f](https://github.com/stacklok/codegate-ui/commit/d7e244fecb358c572a26490344c30e5362b2c78a))
+- workspace scoped queries (messages & alerts) ([#140](https://github.com/stacklok/codegate-ui/issues/140)) ([500d48d](https://github.com/stacklok/codegate-ui/commit/500d48d06014b6e54bc55942a7cc705628bc30bc))
+- workspace settings route & system prompt editor ([#114](https://github.com/stacklok/codegate-ui/issues/114)) ([0d9d752](https://github.com/stacklok/codegate-ui/commit/0d9d75226e4670b54e33c9e184c2339b341eaee9))
+- workspace system prompt CRUD ([#147](https://github.com/stacklok/codegate-ui/issues/147)) ([0ed8d66](https://github.com/stacklok/codegate-ui/commit/0ed8d669862239129af93c6c998461f25a83849f))
+- **workspace:** add dropdown ([#123](https://github.com/stacklok/codegate-ui/issues/123)) ([51fd254](https://github.com/stacklok/codegate-ui/commit/51fd254affd1333b3f839fb6de7b5058fce68ca2))
+
+### Bug Fixes
+
+- buttons in workspace page ([#159](https://github.com/stacklok/codegate-ui/issues/159)) ([1d80c9f](https://github.com/stacklok/codegate-ui/commit/1d80c9fa761cb3da341ef471480d4665effd86c1))
+- cert instructions ([#150](https://github.com/stacklok/codegate-ui/issues/150)) ([81a1846](https://github.com/stacklok/codegate-ui/commit/81a1846767d7d2996848b0c26bf296e989f620ce))
+- enter to submit workspace create form ([#142](https://github.com/stacklok/codegate-ui/issues/142)) ([14a9cfe](https://github.com/stacklok/codegate-ui/commit/14a9cfe642e6cb0e8def62422d2fb4f562d0a729))
+- fix providers for better debugging ([#141](https://github.com/stacklok/codegate-ui/issues/141)) ([d471fd0](https://github.com/stacklok/codegate-ui/commit/d471fd0e91b5adc30aad34acb027d922c5026302))
+- title changed ([#158](https://github.com/stacklok/codegate-ui/issues/158)) ([ff6ce7f](https://github.com/stacklok/codegate-ui/commit/ff6ce7fa73039cc8f32fcabd1b1f11d886d25823))
+- workspace ui nits ([#132](https://github.com/stacklok/codegate-ui/issues/132)) ([2c97fd4](https://github.com/stacklok/codegate-ui/commit/2c97fd4982a17ece0c6c0475bd8d8ac7ad1420a7))
+- workspaces dropdown overflow ([#138](https://github.com/stacklok/codegate-ui/issues/138)) ([bc08ac9](https://github.com/stacklok/codegate-ui/commit/bc08ac918e8de8f3a01f987549459cd108e7d4fc))
## [0.6.1](https://github.com/stacklok/codegate-ui/compare/v0.6.0...v0.6.1) (2025-01-20)
-
### Bug Fixes
-* version endpoint ([#119](https://github.com/stacklok/codegate-ui/issues/119)) ([eada275](https://github.com/stacklok/codegate-ui/commit/eada275d8bd40f38b1930bd7fb9be5ddf5cdd494))
+- version endpoint ([#119](https://github.com/stacklok/codegate-ui/issues/119)) ([eada275](https://github.com/stacklok/codegate-ui/commit/eada275d8bd40f38b1930bd7fb9be5ddf5cdd494))
## [0.6.0](https://github.com/stacklok/codegate-ui/compare/v0.5.0...v0.6.0) (2025-01-20)
-
### Features
-* add pagination for alerts table ([#104](https://github.com/stacklok/codegate-ui/issues/104)) ([a38e2cd](https://github.com/stacklok/codegate-ui/commit/a38e2cdd2ced63de130fe3a88118cc00a977b41f))
-* **workspaces:** create workspaces query hook ([#111](https://github.com/stacklok/codegate-ui/issues/111)) ([8ba7d36](https://github.com/stacklok/codegate-ui/commit/8ba7d369a6a51dfc66e4cc445b196bde77969061))
-
+- add pagination for alerts table ([#104](https://github.com/stacklok/codegate-ui/issues/104)) ([a38e2cd](https://github.com/stacklok/codegate-ui/commit/a38e2cdd2ced63de130fe3a88118cc00a977b41f))
+- **workspaces:** create workspaces query hook ([#111](https://github.com/stacklok/codegate-ui/issues/111)) ([8ba7d36](https://github.com/stacklok/codegate-ui/commit/8ba7d369a6a51dfc66e4cc445b196bde77969061))
### Bug Fixes
-* breaking changes in openapi spec ([#113](https://github.com/stacklok/codegate-ui/issues/113)) ([cd549d2](https://github.com/stacklok/codegate-ui/commit/cd549d2feb2d87c97205653458faa586442cfc8c))
+- breaking changes in openapi spec ([#113](https://github.com/stacklok/codegate-ui/issues/113)) ([cd549d2](https://github.com/stacklok/codegate-ui/commit/cd549d2feb2d87c97205653458faa586442cfc8c))
## [0.5.0](https://github.com/stacklok/codegate-ui/compare/v0.4.1...v0.5.0) (2025-01-17)
-
### Features
-* initial work on codegate version check widget ([#94](https://github.com/stacklok/codegate-ui/issues/94)) ([0baa10b](https://github.com/stacklok/codegate-ui/commit/0baa10ba8875c6baa977b34eba637d8b1e11aa63))
-* react-query hey-api integration ([#101](https://github.com/stacklok/codegate-ui/issues/101)) ([58d4d47](https://github.com/stacklok/codegate-ui/commit/58d4d477677f5047c3b7efa7f744a5e88c57dd8f))
+- initial work on codegate version check widget ([#94](https://github.com/stacklok/codegate-ui/issues/94)) ([0baa10b](https://github.com/stacklok/codegate-ui/commit/0baa10ba8875c6baa977b34eba637d8b1e11aa63))
+- react-query hey-api integration ([#101](https://github.com/stacklok/codegate-ui/issues/101)) ([58d4d47](https://github.com/stacklok/codegate-ui/commit/58d4d477677f5047c3b7efa7f744a5e88c57dd8f))
## [0.4.1](https://github.com/stacklok/codegate-ui/compare/v0.4.0...v0.4.1) (2025-01-17)
-
### Bug Fixes
-* fix data fetching bug ([#99](https://github.com/stacklok/codegate-ui/issues/99)) ([d75c445](https://github.com/stacklok/codegate-ui/commit/d75c445332827d8b1141f03d4c5a42627b6c71c7))
+- fix data fetching bug ([#99](https://github.com/stacklok/codegate-ui/issues/99)) ([d75c445](https://github.com/stacklok/codegate-ui/commit/d75c445332827d8b1141f03d4c5a42627b6c71c7))
## [0.4.0](https://github.com/stacklok/codegate-ui/compare/v0.3.0...v0.4.0) (2025-01-16)
-
### Features
-* add dark mode switcher ([#89](https://github.com/stacklok/codegate-ui/issues/89)) ([603b397](https://github.com/stacklok/codegate-ui/commit/603b397d25071ad6852f18c0cc5deb21f6fdba7c)), closes [#30](https://github.com/stacklok/codegate-ui/issues/30)
+- add dark mode switcher ([#89](https://github.com/stacklok/codegate-ui/issues/89)) ([603b397](https://github.com/stacklok/codegate-ui/commit/603b397d25071ad6852f18c0cc5deb21f6fdba7c)), closes [#30](https://github.com/stacklok/codegate-ui/issues/30)
## [0.3.0](https://github.com/stacklok/codegate-ui/compare/v0.2.0...v0.3.0) (2025-01-16)
-
### Features
-* use @stacklok/ui-kit ([#80](https://github.com/stacklok/codegate-ui/issues/80)) ([48e7103](https://github.com/stacklok/codegate-ui/commit/48e7103bb9faa32d50c24b59b937b40b2c1ab27e))
-
+- use @stacklok/ui-kit ([#80](https://github.com/stacklok/codegate-ui/issues/80)) ([48e7103](https://github.com/stacklok/codegate-ui/commit/48e7103bb9faa32d50c24b59b937b40b2c1ab27e))
### Bug Fixes
-* **alerts-table:** add clear button to input search ([#87](https://github.com/stacklok/codegate-ui/issues/87)) ([ee1ac50](https://github.com/stacklok/codegate-ui/commit/ee1ac50a5066daa8f149c590d0144f93e2c3dad8))
-* **alerts-table:** trigger token box and copy to clipboard icon ([#85](https://github.com/stacklok/codegate-ui/issues/85)) ([7a0e61c](https://github.com/stacklok/codegate-ui/commit/7a0e61c62bdd0c466b529f4c1b6eb38e8fdbafef))
+- **alerts-table:** add clear button to input search ([#87](https://github.com/stacklok/codegate-ui/issues/87)) ([ee1ac50](https://github.com/stacklok/codegate-ui/commit/ee1ac50a5066daa8f149c590d0144f93e2c3dad8))
+- **alerts-table:** trigger token box and copy to clipboard icon ([#85](https://github.com/stacklok/codegate-ui/issues/85)) ([7a0e61c](https://github.com/stacklok/codegate-ui/commit/7a0e61c62bdd0c466b529f4c1b6eb38e8fdbafef))
## [0.2.0](https://github.com/stacklok/codegate-ui/compare/v0.1.0...v0.2.0) (2025-01-15)
-
### Features
-* make "setup" menu section less confusing ([#81](https://github.com/stacklok/codegate-ui/issues/81)) ([ae42046](https://github.com/stacklok/codegate-ui/commit/ae42046f76a08a3f7cb2ad2ba3e348b82764c7d5))
-
+- make "setup" menu section less confusing ([#81](https://github.com/stacklok/codegate-ui/issues/81)) ([ae42046](https://github.com/stacklok/codegate-ui/commit/ae42046f76a08a3f7cb2ad2ba3e348b82764c7d5))
### Bug Fixes
-* **alerts:** table sorting ([#82](https://github.com/stacklok/codegate-ui/issues/82)) ([8a98ec1](https://github.com/stacklok/codegate-ui/commit/8a98ec187dba98be0d4b300e4057ee8c6cba9183))
-* fix basic markdown usage ([#75](https://github.com/stacklok/codegate-ui/issues/75)) ([d787eec](https://github.com/stacklok/codegate-ui/commit/d787eec17f83ff8422993ba536a1817c4a0e55f1))
+- **alerts:** table sorting ([#82](https://github.com/stacklok/codegate-ui/issues/82)) ([8a98ec1](https://github.com/stacklok/codegate-ui/commit/8a98ec187dba98be0d4b300e4057ee8c6cba9183))
+- fix basic markdown usage ([#75](https://github.com/stacklok/codegate-ui/issues/75)) ([d787eec](https://github.com/stacklok/codegate-ui/commit/d787eec17f83ff8422993ba536a1817c4a0e55f1))
## [0.1.0](https://github.com/stacklok/codegate-ui/compare/v0.0.15...v0.1.0) (2025-01-15)
-
### Features
-* add ErrorBoundary ([#69](https://github.com/stacklok/codegate-ui/issues/69)) ([78bdfb9](https://github.com/stacklok/codegate-ui/commit/78bdfb9ef98ee9426c35c254fd4f670954c162b1))
-* health check card ([#62](https://github.com/stacklok/codegate-ui/issues/62)) ([f2673bc](https://github.com/stacklok/codegate-ui/commit/f2673bcf71d2ce1850ac9ad8e861bf448ac84a91))
+- add ErrorBoundary ([#69](https://github.com/stacklok/codegate-ui/issues/69)) ([78bdfb9](https://github.com/stacklok/codegate-ui/commit/78bdfb9ef98ee9426c35c254fd4f670954c162b1))
+- health check card ([#62](https://github.com/stacklok/codegate-ui/issues/62)) ([f2673bc](https://github.com/stacklok/codegate-ui/commit/f2673bcf71d2ce1850ac9ad8e861bf448ac84a91))
## [0.0.15](https://github.com/stacklok/codegate-ui/compare/v0.0.14...v0.0.15) (2025-01-15)
-
### Bug Fixes
-* alerts line chart sorting ([#63](https://github.com/stacklok/codegate-ui/issues/63)) ([ce8146c](https://github.com/stacklok/codegate-ui/commit/ce8146c644b9a44884aba2a5b4fc0adba08a4518))
-* formatting alert time in the table, add seconds ([#56](https://github.com/stacklok/codegate-ui/issues/56)) ([8d764aa](https://github.com/stacklok/codegate-ui/commit/8d764aac08329fceb2e3bc53b0ba01ef785ecf7e))
+- alerts line chart sorting ([#63](https://github.com/stacklok/codegate-ui/issues/63)) ([ce8146c](https://github.com/stacklok/codegate-ui/commit/ce8146c644b9a44884aba2a5b4fc0adba08a4518))
+- formatting alert time in the table, add seconds ([#56](https://github.com/stacklok/codegate-ui/issues/56)) ([8d764aa](https://github.com/stacklok/codegate-ui/commit/8d764aac08329fceb2e3bc53b0ba01ef785ecf7e))
diff --git a/README.md b/README.md
index 6c88c90b..2e4c9b12 100644
--- a/README.md
+++ b/README.md
@@ -61,5 +61,3 @@ Then run:
```bash
npm run generate-icons
```
-
-
diff --git a/components.json b/components.json
index d627b2e1..782a943e 100644
--- a/components.json
+++ b/components.json
@@ -18,4 +18,4 @@
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
-}
\ No newline at end of file
+}
diff --git a/eslint.config.js b/eslint.config.js
index 0ac4e0d8..a031d0a6 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -1,14 +1,14 @@
-import js from "@eslint/js";
-import globals from "globals";
-import importPlugin from "eslint-plugin-import";
-import reactHooks from "eslint-plugin-react-hooks";
-import reactRefresh from "eslint-plugin-react-refresh";
-import tseslint from "typescript-eslint";
-import tailwindPlugin from "eslint-plugin-tailwindcss";
-import path from "path";
-import fs from "fs";
+import js from '@eslint/js'
+import globals from 'globals'
+import importPlugin from 'eslint-plugin-import'
+import reactHooks from 'eslint-plugin-react-hooks'
+import reactRefresh from 'eslint-plugin-react-refresh'
+import tseslint from 'typescript-eslint'
+import tailwindPlugin from 'eslint-plugin-tailwindcss'
+import path from 'path'
+import fs from 'fs'
-const FEATURES_DIR = "./src/features";
+const FEATURES_DIR = './src/features'
/**
* Traverse the features directory and return an array of restricted paths for
@@ -36,31 +36,31 @@ const FEATURES_DIR = "./src/features";
* ```
*/
const getRestrictedPathsForFeatureDir = () => {
- const featureDirPath = path.resolve(FEATURES_DIR);
+ const featureDirPath = path.resolve(FEATURES_DIR)
/**
* @type {Array<{except: `./${string}`[], from: './src/features', target: string}>}
*/
- const restrictedPaths = [];
+ const restrictedPaths = []
try {
- const featureDirs = fs.readdirSync(featureDirPath);
+ const featureDirs = fs.readdirSync(featureDirPath)
featureDirs.forEach((featureDir) => {
- const subPath = path.join(featureDirPath, featureDir);
+ const subPath = path.join(featureDirPath, featureDir)
if (fs.lstatSync(subPath).isDirectory()) {
restrictedPaths.push({
except: [`./${featureDir}`],
from: FEATURES_DIR,
target: path.join(FEATURES_DIR, featureDir),
- });
+ })
}
- });
+ })
} catch (error) {
- console.error("Error reading features directory:", error);
+ console.error('Error reading features directory:', error)
}
- return restrictedPaths;
-};
+ return restrictedPaths
+}
const restrictedSyntax = {
reactQuery: {
@@ -69,17 +69,17 @@ const restrictedSyntax = {
useQueries: (v) =>
`CallExpression[callee.name='useQueries'] > ObjectExpression:first-child > Property[key.name='queries'] > ArrayExpression > ObjectExpression > Property[key.name='${v}']`,
},
-};
+}
export default tseslint.config(
- { ignores: ["dist"] },
+ { ignores: ['dist'] },
{
extends: [
js.configs.recommended,
...tseslint.configs.recommended,
- ...tailwindPlugin.configs["flat/recommended"],
+ ...tailwindPlugin.configs['flat/recommended'],
],
- files: ["**/*.{ts,tsx}"],
+ files: ['**/*.{ts,tsx}'],
languageOptions: {
parserOptions: {
projectService: true,
@@ -94,87 +94,87 @@ export default tseslint.config(
},
},
plugins: {
- "react-hooks": reactHooks,
- "react-refresh": reactRefresh,
+ 'react-hooks': reactHooks,
+ 'react-refresh': reactRefresh,
import: importPlugin,
},
settings: {
- "import/resolver": {
+ 'import/resolver': {
typescript: true,
node: true,
},
tailwindcss: {
- callees: ["tv", "twMerge"],
- config: "./tailwind.config.ts",
+ callees: ['tv', 'twMerge'],
+ config: './tailwind.config.ts',
},
},
rules: {
...reactHooks.configs.recommended.rules,
- "react-refresh/only-export-components": [
- "warn",
+ 'react-refresh/only-export-components': [
+ 'warn',
{ allowConstantExport: true },
],
- "tailwindcss/enforces-negative-arbitrary-values": "error",
- "tailwindcss/enforces-shorthand": "error",
- "tailwindcss/classnames-order": "off", // handled by prettier
- "tailwindcss/no-contradicting-classname": "error",
- "tailwindcss/no-custom-classname": [
- "error",
+ 'tailwindcss/enforces-negative-arbitrary-values': 'error',
+ 'tailwindcss/enforces-shorthand': 'error',
+ 'tailwindcss/classnames-order': 'off', // handled by prettier
+ 'tailwindcss/no-contradicting-classname': 'error',
+ 'tailwindcss/no-custom-classname': [
+ 'error',
{
- callees: ["tv", "twMerge"],
+ callees: ['tv', 'twMerge'],
whitelist: [
- "theme\\-(minder|trusty)",
- "light",
- "dark",
- "scrollbar-thin",
- "font-default",
- "font-title",
- "font-code",
- "subhead-bold",
- "subhead-regular",
+ 'theme\\-(minder|trusty)',
+ 'light',
+ 'dark',
+ 'scrollbar-thin',
+ 'font-default',
+ 'font-title',
+ 'font-code',
+ 'subhead-bold',
+ 'subhead-regular',
],
},
],
- "no-restricted-syntax": [
- "error",
+ 'no-restricted-syntax': [
+ 'error',
{
selector: [
- restrictedSyntax.reactQuery.useQuery("staleTime"),
- restrictedSyntax.reactQuery.useQuery("gcTime"),
- restrictedSyntax.reactQuery.useQueries("staleTime"),
- restrictedSyntax.reactQuery.useQueries("gcTime"),
- ].join(", "),
+ restrictedSyntax.reactQuery.useQuery('staleTime'),
+ restrictedSyntax.reactQuery.useQuery('gcTime'),
+ restrictedSyntax.reactQuery.useQueries('staleTime'),
+ restrictedSyntax.reactQuery.useQueries('gcTime'),
+ ].join(', '),
message:
- "`staleTime` & `gcTime` should be managed via the `getQueryCacheConfig` util instead.",
+ '`staleTime` & `gcTime` should be managed via the `getQueryCacheConfig` util instead.',
},
{
selector: [
- restrictedSyntax.reactQuery.useQuery("queryKey"),
- restrictedSyntax.reactQuery.useQuery("queryFn"),
- restrictedSyntax.reactQuery.useQueries("queryKey"),
- restrictedSyntax.reactQuery.useQueries("queryFn"),
- ].join(", "),
+ restrictedSyntax.reactQuery.useQuery('queryKey'),
+ restrictedSyntax.reactQuery.useQuery('queryFn'),
+ restrictedSyntax.reactQuery.useQueries('queryKey'),
+ restrictedSyntax.reactQuery.useQueries('queryFn'),
+ ].join(', '),
message:
"'queryKey' & 'queryFn' should be managed by openapi-ts react-query integration instead. This allows standardized management of query keys & cache invalidation.",
},
{
selector: [
- restrictedSyntax.reactQuery.useQuery("refetchOnMount"),
- restrictedSyntax.reactQuery.useQuery("refetchOnReconnect"),
- restrictedSyntax.reactQuery.useQuery("refetchOnWindowFocus"),
- restrictedSyntax.reactQuery.useQueries("refetchOnMount"),
- restrictedSyntax.reactQuery.useQueries("refetchOnReconnect"),
- restrictedSyntax.reactQuery.useQueries("refetchOnWindowFocus"),
- ].join(", "),
+ restrictedSyntax.reactQuery.useQuery('refetchOnMount'),
+ restrictedSyntax.reactQuery.useQuery('refetchOnReconnect'),
+ restrictedSyntax.reactQuery.useQuery('refetchOnWindowFocus'),
+ restrictedSyntax.reactQuery.useQueries('refetchOnMount'),
+ restrictedSyntax.reactQuery.useQueries('refetchOnReconnect'),
+ restrictedSyntax.reactQuery.useQueries('refetchOnWindowFocus'),
+ ].join(', '),
message:
- "`refetchOnMount`, `refetchOnReconnect` & `refetchOnWindowFocus` should be managed centrally in the react-query provider",
+ '`refetchOnMount`, `refetchOnReconnect` & `refetchOnWindowFocus` should be managed centrally in the react-query provider',
},
{
selector:
"CallExpression > MemberExpression[property.name='invalidateQueries']",
message:
- "Do not directly call `invalidateQueries`. Instead, use the `invalidateQueries` helper function.",
+ 'Do not directly call `invalidateQueries`. Instead, use the `invalidateQueries` helper function.',
},
{
selector: [
@@ -186,25 +186,25 @@ export default tseslint.config(
"CallExpression[callee.object.name='http'][callee.property.name='delete'] > Literal:first-child",
"CallExpression[callee.object.name='http'][callee.property.name='patch'] > Literal:first-child",
"CallExpression[callee.object.name='http'][callee.property.name='options'] > Literal:first-child",
- ].join(", "),
+ ].join(', '),
message:
"Do not pass a string as the first argument to methods on Mock Service Worker's `http`. Use the `mswEndpoint` helper function instead, which provides type-safe routes based on the OpenAPI spec and the API base URL.",
},
],
- "no-restricted-imports": [
- "error",
+ 'no-restricted-imports': [
+ 'error',
{
paths: [
{
- importNames: ["useMutation"],
- message: "Use the custom `useToastMutation` instead",
- name: "@tanstack/react-query",
+ importNames: ['useMutation'],
+ message: 'Use the custom `useToastMutation` instead',
+ name: '@tanstack/react-query',
},
],
},
],
- "import/no-restricted-paths": [
- "error",
+ 'import/no-restricted-paths': [
+ 'error',
{
zones: [
// disables cross-feature imports:
@@ -214,29 +214,29 @@ export default tseslint.config(
// enforce unidirectional codebase:
// e.g. src/routes can import from src/features but not the other way around
{
- from: "./src/routes",
- target: "./src/features",
+ from: './src/routes',
+ target: './src/features',
},
// enforce unidirectional codebase:
// e.g src/features and src/routes can import from these shared modules but not the other way around
{
- from: ["./src/features", "./src/routes"],
+ from: ['./src/features', './src/routes'],
target: [
- "./src/components",
- "./src/constants",
- "./src/hooks",
- "./src/i18n",
- "./src/lib",
- "./src/mocks",
- "./src/trusty-api",
- "./src/types",
- "./src/utils",
+ './src/components',
+ './src/constants',
+ './src/hooks',
+ './src/i18n',
+ './src/lib',
+ './src/mocks',
+ './src/trusty-api',
+ './src/types',
+ './src/utils',
],
},
],
},
],
},
- },
-);
+ }
+)
diff --git a/lint-staged.config.mjs b/lint-staged.config.mjs
index f0420ddd..65929b81 100644
--- a/lint-staged.config.mjs
+++ b/lint-staged.config.mjs
@@ -2,9 +2,9 @@
* @type {import("lint-staged").Config}
*/
export default {
- "**/*.{js,jsx,ts,tsx,mjs,cjs}": [
- "npx prettier --write",
- "npx eslint --fix",
- "bash -c tsc -p ./tsconfig.app.json --noEmit",
+ '**/*.{js,jsx,ts,tsx,mjs,cjs}': [
+ 'npx prettier --write',
+ 'npx eslint --fix',
+ 'bash -c tsc -p ./tsconfig.app.json --noEmit',
],
-};
+}
diff --git a/openapi-ts.config.ts b/openapi-ts.config.ts
index af1af339..d5f6ee19 100644
--- a/openapi-ts.config.ts
+++ b/openapi-ts.config.ts
@@ -1,19 +1,19 @@
-import { defineConfig } from "@hey-api/openapi-ts";
+import { defineConfig } from '@hey-api/openapi-ts'
export default defineConfig({
- client: "@hey-api/client-fetch",
- input: "src/api/openapi.json",
+ client: '@hey-api/client-fetch',
+ input: 'src/api/openapi.json',
output: {
- format: "prettier",
- path: "./src/api/generated",
- lint: "eslint",
+ format: 'prettier',
+ path: './src/api/generated',
+ lint: 'eslint',
},
plugins: [
- "@hey-api/sdk",
- "@tanstack/react-query",
+ '@hey-api/sdk',
+ '@tanstack/react-query',
{
- enums: "typescript",
- name: "@hey-api/typescript",
+ enums: 'typescript',
+ name: '@hey-api/typescript',
},
],
-});
+})
diff --git a/package-lock.json b/package-lock.json
index 8ffe8673..6619df64 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -73,6 +73,9 @@
"msw": "^2.7.0",
"postcss": "^8.4.49",
"prettier": "3.4.2",
+ "prettier-plugin-classnames": "^0.7.6",
+ "prettier-plugin-merge": "^0.7.2",
+ "prettier-plugin-tailwindcss": "^0.6.11",
"tailwindcss": "^3.4.15",
"typescript": "~5.7.2",
"typescript-eslint": "^8.15.0",
@@ -6507,6 +6510,16 @@
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
"license": "Apache-2.0"
},
+ "node_modules/diff": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz",
+ "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.3.1"
+ }
+ },
"node_modules/dlv": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
@@ -11337,6 +11350,124 @@
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
+ "node_modules/prettier-plugin-classnames": {
+ "version": "0.7.6",
+ "resolved": "https://registry.npmjs.org/prettier-plugin-classnames/-/prettier-plugin-classnames-0.7.6.tgz",
+ "integrity": "sha512-sBBnS0VK/gXwtSbsgK5bB5kdtGRWn+hZVBoV/GqLKK6vJGlECmDck/ilqEUCmk7KTGZpRsxIFMW6DXn6OJHAng==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "peerDependencies": {
+ "prettier": "^2 || ^3",
+ "prettier-plugin-astro": "*",
+ "prettier-plugin-svelte": "*"
+ },
+ "peerDependenciesMeta": {
+ "prettier-plugin-astro": {
+ "optional": true
+ },
+ "prettier-plugin-svelte": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/prettier-plugin-merge": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/prettier-plugin-merge/-/prettier-plugin-merge-0.7.2.tgz",
+ "integrity": "sha512-o/twjBSVjKttfBBz7KWdJxyPjEAoSh1WwkEv02XKWoyifPXVPSifgB597nntEzTo5hN63XnoZQkAdDVnt6WRLA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "diff": "5.1.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "peerDependencies": {
+ "prettier": "^2 || ^3"
+ }
+ },
+ "node_modules/prettier-plugin-tailwindcss": {
+ "version": "0.6.11",
+ "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.11.tgz",
+ "integrity": "sha512-YxaYSIvZPAqhrrEpRtonnrXdghZg1irNg4qrjboCXrpybLWVs55cW2N3juhspVJiO0JBvYJT8SYsJpc8OQSnsA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.21.3"
+ },
+ "peerDependencies": {
+ "@ianvs/prettier-plugin-sort-imports": "*",
+ "@prettier/plugin-pug": "*",
+ "@shopify/prettier-plugin-liquid": "*",
+ "@trivago/prettier-plugin-sort-imports": "*",
+ "@zackad/prettier-plugin-twig": "*",
+ "prettier": "^3.0",
+ "prettier-plugin-astro": "*",
+ "prettier-plugin-css-order": "*",
+ "prettier-plugin-import-sort": "*",
+ "prettier-plugin-jsdoc": "*",
+ "prettier-plugin-marko": "*",
+ "prettier-plugin-multiline-arrays": "*",
+ "prettier-plugin-organize-attributes": "*",
+ "prettier-plugin-organize-imports": "*",
+ "prettier-plugin-sort-imports": "*",
+ "prettier-plugin-style-order": "*",
+ "prettier-plugin-svelte": "*"
+ },
+ "peerDependenciesMeta": {
+ "@ianvs/prettier-plugin-sort-imports": {
+ "optional": true
+ },
+ "@prettier/plugin-pug": {
+ "optional": true
+ },
+ "@shopify/prettier-plugin-liquid": {
+ "optional": true
+ },
+ "@trivago/prettier-plugin-sort-imports": {
+ "optional": true
+ },
+ "@zackad/prettier-plugin-twig": {
+ "optional": true
+ },
+ "prettier-plugin-astro": {
+ "optional": true
+ },
+ "prettier-plugin-css-order": {
+ "optional": true
+ },
+ "prettier-plugin-import-sort": {
+ "optional": true
+ },
+ "prettier-plugin-jsdoc": {
+ "optional": true
+ },
+ "prettier-plugin-marko": {
+ "optional": true
+ },
+ "prettier-plugin-multiline-arrays": {
+ "optional": true
+ },
+ "prettier-plugin-organize-attributes": {
+ "optional": true
+ },
+ "prettier-plugin-organize-imports": {
+ "optional": true
+ },
+ "prettier-plugin-sort-imports": {
+ "optional": true
+ },
+ "prettier-plugin-style-order": {
+ "optional": true
+ },
+ "prettier-plugin-svelte": {
+ "optional": true
+ }
+ }
+ },
"node_modules/pretty-format": {
"version": "27.5.1",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
diff --git a/package.json b/package.json
index 6757ca47..9fb2c2cc 100644
--- a/package.json
+++ b/package.json
@@ -85,6 +85,9 @@
"msw": "^2.7.0",
"postcss": "^8.4.49",
"prettier": "3.4.2",
+ "prettier-plugin-classnames": "^0.7.6",
+ "prettier-plugin-merge": "^0.7.2",
+ "prettier-plugin-tailwindcss": "^0.6.11",
"tailwindcss": "^3.4.15",
"typescript": "~5.7.2",
"typescript-eslint": "^8.15.0",
diff --git a/src/App.test.tsx b/src/App.test.tsx
index 1cf341d5..1e5d55c0 100644
--- a/src/App.test.tsx
+++ b/src/App.test.tsx
@@ -1,109 +1,109 @@
-import { render } from "@/lib/test-utils";
-import { screen, waitFor } from "@testing-library/react";
-import { describe, expect, it } from "vitest";
-import App from "./App";
-import userEvent from "@testing-library/user-event";
-
-describe("App", () => {
- it("should render header", async () => {
- render( );
- expect(screen.getByText("Settings")).toBeVisible();
- expect(screen.getByText("Help")).toBeVisible();
- expect(screen.getByRole("banner", { name: "App header" })).toBeVisible();
- expect(screen.getByRole("heading", { name: /codeGate/i })).toBeVisible();
-
- await userEvent.click(screen.getByText("Settings"));
+import { render } from '@/lib/test-utils'
+import { screen, waitFor } from '@testing-library/react'
+import { describe, expect, it } from 'vitest'
+import App from './App'
+import userEvent from '@testing-library/user-event'
+
+describe('App', () => {
+ it('should render header', async () => {
+ render( )
+ expect(screen.getByText('Settings')).toBeVisible()
+ expect(screen.getByText('Help')).toBeVisible()
+ expect(screen.getByRole('banner', { name: 'App header' })).toBeVisible()
+ expect(screen.getByRole('heading', { name: /codeGate/i })).toBeVisible()
+
+ await userEvent.click(screen.getByText('Settings'))
expect(
- screen.getByRole("menuitem", {
+ screen.getByRole('menuitem', {
name: /providers/i,
- }),
- ).toBeVisible();
+ })
+ ).toBeVisible()
expect(
- screen.getByRole("menuitem", {
+ screen.getByRole('menuitem', {
name: /certificate security/i,
- }),
- ).toBeVisible();
+ })
+ ).toBeVisible()
expect(
- screen.getByRole("menuitem", {
+ screen.getByRole('menuitem', {
name: /download/i,
- }),
- ).toBeVisible();
+ })
+ ).toBeVisible()
- await userEvent.click(screen.getByText("Settings"));
- await userEvent.click(screen.getByText("Help"));
+ await userEvent.click(screen.getByText('Settings'))
+ await userEvent.click(screen.getByText('Help'))
expect(
- screen.getByRole("menuitem", {
+ screen.getByRole('menuitem', {
name: /use with continue/i,
- }),
- ).toBeVisible();
+ })
+ ).toBeVisible()
expect(
- screen.getByRole("menuitem", {
+ screen.getByRole('menuitem', {
name: /use with copilot/i,
- }),
- ).toBeVisible();
+ })
+ ).toBeVisible()
expect(
- screen.getByRole("menuitem", {
+ screen.getByRole('menuitem', {
name: /documentation/i,
- }),
- ).toBeVisible();
+ })
+ ).toBeVisible()
- const discordMenuItem = screen.getByRole("menuitem", {
+ const discordMenuItem = screen.getByRole('menuitem', {
name: /discord/i,
- });
- expect(discordMenuItem).toBeVisible();
+ })
+ expect(discordMenuItem).toBeVisible()
expect(discordMenuItem).toHaveAttribute(
- "href",
- "https://discord.gg/stacklok",
- );
+ 'href',
+ 'https://discord.gg/stacklok'
+ )
- const githubMenuItem = screen.getByRole("menuitem", {
+ const githubMenuItem = screen.getByRole('menuitem', {
name: /github/i,
- });
- expect(githubMenuItem).toBeVisible();
+ })
+ expect(githubMenuItem).toBeVisible()
expect(githubMenuItem).toHaveAttribute(
- "href",
- "https://github.com/stacklok/codegate",
- );
+ 'href',
+ 'https://github.com/stacklok/codegate'
+ )
- const youtubeMenuItem = screen.getByRole("menuitem", {
+ const youtubeMenuItem = screen.getByRole('menuitem', {
name: /youtube/i,
- });
- expect(youtubeMenuItem).toBeVisible();
+ })
+ expect(youtubeMenuItem).toBeVisible()
expect(youtubeMenuItem).toHaveAttribute(
- "href",
- "https://www.youtube.com/@Stacklok",
- );
+ 'href',
+ 'https://www.youtube.com/@Stacklok'
+ )
- await userEvent.click(screen.getByText("Help"));
+ await userEvent.click(screen.getByText('Help'))
await waitFor(() =>
- expect(screen.getByRole("link", { name: /codeGate/i })).toBeVisible(),
- );
- });
+ expect(screen.getByRole('link', { name: /codeGate/i })).toBeVisible()
+ )
+ })
- it("should render workspaces dropdown", async () => {
- render( );
+ it('should render workspaces dropdown', async () => {
+ render( )
await waitFor(() =>
- expect(screen.getByRole("link", { name: "CodeGate" })).toBeVisible(),
- );
+ expect(screen.getByRole('link', { name: 'CodeGate' })).toBeVisible()
+ )
- const workspaceSelectionButton = screen.getByRole("button", {
- name: "Active workspace default",
- });
- await waitFor(() => expect(workspaceSelectionButton).toBeVisible());
+ const workspaceSelectionButton = screen.getByRole('button', {
+ name: 'Active workspace default',
+ })
+ await waitFor(() => expect(workspaceSelectionButton).toBeVisible())
- await userEvent.click(workspaceSelectionButton);
+ await userEvent.click(workspaceSelectionButton)
await waitFor(() =>
expect(
- screen.getByRole("option", {
+ screen.getByRole('option', {
name: /anotherworkspae/i,
- }),
- ).toBeVisible(),
- );
- });
-});
+ })
+ ).toBeVisible()
+ )
+ })
+})
diff --git a/src/App.tsx b/src/App.tsx
index 2badc373..e8404492 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,9 +1,9 @@
-import { Header } from "./features/header/components/header";
-import { useSse } from "./hooks/useSse";
-import Page from "./Page";
+import { Header } from './features/header/components/header'
+import { useSse } from './hooks/useSse'
+import Page from './Page'
export default function App() {
- useSse();
+ useSse()
return (
<>
@@ -12,5 +12,5 @@ export default function App() {
>
- );
+ )
}
diff --git a/src/Page.test.tsx b/src/Page.test.tsx
index b01d95cb..9e95bd13 100644
--- a/src/Page.test.tsx
+++ b/src/Page.test.tsx
@@ -1,13 +1,13 @@
-import { render } from "./lib/test-utils";
-import Page from "./Page";
+import { render } from './lib/test-utils'
+import Page from './Page'
-test("render NotFound route", () => {
+test('render NotFound route', () => {
const { getByText, getByRole } = render( , {
routeConfig: {
- initialEntries: ["/fake-route"],
+ initialEntries: ['/fake-route'],
},
- });
+ })
- expect(getByText(/Oops! There's nothing here/i)).toBeVisible();
- expect(getByRole("button", { name: "Home" })).toBeVisible();
-});
+ expect(getByText(/Oops! There's nothing here/i)).toBeVisible()
+ expect(getByRole('button', { name: 'Home' })).toBeVisible()
+})
diff --git a/src/Page.tsx b/src/Page.tsx
index 65d5c1ce..e2d4c30d 100644
--- a/src/Page.tsx
+++ b/src/Page.tsx
@@ -1,16 +1,16 @@
-import { Route, Routes } from "react-router-dom";
+import { Route, Routes } from 'react-router-dom'
-import { RouteWorkspace } from "./routes/route-workspace";
-import { RouteWorkspaces } from "./routes/route-workspaces";
-import { RouteCertificates } from "./routes/route-certificates";
-import { RouteChat } from "./routes/route-chat";
-import { RouteDashboard } from "./routes/route-dashboard";
-import { RouteCertificateSecurity } from "./routes/route-certificate-security";
-import { RouteWorkspaceCreation } from "./routes/route-workspace-creation";
-import { RouteNotFound } from "./routes/route-not-found";
-import { RouteProvider } from "./routes/route-providers";
-import { RouteProviderCreate } from "./routes/route-provider-create";
-import { RouteProviderUpdate } from "./routes/route-provider-update";
+import { RouteWorkspace } from './routes/route-workspace'
+import { RouteWorkspaces } from './routes/route-workspaces'
+import { RouteCertificates } from './routes/route-certificates'
+import { RouteChat } from './routes/route-chat'
+import { RouteDashboard } from './routes/route-dashboard'
+import { RouteCertificateSecurity } from './routes/route-certificate-security'
+import { RouteWorkspaceCreation } from './routes/route-workspace-creation'
+import { RouteNotFound } from './routes/route-not-found'
+import { RouteProvider } from './routes/route-providers'
+import { RouteProviderCreate } from './routes/route-provider-create'
+import { RouteProviderUpdate } from './routes/route-provider-update'
export default function Page() {
return (
@@ -36,5 +36,5 @@ export default function Page() {
} />
- );
+ )
}
diff --git a/src/api/generated/@tanstack/react-query.gen.ts b/src/api/generated/@tanstack/react-query.gen.ts
index 28e8ffbb..102ed855 100644
--- a/src/api/generated/@tanstack/react-query.gen.ts
+++ b/src/api/generated/@tanstack/react-query.gen.ts
@@ -1,7 +1,7 @@
// This file is auto-generated by @hey-api/openapi-ts
-import type { OptionsLegacyParser } from "@hey-api/client-fetch";
-import { queryOptions, type UseMutationOptions } from "@tanstack/react-query";
+import type { OptionsLegacyParser } from '@hey-api/client-fetch'
+import { queryOptions, type UseMutationOptions } from '@tanstack/react-query'
import {
client,
healthCheckHealthGet,
@@ -31,7 +31,7 @@ import {
v1StreamSse,
v1VersionCheck,
v1GetWorkspaceTokenUsage,
-} from "../sdk.gen";
+} from '../sdk.gen'
import type {
V1ListProviderEndpointsData,
V1AddProviderEndpointData,
@@ -77,45 +77,45 @@ import type {
V1SetWorkspaceMuxesError,
V1SetWorkspaceMuxesResponse,
V1GetWorkspaceTokenUsageData,
-} from "../types.gen";
+} from '../types.gen'
type QueryKey = [
- Pick & {
- _id: string;
- _infinite?: boolean;
+ Pick & {
+ _id: string
+ _infinite?: boolean
},
-];
+]
const createQueryKey = (
id: string,
options?: TOptions,
- infinite?: boolean,
+ infinite?: boolean
): QueryKey[0] => {
const params: QueryKey[0] = {
_id: id,
baseUrl: (options?.client ?? client).getConfig().baseUrl,
- } as QueryKey[0];
+ } as QueryKey[0]
if (infinite) {
- params._infinite = infinite;
+ params._infinite = infinite
}
if (options?.body) {
- params.body = options.body;
+ params.body = options.body
}
if (options?.headers) {
- params.headers = options.headers;
+ params.headers = options.headers
}
if (options?.path) {
- params.path = options.path;
+ params.path = options.path
}
if (options?.query) {
- params.query = options.query;
+ params.query = options.query
}
- return params;
-};
+ return params
+}
export const healthCheckHealthGetQueryKey = (options?: OptionsLegacyParser) => [
- createQueryKey("healthCheckHealthGet", options),
-];
+ createQueryKey('healthCheckHealthGet', options),
+]
export const healthCheckHealthGetOptions = (options?: OptionsLegacyParser) => {
return queryOptions({
@@ -125,19 +125,19 @@ export const healthCheckHealthGetOptions = (options?: OptionsLegacyParser) => {
...queryKey[0],
signal,
throwOnError: true,
- });
- return data;
+ })
+ return data
},
queryKey: healthCheckHealthGetQueryKey(options),
- });
-};
+ })
+}
export const v1ListProviderEndpointsQueryKey = (
- options?: OptionsLegacyParser,
-) => [createQueryKey("v1ListProviderEndpoints", options)];
+ options?: OptionsLegacyParser
+) => [createQueryKey('v1ListProviderEndpoints', options)]
export const v1ListProviderEndpointsOptions = (
- options?: OptionsLegacyParser,
+ options?: OptionsLegacyParser
) => {
return queryOptions({
queryFn: async ({ queryKey, signal }) => {
@@ -146,19 +146,19 @@ export const v1ListProviderEndpointsOptions = (
...queryKey[0],
signal,
throwOnError: true,
- });
- return data;
+ })
+ return data
},
queryKey: v1ListProviderEndpointsQueryKey(options),
- });
-};
+ })
+}
export const v1AddProviderEndpointQueryKey = (
- options: OptionsLegacyParser,
-) => [createQueryKey("v1AddProviderEndpoint", options)];
+ options: OptionsLegacyParser
+) => [createQueryKey('v1AddProviderEndpoint', options)]
export const v1AddProviderEndpointOptions = (
- options: OptionsLegacyParser,
+ options: OptionsLegacyParser
) => {
return queryOptions({
queryFn: async ({ queryKey, signal }) => {
@@ -167,15 +167,15 @@ export const v1AddProviderEndpointOptions = (
...queryKey[0],
signal,
throwOnError: true,
- });
- return data;
+ })
+ return data
},
queryKey: v1AddProviderEndpointQueryKey(options),
- });
-};
+ })
+}
export const v1AddProviderEndpointMutation = (
- options?: Partial>,
+ options?: Partial>
) => {
const mutationOptions: UseMutationOptions<
V1AddProviderEndpointResponse,
@@ -187,19 +187,19 @@ export const v1AddProviderEndpointMutation = (
...options,
...localOptions,
throwOnError: true,
- });
- return data;
+ })
+ return data
},
- };
- return mutationOptions;
-};
+ }
+ return mutationOptions
+}
export const v1ListAllModelsForAllProvidersQueryKey = (
- options?: OptionsLegacyParser,
-) => [createQueryKey("v1ListAllModelsForAllProviders", options)];
+ options?: OptionsLegacyParser
+) => [createQueryKey('v1ListAllModelsForAllProviders', options)]
export const v1ListAllModelsForAllProvidersOptions = (
- options?: OptionsLegacyParser,
+ options?: OptionsLegacyParser
) => {
return queryOptions({
queryFn: async ({ queryKey, signal }) => {
@@ -208,19 +208,19 @@ export const v1ListAllModelsForAllProvidersOptions = (
...queryKey[0],
signal,
throwOnError: true,
- });
- return data;
+ })
+ return data
},
queryKey: v1ListAllModelsForAllProvidersQueryKey(options),
- });
-};
+ })
+}
export const v1ListModelsByProviderQueryKey = (
- options: OptionsLegacyParser,
-) => [createQueryKey("v1ListModelsByProvider", options)];
+ options: OptionsLegacyParser
+) => [createQueryKey('v1ListModelsByProvider', options)]
export const v1ListModelsByProviderOptions = (
- options: OptionsLegacyParser,
+ options: OptionsLegacyParser
) => {
return queryOptions({
queryFn: async ({ queryKey, signal }) => {
@@ -229,19 +229,19 @@ export const v1ListModelsByProviderOptions = (
...queryKey[0],
signal,
throwOnError: true,
- });
- return data;
+ })
+ return data
},
queryKey: v1ListModelsByProviderQueryKey(options),
- });
-};
+ })
+}
export const v1GetProviderEndpointQueryKey = (
- options: OptionsLegacyParser,
-) => [createQueryKey("v1GetProviderEndpoint", options)];
+ options: OptionsLegacyParser
+) => [createQueryKey('v1GetProviderEndpoint', options)]
export const v1GetProviderEndpointOptions = (
- options: OptionsLegacyParser,
+ options: OptionsLegacyParser
) => {
return queryOptions({
queryFn: async ({ queryKey, signal }) => {
@@ -250,15 +250,15 @@ export const v1GetProviderEndpointOptions = (
...queryKey[0],
signal,
throwOnError: true,
- });
- return data;
+ })
+ return data
},
queryKey: v1GetProviderEndpointQueryKey(options),
- });
-};
+ })
+}
export const v1UpdateProviderEndpointMutation = (
- options?: Partial>,
+ options?: Partial>
) => {
const mutationOptions: UseMutationOptions<
V1UpdateProviderEndpointResponse,
@@ -270,15 +270,15 @@ export const v1UpdateProviderEndpointMutation = (
...options,
...localOptions,
throwOnError: true,
- });
- return data;
+ })
+ return data
},
- };
- return mutationOptions;
-};
+ }
+ return mutationOptions
+}
export const v1DeleteProviderEndpointMutation = (
- options?: Partial>,
+ options?: Partial>
) => {
const mutationOptions: UseMutationOptions<
V1DeleteProviderEndpointResponse,
@@ -290,15 +290,15 @@ export const v1DeleteProviderEndpointMutation = (
...options,
...localOptions,
throwOnError: true,
- });
- return data;
+ })
+ return data
},
- };
- return mutationOptions;
-};
+ }
+ return mutationOptions
+}
export const v1ConfigureAuthMaterialMutation = (
- options?: Partial>,
+ options?: Partial>
) => {
const mutationOptions: UseMutationOptions<
V1ConfigureAuthMaterialResponse,
@@ -310,16 +310,16 @@ export const v1ConfigureAuthMaterialMutation = (
...options,
...localOptions,
throwOnError: true,
- });
- return data;
+ })
+ return data
},
- };
- return mutationOptions;
-};
+ }
+ return mutationOptions
+}
export const v1ListWorkspacesQueryKey = (options?: OptionsLegacyParser) => [
- createQueryKey("v1ListWorkspaces", options),
-];
+ createQueryKey('v1ListWorkspaces', options),
+]
export const v1ListWorkspacesOptions = (options?: OptionsLegacyParser) => {
return queryOptions({
@@ -329,19 +329,19 @@ export const v1ListWorkspacesOptions = (options?: OptionsLegacyParser) => {
...queryKey[0],
signal,
throwOnError: true,
- });
- return data;
+ })
+ return data
},
queryKey: v1ListWorkspacesQueryKey(options),
- });
-};
+ })
+}
export const v1CreateWorkspaceQueryKey = (
- options: OptionsLegacyParser,
-) => [createQueryKey("v1CreateWorkspace", options)];
+ options: OptionsLegacyParser
+) => [createQueryKey('v1CreateWorkspace', options)]
export const v1CreateWorkspaceOptions = (
- options: OptionsLegacyParser,
+ options: OptionsLegacyParser
) => {
return queryOptions({
queryFn: async ({ queryKey, signal }) => {
@@ -350,15 +350,15 @@ export const v1CreateWorkspaceOptions = (
...queryKey[0],
signal,
throwOnError: true,
- });
- return data;
+ })
+ return data
},
queryKey: v1CreateWorkspaceQueryKey(options),
- });
-};
+ })
+}
export const v1CreateWorkspaceMutation = (
- options?: Partial>,
+ options?: Partial>
) => {
const mutationOptions: UseMutationOptions<
V1CreateWorkspaceResponse,
@@ -370,19 +370,19 @@ export const v1CreateWorkspaceMutation = (
...options,
...localOptions,
throwOnError: true,
- });
- return data;
+ })
+ return data
},
- };
- return mutationOptions;
-};
+ }
+ return mutationOptions
+}
export const v1ListActiveWorkspacesQueryKey = (
- options?: OptionsLegacyParser,
-) => [createQueryKey("v1ListActiveWorkspaces", options)];
+ options?: OptionsLegacyParser
+) => [createQueryKey('v1ListActiveWorkspaces', options)]
export const v1ListActiveWorkspacesOptions = (
- options?: OptionsLegacyParser,
+ options?: OptionsLegacyParser
) => {
return queryOptions({
queryFn: async ({ queryKey, signal }) => {
@@ -391,19 +391,19 @@ export const v1ListActiveWorkspacesOptions = (
...queryKey[0],
signal,
throwOnError: true,
- });
- return data;
+ })
+ return data
},
queryKey: v1ListActiveWorkspacesQueryKey(options),
- });
-};
+ })
+}
export const v1ActivateWorkspaceQueryKey = (
- options: OptionsLegacyParser,
-) => [createQueryKey("v1ActivateWorkspace", options)];
+ options: OptionsLegacyParser
+) => [createQueryKey('v1ActivateWorkspace', options)]
export const v1ActivateWorkspaceOptions = (
- options: OptionsLegacyParser,
+ options: OptionsLegacyParser
) => {
return queryOptions({
queryFn: async ({ queryKey, signal }) => {
@@ -412,15 +412,15 @@ export const v1ActivateWorkspaceOptions = (
...queryKey[0],
signal,
throwOnError: true,
- });
- return data;
+ })
+ return data
},
queryKey: v1ActivateWorkspaceQueryKey(options),
- });
-};
+ })
+}
export const v1ActivateWorkspaceMutation = (
- options?: Partial>,
+ options?: Partial>
) => {
const mutationOptions: UseMutationOptions<
V1ActivateWorkspaceResponse,
@@ -432,15 +432,15 @@ export const v1ActivateWorkspaceMutation = (
...options,
...localOptions,
throwOnError: true,
- });
- return data;
+ })
+ return data
},
- };
- return mutationOptions;
-};
+ }
+ return mutationOptions
+}
export const v1DeleteWorkspaceMutation = (
- options?: Partial>,
+ options?: Partial>
) => {
const mutationOptions: UseMutationOptions<
V1DeleteWorkspaceResponse,
@@ -452,19 +452,19 @@ export const v1DeleteWorkspaceMutation = (
...options,
...localOptions,
throwOnError: true,
- });
- return data;
+ })
+ return data
},
- };
- return mutationOptions;
-};
+ }
+ return mutationOptions
+}
export const v1ListArchivedWorkspacesQueryKey = (
- options?: OptionsLegacyParser,
-) => [createQueryKey("v1ListArchivedWorkspaces", options)];
+ options?: OptionsLegacyParser
+) => [createQueryKey('v1ListArchivedWorkspaces', options)]
export const v1ListArchivedWorkspacesOptions = (
- options?: OptionsLegacyParser,
+ options?: OptionsLegacyParser
) => {
return queryOptions({
queryFn: async ({ queryKey, signal }) => {
@@ -473,19 +473,19 @@ export const v1ListArchivedWorkspacesOptions = (
...queryKey[0],
signal,
throwOnError: true,
- });
- return data;
+ })
+ return data
},
queryKey: v1ListArchivedWorkspacesQueryKey(options),
- });
-};
+ })
+}
export const v1RecoverWorkspaceQueryKey = (
- options: OptionsLegacyParser,
-) => [createQueryKey("v1RecoverWorkspace", options)];
+ options: OptionsLegacyParser
+) => [createQueryKey('v1RecoverWorkspace', options)]
export const v1RecoverWorkspaceOptions = (
- options: OptionsLegacyParser,
+ options: OptionsLegacyParser
) => {
return queryOptions({
queryFn: async ({ queryKey, signal }) => {
@@ -494,15 +494,15 @@ export const v1RecoverWorkspaceOptions = (
...queryKey[0],
signal,
throwOnError: true,
- });
- return data;
+ })
+ return data
},
queryKey: v1RecoverWorkspaceQueryKey(options),
- });
-};
+ })
+}
export const v1RecoverWorkspaceMutation = (
- options?: Partial>,
+ options?: Partial>
) => {
const mutationOptions: UseMutationOptions<
V1RecoverWorkspaceResponse,
@@ -514,15 +514,15 @@ export const v1RecoverWorkspaceMutation = (
...options,
...localOptions,
throwOnError: true,
- });
- return data;
+ })
+ return data
},
- };
- return mutationOptions;
-};
+ }
+ return mutationOptions
+}
export const v1HardDeleteWorkspaceMutation = (
- options?: Partial>,
+ options?: Partial>
) => {
const mutationOptions: UseMutationOptions<
V1HardDeleteWorkspaceResponse,
@@ -534,19 +534,19 @@ export const v1HardDeleteWorkspaceMutation = (
...options,
...localOptions,
throwOnError: true,
- });
- return data;
+ })
+ return data
},
- };
- return mutationOptions;
-};
+ }
+ return mutationOptions
+}
export const v1GetWorkspaceAlertsQueryKey = (
- options: OptionsLegacyParser,
-) => [createQueryKey("v1GetWorkspaceAlerts", options)];
+ options: OptionsLegacyParser
+) => [createQueryKey('v1GetWorkspaceAlerts', options)]
export const v1GetWorkspaceAlertsOptions = (
- options: OptionsLegacyParser,
+ options: OptionsLegacyParser
) => {
return queryOptions({
queryFn: async ({ queryKey, signal }) => {
@@ -555,19 +555,19 @@ export const v1GetWorkspaceAlertsOptions = (
...queryKey[0],
signal,
throwOnError: true,
- });
- return data;
+ })
+ return data
},
queryKey: v1GetWorkspaceAlertsQueryKey(options),
- });
-};
+ })
+}
export const v1GetWorkspaceMessagesQueryKey = (
- options: OptionsLegacyParser,
-) => [createQueryKey("v1GetWorkspaceMessages", options)];
+ options: OptionsLegacyParser
+) => [createQueryKey('v1GetWorkspaceMessages', options)]
export const v1GetWorkspaceMessagesOptions = (
- options: OptionsLegacyParser,
+ options: OptionsLegacyParser
) => {
return queryOptions({
queryFn: async ({ queryKey, signal }) => {
@@ -576,19 +576,19 @@ export const v1GetWorkspaceMessagesOptions = (
...queryKey[0],
signal,
throwOnError: true,
- });
- return data;
+ })
+ return data
},
queryKey: v1GetWorkspaceMessagesQueryKey(options),
- });
-};
+ })
+}
export const v1GetWorkspaceCustomInstructionsQueryKey = (
- options: OptionsLegacyParser,
-) => [createQueryKey("v1GetWorkspaceCustomInstructions", options)];
+ options: OptionsLegacyParser
+) => [createQueryKey('v1GetWorkspaceCustomInstructions', options)]
export const v1GetWorkspaceCustomInstructionsOptions = (
- options: OptionsLegacyParser,
+ options: OptionsLegacyParser
) => {
return queryOptions({
queryFn: async ({ queryKey, signal }) => {
@@ -597,15 +597,15 @@ export const v1GetWorkspaceCustomInstructionsOptions = (
...queryKey[0],
signal,
throwOnError: true,
- });
- return data;
+ })
+ return data
},
queryKey: v1GetWorkspaceCustomInstructionsQueryKey(options),
- });
-};
+ })
+}
export const v1SetWorkspaceCustomInstructionsMutation = (
- options?: Partial>,
+ options?: Partial>
) => {
const mutationOptions: UseMutationOptions<
V1SetWorkspaceCustomInstructionsResponse,
@@ -617,17 +617,17 @@ export const v1SetWorkspaceCustomInstructionsMutation = (
...options,
...localOptions,
throwOnError: true,
- });
- return data;
+ })
+ return data
},
- };
- return mutationOptions;
-};
+ }
+ return mutationOptions
+}
export const v1DeleteWorkspaceCustomInstructionsMutation = (
options?: Partial<
OptionsLegacyParser
- >,
+ >
) => {
const mutationOptions: UseMutationOptions<
V1DeleteWorkspaceCustomInstructionsResponse,
@@ -639,19 +639,19 @@ export const v1DeleteWorkspaceCustomInstructionsMutation = (
...options,
...localOptions,
throwOnError: true,
- });
- return data;
+ })
+ return data
},
- };
- return mutationOptions;
-};
+ }
+ return mutationOptions
+}
export const v1GetWorkspaceMuxesQueryKey = (
- options: OptionsLegacyParser,
-) => [createQueryKey("v1GetWorkspaceMuxes", options)];
+ options: OptionsLegacyParser
+) => [createQueryKey('v1GetWorkspaceMuxes', options)]
export const v1GetWorkspaceMuxesOptions = (
- options: OptionsLegacyParser,
+ options: OptionsLegacyParser
) => {
return queryOptions({
queryFn: async ({ queryKey, signal }) => {
@@ -660,15 +660,15 @@ export const v1GetWorkspaceMuxesOptions = (
...queryKey[0],
signal,
throwOnError: true,
- });
- return data;
+ })
+ return data
},
queryKey: v1GetWorkspaceMuxesQueryKey(options),
- });
-};
+ })
+}
export const v1SetWorkspaceMuxesMutation = (
- options?: Partial>,
+ options?: Partial>
) => {
const mutationOptions: UseMutationOptions<
V1SetWorkspaceMuxesResponse,
@@ -680,16 +680,16 @@ export const v1SetWorkspaceMuxesMutation = (
...options,
...localOptions,
throwOnError: true,
- });
- return data;
+ })
+ return data
},
- };
- return mutationOptions;
-};
+ }
+ return mutationOptions
+}
export const v1StreamSseQueryKey = (options?: OptionsLegacyParser) => [
- createQueryKey("v1StreamSse", options),
-];
+ createQueryKey('v1StreamSse', options),
+]
export const v1StreamSseOptions = (options?: OptionsLegacyParser) => {
return queryOptions({
@@ -699,16 +699,16 @@ export const v1StreamSseOptions = (options?: OptionsLegacyParser) => {
...queryKey[0],
signal,
throwOnError: true,
- });
- return data;
+ })
+ return data
},
queryKey: v1StreamSseQueryKey(options),
- });
-};
+ })
+}
export const v1VersionCheckQueryKey = (options?: OptionsLegacyParser) => [
- createQueryKey("v1VersionCheck", options),
-];
+ createQueryKey('v1VersionCheck', options),
+]
export const v1VersionCheckOptions = (options?: OptionsLegacyParser) => {
return queryOptions({
@@ -718,19 +718,19 @@ export const v1VersionCheckOptions = (options?: OptionsLegacyParser) => {
...queryKey[0],
signal,
throwOnError: true,
- });
- return data;
+ })
+ return data
},
queryKey: v1VersionCheckQueryKey(options),
- });
-};
+ })
+}
export const v1GetWorkspaceTokenUsageQueryKey = (
- options: OptionsLegacyParser,
-) => [createQueryKey("v1GetWorkspaceTokenUsage", options)];
+ options: OptionsLegacyParser
+) => [createQueryKey('v1GetWorkspaceTokenUsage', options)]
export const v1GetWorkspaceTokenUsageOptions = (
- options: OptionsLegacyParser,
+ options: OptionsLegacyParser
) => {
return queryOptions({
queryFn: async ({ queryKey, signal }) => {
@@ -739,9 +739,9 @@ export const v1GetWorkspaceTokenUsageOptions = (
...queryKey[0],
signal,
throwOnError: true,
- });
- return data;
+ })
+ return data
},
queryKey: v1GetWorkspaceTokenUsageQueryKey(options),
- });
-};
+ })
+}
diff --git a/src/api/generated/index.ts b/src/api/generated/index.ts
index eae885d0..544a87d4 100644
--- a/src/api/generated/index.ts
+++ b/src/api/generated/index.ts
@@ -1,3 +1,3 @@
// This file is auto-generated by @hey-api/openapi-ts
-export * from "./sdk.gen";
-export * from "./types.gen";
+export * from './sdk.gen'
+export * from './types.gen'
diff --git a/src/api/generated/sdk.gen.ts b/src/api/generated/sdk.gen.ts
index 8069e38d..9834d09b 100644
--- a/src/api/generated/sdk.gen.ts
+++ b/src/api/generated/sdk.gen.ts
@@ -4,7 +4,7 @@ import {
createClient,
createConfig,
type OptionsLegacyParser,
-} from "@hey-api/client-fetch";
+} from '@hey-api/client-fetch'
import type {
HealthCheckHealthGetError,
HealthCheckHealthGetResponse,
@@ -80,15 +80,15 @@ import type {
V1GetWorkspaceTokenUsageData,
V1GetWorkspaceTokenUsageError,
V1GetWorkspaceTokenUsageResponse,
-} from "./types.gen";
+} from './types.gen'
-export const client = createClient(createConfig());
+export const client = createClient(createConfig())
/**
* Health Check
*/
export const healthCheckHealthGet = (
- options?: OptionsLegacyParser,
+ options?: OptionsLegacyParser
) => {
return (options?.client ?? client).get<
HealthCheckHealthGetResponse,
@@ -96,16 +96,16 @@ export const healthCheckHealthGet = (
ThrowOnError
>({
...options,
- url: "/health",
- });
-};
+ url: '/health',
+ })
+}
/**
* List Provider Endpoints
* List all provider endpoints.
*/
export const v1ListProviderEndpoints = (
- options?: OptionsLegacyParser,
+ options?: OptionsLegacyParser
) => {
return (options?.client ?? client).get<
V1ListProviderEndpointsResponse,
@@ -113,16 +113,16 @@ export const v1ListProviderEndpoints = (
ThrowOnError
>({
...options,
- url: "/api/v1/provider-endpoints",
- });
-};
+ url: '/api/v1/provider-endpoints',
+ })
+}
/**
* Add Provider Endpoint
* Add a provider endpoint.
*/
export const v1AddProviderEndpoint = (
- options: OptionsLegacyParser,
+ options: OptionsLegacyParser
) => {
return (options?.client ?? client).post<
V1AddProviderEndpointResponse,
@@ -130,9 +130,9 @@ export const v1AddProviderEndpoint = (
ThrowOnError
>({
...options,
- url: "/api/v1/provider-endpoints",
- });
-};
+ url: '/api/v1/provider-endpoints',
+ })
+}
/**
* List All Models For All Providers
@@ -141,7 +141,7 @@ export const v1AddProviderEndpoint = (
export const v1ListAllModelsForAllProviders = <
ThrowOnError extends boolean = false,
>(
- options?: OptionsLegacyParser,
+ options?: OptionsLegacyParser
) => {
return (options?.client ?? client).get<
V1ListAllModelsForAllProvidersResponse,
@@ -149,16 +149,16 @@ export const v1ListAllModelsForAllProviders = <
ThrowOnError
>({
...options,
- url: "/api/v1/provider-endpoints/models",
- });
-};
+ url: '/api/v1/provider-endpoints/models',
+ })
+}
/**
* List Models By Provider
* List models by provider.
*/
export const v1ListModelsByProvider = (
- options: OptionsLegacyParser,
+ options: OptionsLegacyParser
) => {
return (options?.client ?? client).get<
V1ListModelsByProviderResponse,
@@ -166,16 +166,16 @@ export const v1ListModelsByProvider = (
ThrowOnError
>({
...options,
- url: "/api/v1/provider-endpoints/{provider_id}/models",
- });
-};
+ url: '/api/v1/provider-endpoints/{provider_id}/models',
+ })
+}
/**
* Get Provider Endpoint
* Get a provider endpoint by ID.
*/
export const v1GetProviderEndpoint = (
- options: OptionsLegacyParser,
+ options: OptionsLegacyParser
) => {
return (options?.client ?? client).get<
V1GetProviderEndpointResponse,
@@ -183,16 +183,16 @@ export const v1GetProviderEndpoint = (
ThrowOnError
>({
...options,
- url: "/api/v1/provider-endpoints/{provider_id}",
- });
-};
+ url: '/api/v1/provider-endpoints/{provider_id}',
+ })
+}
/**
* Update Provider Endpoint
* Update a provider endpoint by ID.
*/
export const v1UpdateProviderEndpoint = (
- options: OptionsLegacyParser,
+ options: OptionsLegacyParser
) => {
return (options?.client ?? client).put<
V1UpdateProviderEndpointResponse,
@@ -200,16 +200,16 @@ export const v1UpdateProviderEndpoint = (
ThrowOnError
>({
...options,
- url: "/api/v1/provider-endpoints/{provider_id}",
- });
-};
+ url: '/api/v1/provider-endpoints/{provider_id}',
+ })
+}
/**
* Delete Provider Endpoint
* Delete a provider endpoint by id.
*/
export const v1DeleteProviderEndpoint = (
- options: OptionsLegacyParser,
+ options: OptionsLegacyParser
) => {
return (options?.client ?? client).delete<
V1DeleteProviderEndpointResponse,
@@ -217,16 +217,16 @@ export const v1DeleteProviderEndpoint = (
ThrowOnError
>({
...options,
- url: "/api/v1/provider-endpoints/{provider_id}",
- });
-};
+ url: '/api/v1/provider-endpoints/{provider_id}',
+ })
+}
/**
* Configure Auth Material
* Configure auth material for a provider.
*/
export const v1ConfigureAuthMaterial = (
- options: OptionsLegacyParser,
+ options: OptionsLegacyParser
) => {
return (options?.client ?? client).put<
V1ConfigureAuthMaterialResponse,
@@ -234,16 +234,16 @@ export const v1ConfigureAuthMaterial = (
ThrowOnError
>({
...options,
- url: "/api/v1/provider-endpoints/{provider_id}/auth-material",
- });
-};
+ url: '/api/v1/provider-endpoints/{provider_id}/auth-material',
+ })
+}
/**
* List Workspaces
* List all workspaces.
*/
export const v1ListWorkspaces = (
- options?: OptionsLegacyParser,
+ options?: OptionsLegacyParser
) => {
return (options?.client ?? client).get<
V1ListWorkspacesResponse,
@@ -251,16 +251,16 @@ export const v1ListWorkspaces = (
ThrowOnError
>({
...options,
- url: "/api/v1/workspaces",
- });
-};
+ url: '/api/v1/workspaces',
+ })
+}
/**
* Create Workspace
* Create a new workspace.
*/
export const v1CreateWorkspace = (
- options: OptionsLegacyParser,
+ options: OptionsLegacyParser
) => {
return (options?.client ?? client).post<
V1CreateWorkspaceResponse,
@@ -268,9 +268,9 @@ export const v1CreateWorkspace = (
ThrowOnError
>({
...options,
- url: "/api/v1/workspaces",
- });
-};
+ url: '/api/v1/workspaces',
+ })
+}
/**
* List Active Workspaces
@@ -280,7 +280,7 @@ export const v1CreateWorkspace = (
* the globally active workspace.
*/
export const v1ListActiveWorkspaces = (
- options?: OptionsLegacyParser,
+ options?: OptionsLegacyParser
) => {
return (options?.client ?? client).get<
V1ListActiveWorkspacesResponse,
@@ -288,16 +288,16 @@ export const v1ListActiveWorkspaces = (
ThrowOnError
>({
...options,
- url: "/api/v1/workspaces/active",
- });
-};
+ url: '/api/v1/workspaces/active',
+ })
+}
/**
* Activate Workspace
* Activate a workspace by name.
*/
export const v1ActivateWorkspace = (
- options: OptionsLegacyParser,
+ options: OptionsLegacyParser
) => {
return (options?.client ?? client).post<
V1ActivateWorkspaceResponse,
@@ -305,16 +305,16 @@ export const v1ActivateWorkspace = (
ThrowOnError
>({
...options,
- url: "/api/v1/workspaces/active",
- });
-};
+ url: '/api/v1/workspaces/active',
+ })
+}
/**
* Delete Workspace
* Delete a workspace by name.
*/
export const v1DeleteWorkspace = (
- options: OptionsLegacyParser,
+ options: OptionsLegacyParser
) => {
return (options?.client ?? client).delete<
V1DeleteWorkspaceResponse,
@@ -322,16 +322,16 @@ export const v1DeleteWorkspace = (
ThrowOnError
>({
...options,
- url: "/api/v1/workspaces/{workspace_name}",
- });
-};
+ url: '/api/v1/workspaces/{workspace_name}',
+ })
+}
/**
* List Archived Workspaces
* List all archived workspaces.
*/
export const v1ListArchivedWorkspaces = (
- options?: OptionsLegacyParser,
+ options?: OptionsLegacyParser
) => {
return (options?.client ?? client).get<
V1ListArchivedWorkspacesResponse,
@@ -339,16 +339,16 @@ export const v1ListArchivedWorkspaces = (
ThrowOnError
>({
...options,
- url: "/api/v1/workspaces/archive",
- });
-};
+ url: '/api/v1/workspaces/archive',
+ })
+}
/**
* Recover Workspace
* Recover an archived workspace by name.
*/
export const v1RecoverWorkspace = (
- options: OptionsLegacyParser,
+ options: OptionsLegacyParser
) => {
return (options?.client ?? client).post<
V1RecoverWorkspaceResponse,
@@ -356,16 +356,16 @@ export const v1RecoverWorkspace = (
ThrowOnError
>({
...options,
- url: "/api/v1/workspaces/archive/{workspace_name}/recover",
- });
-};
+ url: '/api/v1/workspaces/archive/{workspace_name}/recover',
+ })
+}
/**
* Hard Delete Workspace
* Hard delete an archived workspace by name.
*/
export const v1HardDeleteWorkspace = (
- options: OptionsLegacyParser,
+ options: OptionsLegacyParser
) => {
return (options?.client ?? client).delete<
V1HardDeleteWorkspaceResponse,
@@ -373,16 +373,16 @@ export const v1HardDeleteWorkspace = (
ThrowOnError
>({
...options,
- url: "/api/v1/workspaces/archive/{workspace_name}",
- });
-};
+ url: '/api/v1/workspaces/archive/{workspace_name}',
+ })
+}
/**
* Get Workspace Alerts
* Get alerts for a workspace.
*/
export const v1GetWorkspaceAlerts = (
- options: OptionsLegacyParser,
+ options: OptionsLegacyParser
) => {
return (options?.client ?? client).get<
V1GetWorkspaceAlertsResponse,
@@ -390,16 +390,16 @@ export const v1GetWorkspaceAlerts = (
ThrowOnError
>({
...options,
- url: "/api/v1/workspaces/{workspace_name}/alerts",
- });
-};
+ url: '/api/v1/workspaces/{workspace_name}/alerts',
+ })
+}
/**
* Get Workspace Messages
* Get messages for a workspace.
*/
export const v1GetWorkspaceMessages = (
- options: OptionsLegacyParser,
+ options: OptionsLegacyParser
) => {
return (options?.client ?? client).get<
V1GetWorkspaceMessagesResponse,
@@ -407,9 +407,9 @@ export const v1GetWorkspaceMessages = (
ThrowOnError
>({
...options,
- url: "/api/v1/workspaces/{workspace_name}/messages",
- });
-};
+ url: '/api/v1/workspaces/{workspace_name}/messages',
+ })
+}
/**
* Get Workspace Custom Instructions
@@ -421,7 +421,7 @@ export const v1GetWorkspaceCustomInstructions = <
options: OptionsLegacyParser<
V1GetWorkspaceCustomInstructionsData,
ThrowOnError
- >,
+ >
) => {
return (options?.client ?? client).get<
V1GetWorkspaceCustomInstructionsResponse,
@@ -429,9 +429,9 @@ export const v1GetWorkspaceCustomInstructions = <
ThrowOnError
>({
...options,
- url: "/api/v1/workspaces/{workspace_name}/custom-instructions",
- });
-};
+ url: '/api/v1/workspaces/{workspace_name}/custom-instructions',
+ })
+}
/**
* Set Workspace Custom Instructions
@@ -442,7 +442,7 @@ export const v1SetWorkspaceCustomInstructions = <
options: OptionsLegacyParser<
V1SetWorkspaceCustomInstructionsData,
ThrowOnError
- >,
+ >
) => {
return (options?.client ?? client).put<
V1SetWorkspaceCustomInstructionsResponse,
@@ -450,9 +450,9 @@ export const v1SetWorkspaceCustomInstructions = <
ThrowOnError
>({
...options,
- url: "/api/v1/workspaces/{workspace_name}/custom-instructions",
- });
-};
+ url: '/api/v1/workspaces/{workspace_name}/custom-instructions',
+ })
+}
/**
* Delete Workspace Custom Instructions
@@ -463,7 +463,7 @@ export const v1DeleteWorkspaceCustomInstructions = <
options: OptionsLegacyParser<
V1DeleteWorkspaceCustomInstructionsData,
ThrowOnError
- >,
+ >
) => {
return (options?.client ?? client).delete<
V1DeleteWorkspaceCustomInstructionsResponse,
@@ -471,9 +471,9 @@ export const v1DeleteWorkspaceCustomInstructions = <
ThrowOnError
>({
...options,
- url: "/api/v1/workspaces/{workspace_name}/custom-instructions",
- });
-};
+ url: '/api/v1/workspaces/{workspace_name}/custom-instructions',
+ })
+}
/**
* Get Workspace Muxes
@@ -483,7 +483,7 @@ export const v1DeleteWorkspaceCustomInstructions = <
* has the highest priority.
*/
export const v1GetWorkspaceMuxes = (
- options: OptionsLegacyParser,
+ options: OptionsLegacyParser | | | |