mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-24 13:01:56 +00:00
refactor(ui): streamline Lighthouse config and poll connection test
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
"use server";
|
||||
|
||||
import { pollTaskUntilSettled } from "@/actions/task/poll";
|
||||
import type {
|
||||
LighthouseV2Configuration,
|
||||
LighthouseV2ConfigurationInput,
|
||||
@@ -101,16 +102,51 @@ export async function deleteLighthouseV2Configuration(
|
||||
);
|
||||
}
|
||||
|
||||
// Starts the backend connection-check task, polls it to completion (reusing the
|
||||
// shared task poller), then returns the re-fetched configuration so the caller
|
||||
// can render the authoritative `connected` / `connectionLastCheckedAt` status.
|
||||
export async function testLighthouseV2ConfigurationConnection(
|
||||
configId: string,
|
||||
): Promise<LighthouseV2ActionResult<LighthouseV2Task>> {
|
||||
return mutateSingle(
|
||||
`/lighthouse/config/${encodeURIComponent(configId)}/connection`,
|
||||
{ method: "POST" },
|
||||
mapLighthouseV2Task,
|
||||
"/lighthouse/config",
|
||||
false,
|
||||
);
|
||||
): Promise<LighthouseV2ActionResult<LighthouseV2Configuration>> {
|
||||
try {
|
||||
const response = await fetch(
|
||||
buildApiUrl(
|
||||
`/lighthouse/config/${encodeURIComponent(configId)}/connection`,
|
||||
),
|
||||
{ method: "POST", headers: await getAuthHeaders({ contentType: false }) },
|
||||
);
|
||||
const document = (await handleApiResponse(
|
||||
response,
|
||||
)) as JsonApiDocument<TaskResource>;
|
||||
if (isErrorDocument(document) || !document.data) {
|
||||
return toErrorResult(document);
|
||||
}
|
||||
|
||||
const settled = await pollTaskUntilSettled(
|
||||
mapLighthouseV2Task(document.data).id,
|
||||
{
|
||||
maxAttempts: 20,
|
||||
delayMs: 3000,
|
||||
},
|
||||
);
|
||||
if (!settled.ok) {
|
||||
return { error: settled.error || "Connection test timed out." };
|
||||
}
|
||||
|
||||
const configurations = await getLighthouseV2Configurations();
|
||||
if ("error" in configurations) {
|
||||
return configurations;
|
||||
}
|
||||
const updated = configurations.data.find(
|
||||
(config) => config.id === configId,
|
||||
);
|
||||
if (!updated) {
|
||||
return { error: "Configuration not found after connection test." };
|
||||
}
|
||||
return { data: updated };
|
||||
} catch (error) {
|
||||
return handleApiError(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getLighthouseV2SupportedProviders(): Promise<
|
||||
|
||||
@@ -6,13 +6,10 @@ import {
|
||||
KeyRound,
|
||||
Loader2,
|
||||
PlugZap,
|
||||
RefreshCw,
|
||||
Save,
|
||||
ShieldCheck,
|
||||
Sparkles,
|
||||
Trash2,
|
||||
} from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
|
||||
@@ -34,6 +31,7 @@ import {
|
||||
type LighthouseV2ConfigFormValues,
|
||||
trimToNullable,
|
||||
} from "@/app/(prowler)/lighthouse/_lib/config";
|
||||
import { formatLastChecked } from "@/app/(prowler)/lighthouse/_lib/format";
|
||||
import {
|
||||
type LighthouseV2Configuration,
|
||||
type LighthouseV2ConfigurationInput,
|
||||
@@ -55,9 +53,7 @@ import { Textarea } from "@/components/shadcn/textarea/textarea";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { ConfigurationSection } from "./configuration-section";
|
||||
import { ConnectionStatusPanel } from "./connection-status-panel";
|
||||
import { CredentialFields } from "./credential-fields";
|
||||
import { ModelDetails } from "./model-details";
|
||||
import { ProviderIcon } from "./provider-icon";
|
||||
import { StatusBadge } from "./status-badge";
|
||||
|
||||
@@ -66,6 +62,7 @@ export function LighthouseV2ConfigurationForm({
|
||||
models,
|
||||
onConfigurationDeleted,
|
||||
onConfigurationSaved,
|
||||
onConfigurationTested,
|
||||
onFeedback,
|
||||
provider,
|
||||
}: {
|
||||
@@ -73,10 +70,10 @@ export function LighthouseV2ConfigurationForm({
|
||||
models: LighthouseV2SupportedModel[];
|
||||
onConfigurationDeleted: (configurationId: string) => void;
|
||||
onConfigurationSaved: (configuration: LighthouseV2Configuration) => void;
|
||||
onConfigurationTested: (configuration: LighthouseV2Configuration) => void;
|
||||
onFeedback: (feedback: FeedbackState | null) => void;
|
||||
provider: LighthouseV2SupportedProvider;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [testing, setTesting] = useState(false);
|
||||
const [deleting, setDeleting] = useState(false);
|
||||
@@ -91,10 +88,6 @@ export function LighthouseV2ConfigurationForm({
|
||||
mode: "onSubmit",
|
||||
});
|
||||
const businessContext = form.watch("businessContext");
|
||||
const selectedModel = form.watch("defaultModel");
|
||||
const selectedModelDetails = models.find(
|
||||
(model) => model.id === selectedModel,
|
||||
);
|
||||
const status = getConnectionStatus(configuration);
|
||||
|
||||
const handleSave = async (values: LighthouseV2ConfigFormValues) => {
|
||||
@@ -152,20 +145,14 @@ export function LighthouseV2ConfigurationForm({
|
||||
|
||||
if ("error" in result) {
|
||||
onFeedback({
|
||||
title: "Connection check failed to start",
|
||||
title: "Connection check failed",
|
||||
description: result.error,
|
||||
variant: FEEDBACK_VARIANT.ERROR,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
onFeedback({
|
||||
title: "Connection check started.",
|
||||
description:
|
||||
"The backend is validating this provider. Refresh status when the task finishes.",
|
||||
variant: FEEDBACK_VARIANT.INFO,
|
||||
showRefreshStatus: true,
|
||||
});
|
||||
onConfigurationTested(result.data);
|
||||
};
|
||||
|
||||
const handleDelete = async () => {
|
||||
@@ -191,7 +178,7 @@ export function LighthouseV2ConfigurationForm({
|
||||
|
||||
return (
|
||||
<section className="min-w-0">
|
||||
<div className="border-border-neutral-secondary flex flex-col gap-4 border-b px-4 py-4 md:flex-row md:items-start md:justify-between md:px-5">
|
||||
<div className="border-border-neutral-secondary flex flex-col gap-4 border-b px-4 py-6 md:flex-row md:items-start md:justify-between md:px-5">
|
||||
<div className="flex min-w-0 gap-3">
|
||||
<div className="border-border-neutral-secondary bg-bg-neutral-tertiary flex size-12 shrink-0 items-center justify-center rounded-[10px] border">
|
||||
<ProviderIcon
|
||||
@@ -205,6 +192,9 @@ export function LighthouseV2ConfigurationForm({
|
||||
{provider.name}
|
||||
</h3>
|
||||
<StatusBadge status={status} />
|
||||
<span className="text-text-neutral-tertiary text-xs">
|
||||
{formatLastChecked(configuration?.connectionLastCheckedAt)}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-text-neutral-secondary mt-1 max-w-2xl text-sm">
|
||||
{configuration
|
||||
@@ -222,15 +212,7 @@ export function LighthouseV2ConfigurationForm({
|
||||
disabled={!configuration || testing}
|
||||
>
|
||||
{testing ? <Loader2 className="animate-spin" /> : <PlugZap />}
|
||||
Test connection
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => router.refresh()}
|
||||
>
|
||||
<RefreshCw />
|
||||
Refresh status
|
||||
{testing ? "Testing connection…" : "Test connection"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -240,17 +222,6 @@ export function LighthouseV2ConfigurationForm({
|
||||
onSubmit={form.handleSubmit(handleSave)}
|
||||
noValidate
|
||||
>
|
||||
<ConfigurationSection
|
||||
icon={<ShieldCheck className="size-4" />}
|
||||
title="Connection"
|
||||
description="Current backend check result for this provider."
|
||||
>
|
||||
<ConnectionStatusPanel
|
||||
configuration={configuration}
|
||||
status={status}
|
||||
/>
|
||||
</ConfigurationSection>
|
||||
|
||||
<ConfigurationSection
|
||||
icon={<KeyRound className="size-4" />}
|
||||
title="Credentials"
|
||||
@@ -262,7 +233,6 @@ export function LighthouseV2ConfigurationForm({
|
||||
>
|
||||
<CredentialFields
|
||||
errors={form.formState.errors}
|
||||
hasConfiguration={hasConfiguration}
|
||||
provider={providerType}
|
||||
register={form.register}
|
||||
/>
|
||||
@@ -300,7 +270,6 @@ export function LighthouseV2ConfigurationForm({
|
||||
</Field>
|
||||
)}
|
||||
/>
|
||||
<ModelDetails model={selectedModelDetails} />
|
||||
</ConfigurationSection>
|
||||
|
||||
<ConfigurationSection
|
||||
@@ -339,7 +308,7 @@ export function LighthouseV2ConfigurationForm({
|
||||
</Field>
|
||||
</ConfigurationSection>
|
||||
|
||||
<div className="flex flex-col gap-3 px-4 py-4 sm:flex-row sm:items-center sm:justify-between md:px-5">
|
||||
<div className="flex flex-col gap-4 px-4 py-6 sm:flex-row sm:items-center sm:justify-between md:px-5">
|
||||
<div className="text-text-neutral-secondary text-sm">
|
||||
{configuration
|
||||
? "Saving updates may change chat behavior immediately."
|
||||
|
||||
@@ -12,7 +12,7 @@ export function ConfigurationSection({
|
||||
title: string;
|
||||
}) {
|
||||
return (
|
||||
<section className="border-border-neutral-secondary grid gap-4 border-b px-4 py-5 md:grid-cols-[220px_minmax(0,1fr)] md:px-5">
|
||||
<section className="border-border-neutral-secondary grid gap-6 border-b px-4 py-8 md:grid-cols-[220px_minmax(0,1fr)] md:px-5">
|
||||
<div className="flex gap-3">
|
||||
<div className="border-border-neutral-secondary bg-bg-neutral-tertiary flex size-8 shrink-0 items-center justify-center rounded-[8px] border">
|
||||
{icon}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
import { AlertCircle, CheckCircle2, CircleDashed } from "lucide-react";
|
||||
|
||||
import {
|
||||
CONNECTION_STATUS,
|
||||
type ConnectionStatus,
|
||||
getAlertVariant,
|
||||
getConnectionStatusLabel,
|
||||
} from "@/app/(prowler)/lighthouse/_lib/config";
|
||||
import { formatLastChecked } from "@/app/(prowler)/lighthouse/_lib/format";
|
||||
import { type LighthouseV2Configuration } from "@/app/(prowler)/lighthouse/_types";
|
||||
import { Alert, AlertDescription, AlertTitle } from "@/components/shadcn/alert";
|
||||
|
||||
export function ConnectionStatusPanel({
|
||||
configuration,
|
||||
status,
|
||||
}: {
|
||||
configuration?: LighthouseV2Configuration;
|
||||
status: ConnectionStatus;
|
||||
}) {
|
||||
const statusText = getConnectionStatusLabel(status);
|
||||
const description =
|
||||
status === CONNECTION_STATUS.CONNECTED
|
||||
? "Lighthouse can send messages with this provider."
|
||||
: status === CONNECTION_STATUS.FAILED
|
||||
? "Connection failed. Review credentials and run another test."
|
||||
: "Connection has not been tested yet.";
|
||||
|
||||
return (
|
||||
<Alert variant={getAlertVariant(status)}>
|
||||
{status === CONNECTION_STATUS.CONNECTED ? (
|
||||
<CheckCircle2 className="size-4" />
|
||||
) : status === CONNECTION_STATUS.FAILED ? (
|
||||
<AlertCircle className="size-4" />
|
||||
) : (
|
||||
<CircleDashed className="size-4" />
|
||||
)}
|
||||
<AlertTitle>{statusText}</AlertTitle>
|
||||
<AlertDescription>
|
||||
<p>{description}</p>
|
||||
<p>{formatLastChecked(configuration?.connectionLastCheckedAt)}</p>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
@@ -10,14 +10,12 @@ import { Input } from "@/components/shadcn/input/input";
|
||||
|
||||
export function CredentialFields({
|
||||
errors,
|
||||
hasConfiguration,
|
||||
provider,
|
||||
register,
|
||||
}: {
|
||||
errors: ReturnType<
|
||||
typeof useForm<LighthouseV2ConfigFormValues>
|
||||
>["formState"]["errors"];
|
||||
hasConfiguration: boolean;
|
||||
provider: LighthouseV2ProviderType;
|
||||
register: ReturnType<
|
||||
typeof useForm<LighthouseV2ConfigFormValues>
|
||||
@@ -25,12 +23,6 @@ export function CredentialFields({
|
||||
}) {
|
||||
return (
|
||||
<div className="grid gap-4">
|
||||
{hasConfiguration && (
|
||||
<p className="text-text-neutral-secondary text-sm">
|
||||
Leave blank to keep existing credentials.
|
||||
</p>
|
||||
)}
|
||||
|
||||
{(provider === LIGHTHOUSE_V2_PROVIDER_TYPE.OPENAI ||
|
||||
provider === LIGHTHOUSE_V2_PROVIDER_TYPE.OPENAI_COMPATIBLE) && (
|
||||
<Field>
|
||||
|
||||
@@ -1,27 +1,24 @@
|
||||
import { AlertCircle, CheckCircle2, RefreshCw } from "lucide-react";
|
||||
import { AlertCircle, CheckCircle2, Info } from "lucide-react";
|
||||
|
||||
import {
|
||||
FEEDBACK_VARIANT,
|
||||
type FeedbackState,
|
||||
} from "@/app/(prowler)/lighthouse/_lib/config";
|
||||
import { Alert, AlertDescription, AlertTitle } from "@/components/shadcn/alert";
|
||||
import { Button } from "@/components/shadcn/button/button";
|
||||
|
||||
export function ConfigFeedbackAlert({
|
||||
feedback,
|
||||
onClose,
|
||||
onRefreshStatus,
|
||||
}: {
|
||||
feedback: FeedbackState;
|
||||
onClose: () => void;
|
||||
onRefreshStatus: () => void;
|
||||
}) {
|
||||
const Icon =
|
||||
feedback.variant === FEEDBACK_VARIANT.ERROR
|
||||
? AlertCircle
|
||||
: feedback.variant === FEEDBACK_VARIANT.SUCCESS
|
||||
? CheckCircle2
|
||||
: RefreshCw;
|
||||
: Info;
|
||||
|
||||
return (
|
||||
<Alert variant={feedback.variant} onClose={onClose}>
|
||||
@@ -29,18 +26,6 @@ export function ConfigFeedbackAlert({
|
||||
<AlertTitle>{feedback.title}</AlertTitle>
|
||||
<AlertDescription>
|
||||
{feedback.description && <p>{feedback.description}</p>}
|
||||
{feedback.showRefreshStatus && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="link"
|
||||
size="link-sm"
|
||||
className="h-auto p-0"
|
||||
onClick={onRefreshStatus}
|
||||
>
|
||||
<RefreshCw className="size-3.5" />
|
||||
Refresh status
|
||||
</Button>
|
||||
)}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
);
|
||||
|
||||
@@ -13,21 +13,17 @@ import { LighthouseV2ConfigPage } from "./lighthouse-v2-config-page";
|
||||
const {
|
||||
createConfigurationMock,
|
||||
deleteConfigurationMock,
|
||||
refreshMock,
|
||||
testConnectionMock,
|
||||
updateConfigurationMock,
|
||||
} = vi.hoisted(() => ({
|
||||
createConfigurationMock: vi.fn(),
|
||||
deleteConfigurationMock: vi.fn(),
|
||||
refreshMock: vi.fn(),
|
||||
testConnectionMock: vi.fn(),
|
||||
updateConfigurationMock: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("next/navigation", () => ({
|
||||
useRouter: () => ({
|
||||
refresh: refreshMock,
|
||||
}),
|
||||
useRouter: () => ({ refresh: vi.fn(), push: vi.fn() }),
|
||||
}));
|
||||
|
||||
vi.mock("@/app/(prowler)/lighthouse/_actions", () => ({
|
||||
@@ -90,20 +86,15 @@ describe("LighthouseV2ConfigPage", () => {
|
||||
beforeEach(() => {
|
||||
createConfigurationMock.mockReset();
|
||||
deleteConfigurationMock.mockReset();
|
||||
refreshMock.mockReset();
|
||||
testConnectionMock.mockReset();
|
||||
updateConfigurationMock.mockReset();
|
||||
|
||||
createConfigurationMock.mockResolvedValue({ data: configurations[0] });
|
||||
deleteConfigurationMock.mockResolvedValue({ data: true });
|
||||
// The action polls the task internally and resolves with the re-fetched
|
||||
// configuration carrying the authoritative connection status.
|
||||
testConnectionMock.mockResolvedValue({
|
||||
data: {
|
||||
id: "task-1",
|
||||
name: "lighthouse-config-connection",
|
||||
state: "PENDING",
|
||||
insertedAt: "2026-06-24T10:01:00Z",
|
||||
completedAt: null,
|
||||
},
|
||||
data: { ...configurations[0], connected: true },
|
||||
});
|
||||
updateConfigurationMock.mockResolvedValue({ data: configurations[0] });
|
||||
});
|
||||
@@ -310,26 +301,25 @@ describe("LighthouseV2ConfigPage", () => {
|
||||
expect(updateConfigurationMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("starts a connection test and exposes a refresh status action", async () => {
|
||||
it("tests the connection and reports the resulting status", async () => {
|
||||
// Given
|
||||
const user = userEvent.setup();
|
||||
testConnectionMock.mockResolvedValue({
|
||||
data: { ...configurations[0], connected: false },
|
||||
});
|
||||
renderPage();
|
||||
|
||||
// When
|
||||
await user.click(screen.getByRole("button", { name: /Test connection/i }));
|
||||
|
||||
// Then
|
||||
// Then: the action is polled to completion and the resulting status shown
|
||||
await waitFor(() =>
|
||||
expect(testConnectionMock).toHaveBeenCalledWith("config-openai"),
|
||||
);
|
||||
expect(await screen.findByText("Connection failed.")).toBeInTheDocument();
|
||||
expect(
|
||||
await screen.findByText("Connection check started."),
|
||||
).toBeInTheDocument();
|
||||
|
||||
await user.click(
|
||||
screen.getAllByRole("button", { name: /Refresh status/i })[0],
|
||||
);
|
||||
expect(refreshMock).toHaveBeenCalledTimes(1);
|
||||
screen.queryByRole("button", { name: /Refresh status/i }),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("confirms before deleting an existing configuration", async () => {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
|
||||
import {
|
||||
@@ -36,7 +35,6 @@ export function LighthouseV2ConfigPage({
|
||||
modelsByProvider,
|
||||
error,
|
||||
}: LighthouseV2ConfigPageProps) {
|
||||
const router = useRouter();
|
||||
const [localConfigurations, setLocalConfigurations] =
|
||||
useState(configurations);
|
||||
const [selectedProvider, setSelectedProvider] =
|
||||
@@ -63,13 +61,17 @@ export function LighthouseV2ConfigPage({
|
||||
? modelsByProvider[selectedProviderDefinition.id]
|
||||
: [];
|
||||
|
||||
const handleConfigurationSaved = (
|
||||
configuration: LighthouseV2Configuration,
|
||||
) => {
|
||||
const upsertConfiguration = (configuration: LighthouseV2Configuration) => {
|
||||
setLocalConfigurations((current) => [
|
||||
...current.filter((config) => config.id !== configuration.id),
|
||||
configuration,
|
||||
]);
|
||||
};
|
||||
|
||||
const handleConfigurationSaved = (
|
||||
configuration: LighthouseV2Configuration,
|
||||
) => {
|
||||
upsertConfiguration(configuration);
|
||||
setSelectedProvider(configuration.providerType);
|
||||
setFeedback({
|
||||
title: "Configuration saved.",
|
||||
@@ -78,6 +80,26 @@ export function LighthouseV2ConfigPage({
|
||||
});
|
||||
};
|
||||
|
||||
const handleConfigurationTested = (
|
||||
configuration: LighthouseV2Configuration,
|
||||
) => {
|
||||
upsertConfiguration(configuration);
|
||||
setFeedback(
|
||||
configuration.connected
|
||||
? {
|
||||
title: "Connection successful.",
|
||||
description: "Lighthouse can send messages with this provider.",
|
||||
variant: FEEDBACK_VARIANT.SUCCESS,
|
||||
}
|
||||
: {
|
||||
title: "Connection failed.",
|
||||
description:
|
||||
"Review the credentials and test the connection again.",
|
||||
variant: FEEDBACK_VARIANT.ERROR,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const handleConfigurationDeleted = (configurationId: string) => {
|
||||
setLocalConfigurations((current) =>
|
||||
current.filter((config) => config.id !== configurationId),
|
||||
@@ -105,7 +127,6 @@ export function LighthouseV2ConfigPage({
|
||||
<div className="border-border-neutral-secondary border-b px-4 py-4 md:px-5">
|
||||
<ConfigFeedbackAlert
|
||||
feedback={feedback}
|
||||
onRefreshStatus={() => router.refresh()}
|
||||
onClose={() => setFeedback(null)}
|
||||
/>
|
||||
</div>
|
||||
@@ -138,6 +159,7 @@ export function LighthouseV2ConfigPage({
|
||||
provider={selectedProviderDefinition}
|
||||
onConfigurationSaved={handleConfigurationSaved}
|
||||
onConfigurationDeleted={handleConfigurationDeleted}
|
||||
onConfigurationTested={handleConfigurationTested}
|
||||
onFeedback={setFeedback}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
import { CheckCircle2, CircleDashed } from "lucide-react";
|
||||
|
||||
import { formatTokenLimit } from "@/app/(prowler)/lighthouse/_lib/format";
|
||||
import { type LighthouseV2SupportedModel } from "@/app/(prowler)/lighthouse/_types";
|
||||
|
||||
export function ModelDetails({
|
||||
model,
|
||||
}: {
|
||||
model?: LighthouseV2SupportedModel;
|
||||
}) {
|
||||
if (!model) {
|
||||
return (
|
||||
<div className="border-border-neutral-secondary bg-bg-neutral-tertiary mt-3 rounded-[10px] border px-3 py-3">
|
||||
<p className="text-text-neutral-secondary text-sm">
|
||||
Select a model to see capabilities.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="border-border-neutral-secondary bg-bg-neutral-tertiary mt-3 grid gap-3 rounded-[10px] border px-3 py-3 sm:grid-cols-3">
|
||||
<CapabilityItem label="Tools" enabled={model.supportsFunctionCalling} />
|
||||
<CapabilityItem label="Vision" enabled={model.supportsVision} />
|
||||
<CapabilityItem label="Reasoning" enabled={model.supportsReasoning} />
|
||||
<div className="text-text-neutral-secondary text-xs sm:col-span-3">
|
||||
Input tokens: {formatTokenLimit(model.maxInputTokens)} · Output tokens:{" "}
|
||||
{formatTokenLimit(model.maxOutputTokens)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function CapabilityItem({
|
||||
enabled,
|
||||
label,
|
||||
}: {
|
||||
enabled: boolean | null;
|
||||
label: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
{enabled ? (
|
||||
<CheckCircle2 className="text-text-success-primary size-4" />
|
||||
) : (
|
||||
<CircleDashed className="text-text-neutral-tertiary size-4" />
|
||||
)}
|
||||
<span className="text-text-neutral-primary">{label}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -31,7 +31,6 @@ export interface FeedbackState {
|
||||
title: string;
|
||||
description?: string;
|
||||
variant: FeedbackVariant;
|
||||
showRefreshStatus?: boolean;
|
||||
}
|
||||
|
||||
const lighthouseV2ConfigFormSchemaBase = z.object({
|
||||
@@ -174,15 +173,3 @@ export function getConnectionStatus(
|
||||
if (configuration?.connected === false) return CONNECTION_STATUS.FAILED;
|
||||
return CONNECTION_STATUS.NOT_TESTED;
|
||||
}
|
||||
|
||||
export function getConnectionStatusLabel(status: ConnectionStatus): string {
|
||||
if (status === CONNECTION_STATUS.CONNECTED) return "Connected";
|
||||
if (status === CONNECTION_STATUS.FAILED) return "Failed";
|
||||
return "Not tested";
|
||||
}
|
||||
|
||||
export function getAlertVariant(status: ConnectionStatus) {
|
||||
if (status === CONNECTION_STATUS.CONNECTED) return "success";
|
||||
if (status === CONNECTION_STATUS.FAILED) return "error";
|
||||
return "info";
|
||||
}
|
||||
|
||||
@@ -28,7 +28,3 @@ export function formatSessionAge(dateString: string): string {
|
||||
if (ageInDays === 0) return "Today";
|
||||
return ageInDays === 1 ? "1 day" : `${ageInDays} days`;
|
||||
}
|
||||
|
||||
export function formatTokenLimit(value: number | null): string {
|
||||
return value === null ? "Unknown" : value.toLocaleString();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user