mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-04 19:21:51 +00:00
fix(ui): persist Lighthouse model and business context per provider
Route Lighthouse model defaults and business context onto the per-provider
configuration system (PATCH /lighthouse/config/<id>) and drop all use of the
legacy tenant configuration (/lighthouse/configuration, is_active) from the UI.
This fixes the 400 'No active configuration found' error when selecting a
Bedrock model in chat, and partially reverts 58539dced. The chat now resolves
the initial provider by a fixed priority (OpenAI > Bedrock > OpenAI-compatible)
and remembers the chosen model as the provider's default_model. Also narrows
the chat model combobox width.
This commit is contained in:
@@ -2,12 +2,11 @@ import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
buildLighthouseV2ConfigurationPayload,
|
||||
buildLighthouseV2TenantConfigurationUpdatePayload,
|
||||
buildLighthouseV2ConfigurationUpdatePayload,
|
||||
mapLighthouseV2Configuration,
|
||||
mapLighthouseV2Message,
|
||||
mapLighthouseV2Model,
|
||||
mapLighthouseV2Provider,
|
||||
mapLighthouseV2TenantConfiguration,
|
||||
validateLighthouseV2ConfigurationInput,
|
||||
} from "./lighthouse-v2.adapter";
|
||||
|
||||
@@ -22,6 +21,7 @@ describe("lighthouse-v2.adapter", () => {
|
||||
provider_type: "bedrock",
|
||||
base_url: null,
|
||||
default_model: "anthropic.claude",
|
||||
business_context: "Production tenant",
|
||||
connected: true,
|
||||
connection_last_checked_at: "2026-06-24T10:00:00Z",
|
||||
inserted_at: "2026-06-24T09:00:00Z",
|
||||
@@ -38,6 +38,7 @@ describe("lighthouse-v2.adapter", () => {
|
||||
providerType: "bedrock",
|
||||
baseUrl: null,
|
||||
defaultModel: "anthropic.claude",
|
||||
businessContext: "Production tenant",
|
||||
connected: true,
|
||||
connectionLastCheckedAt: "2026-06-24T10:00:00Z",
|
||||
insertedAt: "2026-06-24T09:00:00Z",
|
||||
@@ -79,37 +80,6 @@ describe("lighthouse-v2.adapter", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should map tenant model defaults by provider", () => {
|
||||
// Given
|
||||
const resource: Parameters<typeof mapLighthouseV2TenantConfiguration>[0] =
|
||||
{
|
||||
id: "tenant-config-1",
|
||||
type: "lighthouse-configurations",
|
||||
attributes: {
|
||||
business_context: "Production tenant",
|
||||
default_provider: "bedrock",
|
||||
default_models: {
|
||||
openai: "gpt-5.1",
|
||||
bedrock: "anthropic.claude-4",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// When
|
||||
const config = mapLighthouseV2TenantConfiguration(resource);
|
||||
|
||||
// Then
|
||||
expect(config).toEqual({
|
||||
id: "tenant-config-1",
|
||||
businessContext: "Production tenant",
|
||||
defaultProvider: "bedrock",
|
||||
defaultModels: {
|
||||
openai: "gpt-5.1",
|
||||
bedrock: "anthropic.claude-4",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should map message parts from backend names", () => {
|
||||
// Given
|
||||
const resource: Parameters<typeof mapLighthouseV2Message>[0] = {
|
||||
@@ -179,34 +149,38 @@ describe("lighthouse-v2.adapter", () => {
|
||||
expect(payload.data.attributes).not.toHaveProperty("business_context");
|
||||
});
|
||||
|
||||
it("should build tenant preference payloads with provider model maps", () => {
|
||||
// Given
|
||||
const input = {
|
||||
defaultProvider: "bedrock" as const,
|
||||
defaultModels: {
|
||||
openai: "gpt-5.1",
|
||||
bedrock: "anthropic.claude-4",
|
||||
},
|
||||
};
|
||||
|
||||
it("should build per-provider update payloads with default_model and business_context", () => {
|
||||
// When
|
||||
const payload = buildLighthouseV2TenantConfigurationUpdatePayload(input);
|
||||
const payload = buildLighthouseV2ConfigurationUpdatePayload("config-1", {
|
||||
defaultModel: "anthropic.claude-4",
|
||||
businessContext: "Production tenant",
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(payload).toEqual({
|
||||
data: {
|
||||
type: "lighthouse-configurations",
|
||||
type: "lighthouse-ai-configurations",
|
||||
id: "config-1",
|
||||
attributes: {
|
||||
default_provider: "bedrock",
|
||||
default_models: {
|
||||
openai: "gpt-5.1",
|
||||
bedrock: "anthropic.claude-4",
|
||||
},
|
||||
default_model: "anthropic.claude-4",
|
||||
business_context: "Production tenant",
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should omit untouched fields from the update payload", () => {
|
||||
// When
|
||||
const payload = buildLighthouseV2ConfigurationUpdatePayload("config-1", {
|
||||
defaultModel: "gpt-5.1",
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(payload.data.attributes).toEqual({ default_model: "gpt-5.1" });
|
||||
expect(payload.data.attributes).not.toHaveProperty("business_context");
|
||||
expect(payload.data.attributes).not.toHaveProperty("credentials");
|
||||
});
|
||||
|
||||
it("should require base_url for OpenAI-compatible configurations", () => {
|
||||
// Given
|
||||
const input = {
|
||||
|
||||
@@ -11,8 +11,6 @@ import {
|
||||
type LighthouseV2SupportedModel,
|
||||
type LighthouseV2SupportedProvider,
|
||||
type LighthouseV2Task,
|
||||
type LighthouseV2TenantConfiguration,
|
||||
type LighthouseV2TenantConfigurationUpdateInput,
|
||||
} from "@/app/(prowler)/lighthouse/_types";
|
||||
|
||||
export interface JsonApiResource<TAttributes> {
|
||||
@@ -35,18 +33,13 @@ interface ConfigurationAttributes {
|
||||
provider_type: LighthouseV2ProviderType;
|
||||
base_url: string | null;
|
||||
default_model?: string | null;
|
||||
business_context?: string | null;
|
||||
connected: boolean | null;
|
||||
connection_last_checked_at: string | null;
|
||||
inserted_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
interface TenantConfigurationAttributes {
|
||||
business_context?: string | null;
|
||||
default_provider?: LighthouseV2ProviderType | "";
|
||||
default_models?: Record<string, string> | null;
|
||||
}
|
||||
|
||||
interface SupportedProviderAttributes {
|
||||
name: string;
|
||||
}
|
||||
@@ -122,6 +115,7 @@ export function mapLighthouseV2Configuration(
|
||||
providerType: resource.attributes.provider_type,
|
||||
baseUrl: resource.attributes.base_url,
|
||||
defaultModel: resource.attributes.default_model ?? null,
|
||||
businessContext: resource.attributes.business_context ?? "",
|
||||
connected: resource.attributes.connected,
|
||||
connectionLastCheckedAt: resource.attributes.connection_last_checked_at,
|
||||
insertedAt: resource.attributes.inserted_at,
|
||||
@@ -129,17 +123,6 @@ export function mapLighthouseV2Configuration(
|
||||
};
|
||||
}
|
||||
|
||||
export function mapLighthouseV2TenantConfiguration(
|
||||
resource: JsonApiResource<TenantConfigurationAttributes>,
|
||||
): LighthouseV2TenantConfiguration {
|
||||
return {
|
||||
id: resource.id,
|
||||
businessContext: resource.attributes.business_context ?? "",
|
||||
defaultProvider: resource.attributes.default_provider ?? "",
|
||||
defaultModels: resource.attributes.default_models ?? {},
|
||||
};
|
||||
}
|
||||
|
||||
export function mapLighthouseV2Provider(
|
||||
resource: JsonApiResource<SupportedProviderAttributes>,
|
||||
): LighthouseV2SupportedProvider {
|
||||
@@ -228,21 +211,8 @@ export function buildLighthouseV2ConfigurationUpdatePayload(
|
||||
attributes: filterUndefinedAttributes({
|
||||
credentials: input.credentials,
|
||||
base_url: input.baseUrl,
|
||||
}),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function buildLighthouseV2TenantConfigurationUpdatePayload(
|
||||
input: LighthouseV2TenantConfigurationUpdateInput,
|
||||
) {
|
||||
return {
|
||||
data: {
|
||||
type: "lighthouse-configurations",
|
||||
attributes: filterUndefinedAttributes({
|
||||
default_model: input.defaultModel,
|
||||
business_context: input.businessContext,
|
||||
default_provider: input.defaultProvider,
|
||||
default_models: input.defaultModels,
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -26,8 +26,8 @@ vi.mock("@/lib/helper", () => ({
|
||||
|
||||
import {
|
||||
createLighthouseV2Session,
|
||||
updateLighthouseV2Configuration,
|
||||
updateLighthouseV2Session,
|
||||
updateLighthouseV2TenantConfiguration,
|
||||
} from "./lighthouse-v2";
|
||||
|
||||
function sessionResponse(id = "session-1") {
|
||||
@@ -49,19 +49,21 @@ function sessionResponse(id = "session-1") {
|
||||
);
|
||||
}
|
||||
|
||||
function tenantConfigurationResponse() {
|
||||
function configurationResponse(id = "config-1") {
|
||||
return Response.json(
|
||||
{
|
||||
data: {
|
||||
id: "tenant-config-1",
|
||||
type: "lighthouse-configurations",
|
||||
id,
|
||||
type: "lighthouse-ai-configurations",
|
||||
attributes: {
|
||||
provider_type: "bedrock",
|
||||
base_url: null,
|
||||
default_model: "anthropic.claude-4",
|
||||
business_context: "Production tenant",
|
||||
default_provider: "bedrock",
|
||||
default_models: {
|
||||
openai: "gpt-5.1",
|
||||
bedrock: "anthropic.claude-4",
|
||||
},
|
||||
connected: true,
|
||||
connection_last_checked_at: "2026-06-25T10:00:00Z",
|
||||
inserted_at: "2026-06-25T09:00:00Z",
|
||||
updated_at: "2026-06-25T10:00:00Z",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -99,40 +101,35 @@ describe("Lighthouse v2 session write actions", () => {
|
||||
expect(revalidatePathMock).toHaveBeenCalledWith("/lighthouse");
|
||||
});
|
||||
|
||||
it("updates tenant model preferences without revalidating the active chat route", async () => {
|
||||
it("persists the chosen model as the provider default without remounting the active chat", async () => {
|
||||
// Given
|
||||
const fetchMock = vi.fn().mockResolvedValue(tenantConfigurationResponse());
|
||||
const fetchMock = vi.fn().mockResolvedValue(configurationResponse());
|
||||
vi.stubGlobal("fetch", fetchMock);
|
||||
|
||||
// When
|
||||
const result = await updateLighthouseV2TenantConfiguration({
|
||||
defaultProvider: "bedrock",
|
||||
defaultModels: {
|
||||
openai: "gpt-5.1",
|
||||
bedrock: "anthropic.claude-4",
|
||||
},
|
||||
const result = await updateLighthouseV2Configuration("config-1", {
|
||||
defaultModel: "anthropic.claude-4",
|
||||
});
|
||||
|
||||
// Then
|
||||
expect("data" in result && result.data.defaultProvider).toBe("bedrock");
|
||||
expect("data" in result && result.data.defaultModel).toBe(
|
||||
"anthropic.claude-4",
|
||||
);
|
||||
expect(fetchMock).toHaveBeenCalledWith(
|
||||
new URL("https://api.example.com/api/v1/lighthouse/configuration"),
|
||||
new URL("https://api.example.com/api/v1/lighthouse/config/config-1"),
|
||||
expect.objectContaining({
|
||||
method: "PATCH",
|
||||
body: JSON.stringify({
|
||||
data: {
|
||||
type: "lighthouse-configurations",
|
||||
attributes: {
|
||||
default_provider: "bedrock",
|
||||
default_models: {
|
||||
openai: "gpt-5.1",
|
||||
bedrock: "anthropic.claude-4",
|
||||
},
|
||||
},
|
||||
type: "lighthouse-ai-configurations",
|
||||
id: "config-1",
|
||||
attributes: { default_model: "anthropic.claude-4" },
|
||||
},
|
||||
}),
|
||||
}),
|
||||
);
|
||||
expect(revalidatePathMock).not.toHaveBeenCalled();
|
||||
// Revalidating the active force-dynamic chat route would remount it and kill
|
||||
// the live EventSource — only the settings route may be revalidated.
|
||||
expect(revalidatePathMock).not.toHaveBeenCalledWith("/lighthouse");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,8 +13,6 @@ import type {
|
||||
LighthouseV2SupportedModel,
|
||||
LighthouseV2SupportedProvider,
|
||||
LighthouseV2Task,
|
||||
LighthouseV2TenantConfiguration,
|
||||
LighthouseV2TenantConfigurationUpdateInput,
|
||||
} from "@/app/(prowler)/lighthouse/_types";
|
||||
import { apiBaseUrl, getAuthHeaders } from "@/lib/helper";
|
||||
import { handleApiError, handleApiResponse } from "@/lib/server-actions-helper";
|
||||
@@ -26,7 +24,6 @@ import {
|
||||
buildLighthouseV2MessagePayload,
|
||||
buildLighthouseV2SessionCreatePayload,
|
||||
buildLighthouseV2SessionUpdatePayload,
|
||||
buildLighthouseV2TenantConfigurationUpdatePayload,
|
||||
getJsonApiArray,
|
||||
type JsonApiDocument,
|
||||
mapLighthouseV2Configuration,
|
||||
@@ -35,7 +32,6 @@ import {
|
||||
mapLighthouseV2Provider,
|
||||
mapLighthouseV2Session,
|
||||
mapLighthouseV2Task,
|
||||
mapLighthouseV2TenantConfiguration,
|
||||
validateLighthouseV2ConfigurationInput,
|
||||
} from "./lighthouse-v2.adapter";
|
||||
|
||||
@@ -106,31 +102,6 @@ export async function deleteLighthouseV2Configuration(
|
||||
);
|
||||
}
|
||||
|
||||
export async function getLighthouseV2TenantConfiguration(): Promise<
|
||||
LighthouseV2ActionResult<LighthouseV2TenantConfiguration>
|
||||
> {
|
||||
return getSingle(
|
||||
"/lighthouse/configuration",
|
||||
mapLighthouseV2TenantConfiguration,
|
||||
);
|
||||
}
|
||||
|
||||
export async function updateLighthouseV2TenantConfiguration(
|
||||
input: LighthouseV2TenantConfigurationUpdateInput,
|
||||
): Promise<LighthouseV2ActionResult<LighthouseV2TenantConfiguration>> {
|
||||
return mutateSingle(
|
||||
"/lighthouse/configuration",
|
||||
{
|
||||
method: "PATCH",
|
||||
body: JSON.stringify(
|
||||
buildLighthouseV2TenantConfigurationUpdatePayload(input),
|
||||
),
|
||||
},
|
||||
mapLighthouseV2TenantConfiguration,
|
||||
"",
|
||||
);
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
||||
@@ -62,13 +62,13 @@ const {
|
||||
createSessionMock,
|
||||
getMessagesMock,
|
||||
sendMessageMock,
|
||||
updateTenantConfigurationMock,
|
||||
updateConfigurationMock,
|
||||
} = vi.hoisted(() => ({
|
||||
cancelRunMock: vi.fn(),
|
||||
createSessionMock: vi.fn(),
|
||||
getMessagesMock: vi.fn(),
|
||||
sendMessageMock: vi.fn(),
|
||||
updateTenantConfigurationMock: vi.fn(),
|
||||
updateConfigurationMock: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@/app/(prowler)/lighthouse/_actions", () => ({
|
||||
@@ -76,7 +76,7 @@ vi.mock("@/app/(prowler)/lighthouse/_actions", () => ({
|
||||
createLighthouseV2Session: createSessionMock,
|
||||
getLighthouseV2Messages: getMessagesMock,
|
||||
sendLighthouseV2Message: sendMessageMock,
|
||||
updateLighthouseV2TenantConfiguration: updateTenantConfigurationMock,
|
||||
updateLighthouseV2Configuration: updateConfigurationMock,
|
||||
}));
|
||||
|
||||
// Streamdown pulls in shiki/wasm syntax highlighting that doesn't run under
|
||||
@@ -92,6 +92,7 @@ const configurations: LighthouseV2Configuration[] = [
|
||||
providerType: "openai",
|
||||
baseUrl: null,
|
||||
defaultModel: "gpt-5.1",
|
||||
businessContext: "Production account",
|
||||
connected: true,
|
||||
connectionLastCheckedAt: "2026-06-24T10:00:00Z",
|
||||
insertedAt: "2026-06-24T09:00:00Z",
|
||||
@@ -102,6 +103,7 @@ const configurations: LighthouseV2Configuration[] = [
|
||||
providerType: "bedrock",
|
||||
baseUrl: null,
|
||||
defaultModel: "anthropic.claude-4",
|
||||
businessContext: "Production account",
|
||||
connected: true,
|
||||
connectionLastCheckedAt: "2026-06-23T10:00:00Z",
|
||||
insertedAt: "2026-06-23T09:00:00Z",
|
||||
@@ -115,16 +117,6 @@ const modelsByProvider = {
|
||||
"openai-compatible": [model("llama-3.3")],
|
||||
};
|
||||
|
||||
const tenantConfiguration = {
|
||||
id: "tenant-config-1",
|
||||
businessContext: "Production account",
|
||||
defaultProvider: "openai",
|
||||
defaultModels: {
|
||||
openai: "gpt-5.1",
|
||||
bedrock: "anthropic.claude-4",
|
||||
},
|
||||
} satisfies Parameters<typeof LighthouseV2ChatPage>[0]["tenantConfiguration"];
|
||||
|
||||
describe("LighthouseV2ChatPage", () => {
|
||||
beforeEach(() => {
|
||||
vi.stubGlobal(
|
||||
@@ -143,7 +135,7 @@ describe("LighthouseV2ChatPage", () => {
|
||||
createSessionMock.mockReset();
|
||||
getMessagesMock.mockReset();
|
||||
sendMessageMock.mockReset();
|
||||
updateTenantConfigurationMock.mockReset();
|
||||
updateConfigurationMock.mockReset();
|
||||
// The mock never fires "open": the client must POST the message without
|
||||
// waiting for it (the backend sends no bytes until the worker emits, which
|
||||
// only happens after the POST). This is the regression guard for the
|
||||
@@ -170,9 +162,7 @@ describe("LighthouseV2ChatPage", () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
updateTenantConfigurationMock.mockResolvedValue({
|
||||
data: tenantConfiguration,
|
||||
});
|
||||
updateConfigurationMock.mockResolvedValue({ data: configurations[1] });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -229,16 +219,11 @@ describe("LighthouseV2ChatPage", () => {
|
||||
).not.toHaveClass("border-t");
|
||||
});
|
||||
|
||||
it("sends messages with the tenant default provider and its saved model", async () => {
|
||||
// Given
|
||||
it("opens the highest-priority connected provider with its remembered model", async () => {
|
||||
// Given: both OpenAI and Bedrock are connected; OpenAI outranks Bedrock
|
||||
const user = userEvent.setup();
|
||||
const replaceStateSpy = vi.spyOn(window.history, "replaceState");
|
||||
renderPage({
|
||||
tenantConfiguration: {
|
||||
...tenantConfiguration,
|
||||
defaultProvider: "bedrock",
|
||||
},
|
||||
});
|
||||
renderPage();
|
||||
|
||||
// When
|
||||
await user.type(
|
||||
@@ -246,13 +231,14 @@ describe("LighthouseV2ChatPage", () => {
|
||||
["Summarize findings", "{Enter}"].join(""),
|
||||
);
|
||||
|
||||
// Then: the message is sent even though EventSource never fires "open"
|
||||
// Then: the message is sent with OpenAI and its remembered defaultModel,
|
||||
// even though EventSource never fires "open"
|
||||
await waitFor(() =>
|
||||
expect(sendMessageMock).toHaveBeenCalledWith({
|
||||
sessionId: "session-1",
|
||||
text: "Summarize findings",
|
||||
provider: "bedrock",
|
||||
model: "anthropic.claude-4",
|
||||
provider: "openai",
|
||||
model: "gpt-5.1",
|
||||
}),
|
||||
);
|
||||
expect(createSessionMock).toHaveBeenCalledWith("Summarize findings");
|
||||
@@ -270,17 +256,14 @@ describe("LighthouseV2ChatPage", () => {
|
||||
replaceStateSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("falls back to the first connected provider when tenant defaults are invalid", async () => {
|
||||
// Given
|
||||
it("opens a lower-priority provider when the higher-priority one is disconnected", async () => {
|
||||
// Given: only Bedrock is connected
|
||||
const user = userEvent.setup();
|
||||
renderPage({
|
||||
tenantConfiguration: {
|
||||
...tenantConfiguration,
|
||||
defaultProvider: "bedrock",
|
||||
defaultModels: {
|
||||
bedrock: "missing-model",
|
||||
},
|
||||
},
|
||||
configurations: [
|
||||
{ ...configurations[0], connected: false },
|
||||
configurations[1],
|
||||
],
|
||||
});
|
||||
|
||||
// When
|
||||
@@ -290,6 +273,33 @@ describe("LighthouseV2ChatPage", () => {
|
||||
);
|
||||
|
||||
// Then
|
||||
await waitFor(() =>
|
||||
expect(sendMessageMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
provider: "bedrock",
|
||||
model: "anthropic.claude-4",
|
||||
}),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it("falls back to the first supported model when the remembered model is unsupported", async () => {
|
||||
// Given: OpenAI's remembered default model is no longer offered
|
||||
const user = userEvent.setup();
|
||||
renderPage({
|
||||
configurations: [
|
||||
{ ...configurations[0], defaultModel: "missing-model" },
|
||||
configurations[1],
|
||||
],
|
||||
});
|
||||
|
||||
// When
|
||||
await user.type(
|
||||
screen.getByRole("textbox", { name: "Message" }),
|
||||
["Summarize findings", "{Enter}"].join(""),
|
||||
);
|
||||
|
||||
// Then: OpenAI stays selected (highest priority) but on its first model
|
||||
await waitFor(() =>
|
||||
expect(sendMessageMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
@@ -300,7 +310,7 @@ describe("LighthouseV2ChatPage", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("persists the selected chat model without deleting other provider defaults", async () => {
|
||||
it("persists the selected chat model as that provider's default", async () => {
|
||||
// Given
|
||||
const user = userEvent.setup();
|
||||
renderPage();
|
||||
@@ -311,14 +321,10 @@ describe("LighthouseV2ChatPage", () => {
|
||||
await screen.findByRole("option", { name: "anthropic.claude-4" }),
|
||||
);
|
||||
|
||||
// Then
|
||||
// Then: only the chosen provider's config is updated, by id
|
||||
await waitFor(() =>
|
||||
expect(updateTenantConfigurationMock).toHaveBeenCalledWith({
|
||||
defaultProvider: "bedrock",
|
||||
defaultModels: {
|
||||
openai: "gpt-5.1",
|
||||
bedrock: "anthropic.claude-4",
|
||||
},
|
||||
expect(updateConfigurationMock).toHaveBeenCalledWith("config-bedrock", {
|
||||
defaultModel: "anthropic.claude-4",
|
||||
}),
|
||||
);
|
||||
});
|
||||
@@ -326,7 +332,7 @@ describe("LighthouseV2ChatPage", () => {
|
||||
it("keeps the chosen model applied and surfaces the backend reason when saving the default fails", async () => {
|
||||
// Given
|
||||
const user = userEvent.setup();
|
||||
updateTenantConfigurationMock.mockResolvedValue({
|
||||
updateConfigurationMock.mockResolvedValue({
|
||||
error: "Invalid model 'anthropic.claude-4' for provider 'bedrock'.",
|
||||
status: 400,
|
||||
});
|
||||
@@ -470,7 +476,6 @@ function renderPage(props?: RenderPageProps) {
|
||||
const componentProps = {
|
||||
configurations: props?.configurations ?? configurations,
|
||||
modelsByProvider: props?.modelsByProvider ?? modelsByProvider,
|
||||
tenantConfiguration: props?.tenantConfiguration ?? tenantConfiguration,
|
||||
initialSessionId: props?.initialSessionId,
|
||||
initialMessages: props?.initialMessages ?? [],
|
||||
initialPrompt: props?.initialPrompt,
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
createLighthouseV2Session,
|
||||
getLighthouseV2Messages,
|
||||
sendLighthouseV2Message,
|
||||
updateLighthouseV2TenantConfiguration,
|
||||
updateLighthouseV2Configuration,
|
||||
} from "@/app/(prowler)/lighthouse/_actions";
|
||||
import {
|
||||
Conversation,
|
||||
@@ -35,13 +35,13 @@ import {
|
||||
import { parseStreamEvent } from "@/app/(prowler)/lighthouse/_lib/stream-event-parser";
|
||||
import { buildLighthouseV2StreamUrl } from "@/app/(prowler)/lighthouse/_lib/stream-url";
|
||||
import {
|
||||
LIGHTHOUSE_V2_PROVIDER_TYPE,
|
||||
LIGHTHOUSE_V2_SSE_EVENT,
|
||||
type LighthouseV2Configuration,
|
||||
type LighthouseV2Message,
|
||||
type LighthouseV2ProviderType,
|
||||
type LighthouseV2SSEEvent,
|
||||
type LighthouseV2SupportedModel,
|
||||
type LighthouseV2TenantConfiguration,
|
||||
} from "@/app/(prowler)/lighthouse/_types";
|
||||
import { Card } from "@/components/shadcn";
|
||||
import {
|
||||
@@ -61,7 +61,6 @@ interface LighthouseV2ChatPageProps {
|
||||
LighthouseV2ProviderType,
|
||||
LighthouseV2SupportedModel[]
|
||||
>;
|
||||
tenantConfiguration?: LighthouseV2TenantConfiguration;
|
||||
initialSessionId?: string;
|
||||
initialMessages: LighthouseV2Message[];
|
||||
initialActiveTaskId?: string | null;
|
||||
@@ -72,7 +71,6 @@ interface LighthouseV2ChatPageProps {
|
||||
export function LighthouseV2ChatPage({
|
||||
configurations,
|
||||
modelsByProvider,
|
||||
tenantConfiguration,
|
||||
initialSessionId,
|
||||
initialMessages,
|
||||
initialActiveTaskId,
|
||||
@@ -87,13 +85,9 @@ export function LighthouseV2ChatPage({
|
||||
const initialModelSelection = resolveInitialModelSelection(
|
||||
connectedConfigurations,
|
||||
modelsByProvider,
|
||||
tenantConfiguration,
|
||||
);
|
||||
const [selectedModelSelection, setSelectedModelSelection] =
|
||||
useState<LighthouseV2ModelSelection | null>(initialModelSelection);
|
||||
const [tenantModelDefaults, setTenantModelDefaults] = useState<
|
||||
Record<string, string>
|
||||
>(tenantConfiguration?.defaultModels ?? {});
|
||||
const [modelPreferenceSaving, setModelPreferenceSaving] = useState(false);
|
||||
const [activeSessionId, setActiveSessionId] = useState<string | null>(
|
||||
initialSessionId ?? null,
|
||||
@@ -313,35 +307,27 @@ export function LighthouseV2ChatPage({
|
||||
selection: LighthouseV2ModelSelection,
|
||||
) => {
|
||||
// The selection drives the model used for the next message, so it stays
|
||||
// applied even if persisting it as the tenant default fails. Only the
|
||||
// saved-default mirror is rolled back on failure — reverting the active
|
||||
// selection would make a connected provider unusable when the save 4xxs.
|
||||
const previousDefaults = tenantModelDefaults;
|
||||
|
||||
// applied even if persisting it as the provider's default model fails —
|
||||
// reverting it would make a connected provider unusable when the save 4xxs.
|
||||
setSelectedModelSelection(selection);
|
||||
setFeedback(null);
|
||||
|
||||
const nextDefaults = {
|
||||
...tenantModelDefaults,
|
||||
[selection.providerType]: selection.modelId,
|
||||
};
|
||||
setTenantModelDefaults(nextDefaults);
|
||||
const configId = connectedConfigurations.find(
|
||||
(configuration) => configuration.providerType === selection.providerType,
|
||||
)?.id;
|
||||
if (!configId) return;
|
||||
|
||||
setModelPreferenceSaving(true);
|
||||
|
||||
const result = await updateLighthouseV2TenantConfiguration({
|
||||
defaultProvider: selection.providerType,
|
||||
defaultModels: nextDefaults,
|
||||
const result = await updateLighthouseV2Configuration(configId, {
|
||||
defaultModel: selection.modelId,
|
||||
});
|
||||
|
||||
setModelPreferenceSaving(false);
|
||||
|
||||
if ("error" in result) {
|
||||
setTenantModelDefaults(previousDefaults);
|
||||
setFeedback(result.error);
|
||||
return;
|
||||
}
|
||||
|
||||
setTenantModelDefaults(result.data.defaultModels);
|
||||
};
|
||||
|
||||
const handleSubmit = (event: SubmitEvent<HTMLFormElement>) => {
|
||||
@@ -421,7 +407,7 @@ export function LighthouseV2ChatPage({
|
||||
input,
|
||||
isStreaming: Boolean(streamState.activeTaskId),
|
||||
modelSelector: (
|
||||
<div className="min-w-0 flex-1 sm:max-w-80">
|
||||
<div className="min-w-0 flex-1 sm:max-w-48">
|
||||
<Combobox
|
||||
aria-label="Model"
|
||||
value={selectedModelValue}
|
||||
@@ -493,71 +479,47 @@ function replaceLighthouseV2SessionUrl(sessionId: string | null) {
|
||||
window.history.replaceState(window.history.state, "", url);
|
||||
}
|
||||
|
||||
// Fixed precedence used to pick which connected provider opens the chat. Any
|
||||
// provider outside this list keeps its relative order behind these.
|
||||
const LIGHTHOUSE_V2_PROVIDER_PRIORITY = [
|
||||
LIGHTHOUSE_V2_PROVIDER_TYPE.OPENAI,
|
||||
LIGHTHOUSE_V2_PROVIDER_TYPE.BEDROCK,
|
||||
LIGHTHOUSE_V2_PROVIDER_TYPE.OPENAI_COMPATIBLE,
|
||||
] as const;
|
||||
|
||||
function resolveInitialModelSelection(
|
||||
connectedConfigurations: LighthouseV2Configuration[],
|
||||
modelsByProvider: Record<
|
||||
LighthouseV2ProviderType,
|
||||
LighthouseV2SupportedModel[]
|
||||
>,
|
||||
tenantConfiguration?: LighthouseV2TenantConfiguration,
|
||||
): LighthouseV2ModelSelection | null {
|
||||
const tenantDefaultProvider = tenantConfiguration?.defaultProvider;
|
||||
const priorityIndex = (providerType: LighthouseV2ProviderType) => {
|
||||
const index = LIGHTHOUSE_V2_PROVIDER_PRIORITY.indexOf(providerType);
|
||||
return index === -1 ? LIGHTHOUSE_V2_PROVIDER_PRIORITY.length : index;
|
||||
};
|
||||
// Stable sort keeps providers outside the priority list in their original order.
|
||||
const orderedConfigurations = [...connectedConfigurations].sort(
|
||||
(a, b) => priorityIndex(a.providerType) - priorityIndex(b.providerType),
|
||||
);
|
||||
|
||||
if (tenantDefaultProvider) {
|
||||
const defaultModel =
|
||||
tenantConfiguration.defaultModels[tenantDefaultProvider];
|
||||
if (
|
||||
defaultModel &&
|
||||
hasConnectedModel(
|
||||
connectedConfigurations,
|
||||
modelsByProvider,
|
||||
tenantDefaultProvider,
|
||||
defaultModel,
|
||||
)
|
||||
) {
|
||||
return {
|
||||
providerType: tenantDefaultProvider,
|
||||
modelId: defaultModel,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
for (const configuration of connectedConfigurations) {
|
||||
for (const configuration of orderedConfigurations) {
|
||||
const providerModels = modelsByProvider[configuration.providerType] ?? [];
|
||||
const savedModel =
|
||||
tenantConfiguration?.defaultModels[configuration.providerType];
|
||||
const model =
|
||||
providerModels.find((candidate) => candidate.id === savedModel) ??
|
||||
providerModels[0];
|
||||
|
||||
if (model) {
|
||||
return {
|
||||
providerType: configuration.providerType,
|
||||
modelId: model.id,
|
||||
};
|
||||
}
|
||||
if (providerModels.length === 0) continue;
|
||||
// Prefer the provider's remembered model when it is still supported;
|
||||
// otherwise fall back to the first supported model.
|
||||
const rememberedModel = providerModels.find(
|
||||
(model) => model.id === configuration.defaultModel,
|
||||
);
|
||||
return {
|
||||
providerType: configuration.providerType,
|
||||
modelId: (rememberedModel ?? providerModels[0]).id,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function hasConnectedModel(
|
||||
connectedConfigurations: LighthouseV2Configuration[],
|
||||
modelsByProvider: Record<
|
||||
LighthouseV2ProviderType,
|
||||
LighthouseV2SupportedModel[]
|
||||
>,
|
||||
providerType: LighthouseV2ProviderType,
|
||||
modelId: string,
|
||||
) {
|
||||
return (
|
||||
connectedConfigurations.some(
|
||||
(configuration) => configuration.providerType === providerType,
|
||||
) &&
|
||||
(modelsByProvider[providerType] ?? []).some((model) => model.id === modelId)
|
||||
);
|
||||
}
|
||||
|
||||
function buildModelSelectorGroups(
|
||||
connectedConfigurations: LighthouseV2Configuration[],
|
||||
modelsByProvider: Record<
|
||||
|
||||
@@ -4,35 +4,43 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { LighthouseV2BusinessContextForm } from "./business-context-form";
|
||||
|
||||
const { updateTenantConfigurationMock } = vi.hoisted(() => ({
|
||||
updateTenantConfigurationMock: vi.fn(),
|
||||
const { updateConfigurationMock } = vi.hoisted(() => ({
|
||||
updateConfigurationMock: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@/app/(prowler)/lighthouse/_actions", () => ({
|
||||
updateLighthouseV2TenantConfiguration: updateTenantConfigurationMock,
|
||||
updateLighthouseV2Configuration: updateConfigurationMock,
|
||||
}));
|
||||
|
||||
function tenantConfig(businessContext: string) {
|
||||
function configuration(businessContext: string) {
|
||||
return {
|
||||
id: "tenant-config-1",
|
||||
id: "config-1",
|
||||
providerType: "bedrock" as const,
|
||||
baseUrl: null,
|
||||
defaultModel: null,
|
||||
businessContext,
|
||||
defaultProvider: "" as const,
|
||||
defaultModels: {},
|
||||
connected: true,
|
||||
connectionLastCheckedAt: null,
|
||||
insertedAt: "2026-06-25T09:00:00Z",
|
||||
updatedAt: "2026-06-25T10:00:00Z",
|
||||
};
|
||||
}
|
||||
|
||||
describe("LighthouseV2BusinessContextForm", () => {
|
||||
beforeEach(() => {
|
||||
updateTenantConfigurationMock.mockReset();
|
||||
updateTenantConfigurationMock.mockResolvedValue({
|
||||
data: tenantConfig("Production context"),
|
||||
updateConfigurationMock.mockReset();
|
||||
updateConfigurationMock.mockResolvedValue({
|
||||
data: configuration("Production context"),
|
||||
});
|
||||
});
|
||||
|
||||
it("seeds the textarea with the tenant business context and disables save until edited", () => {
|
||||
it("seeds the textarea with the business context and disables save until edited", () => {
|
||||
// Given / When
|
||||
render(
|
||||
<LighthouseV2BusinessContextForm initialBusinessContext="Production context" />,
|
||||
<LighthouseV2BusinessContextForm
|
||||
configurationId="config-1"
|
||||
initialBusinessContext="Production context"
|
||||
/>,
|
||||
);
|
||||
|
||||
// Then
|
||||
@@ -44,10 +52,15 @@ describe("LighthouseV2BusinessContextForm", () => {
|
||||
).toBeDisabled();
|
||||
});
|
||||
|
||||
it("saves the shared business context to the tenant configuration", async () => {
|
||||
it("saves the shared business context against the provider configuration", async () => {
|
||||
// Given
|
||||
const user = userEvent.setup();
|
||||
render(<LighthouseV2BusinessContextForm initialBusinessContext="" />);
|
||||
render(
|
||||
<LighthouseV2BusinessContextForm
|
||||
configurationId="config-1"
|
||||
initialBusinessContext=""
|
||||
/>,
|
||||
);
|
||||
|
||||
// When
|
||||
await user.type(
|
||||
@@ -60,7 +73,7 @@ describe("LighthouseV2BusinessContextForm", () => {
|
||||
|
||||
// Then
|
||||
await waitFor(() =>
|
||||
expect(updateTenantConfigurationMock).toHaveBeenCalledWith({
|
||||
expect(updateConfigurationMock).toHaveBeenCalledWith("config-1", {
|
||||
businessContext: "Production context",
|
||||
}),
|
||||
);
|
||||
@@ -69,7 +82,12 @@ describe("LighthouseV2BusinessContextForm", () => {
|
||||
it("shows the counter and blocks saving over the character limit", async () => {
|
||||
// Given
|
||||
const user = userEvent.setup();
|
||||
render(<LighthouseV2BusinessContextForm initialBusinessContext="" />);
|
||||
render(
|
||||
<LighthouseV2BusinessContextForm
|
||||
configurationId="config-1"
|
||||
initialBusinessContext=""
|
||||
/>,
|
||||
);
|
||||
|
||||
// When: paste in one event instead of 1001 keystrokes
|
||||
await user.click(
|
||||
@@ -85,17 +103,22 @@ describe("LighthouseV2BusinessContextForm", () => {
|
||||
expect(
|
||||
screen.getByRole("button", { name: "Save business context" }),
|
||||
).toBeDisabled();
|
||||
expect(updateTenantConfigurationMock).not.toHaveBeenCalled();
|
||||
expect(updateConfigurationMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("surfaces the backend reason when the save fails", async () => {
|
||||
// Given
|
||||
const user = userEvent.setup();
|
||||
updateTenantConfigurationMock.mockResolvedValue({
|
||||
updateConfigurationMock.mockResolvedValue({
|
||||
error: "No active configuration found for 'bedrock'.",
|
||||
status: 400,
|
||||
});
|
||||
render(<LighthouseV2BusinessContextForm initialBusinessContext="" />);
|
||||
render(
|
||||
<LighthouseV2BusinessContextForm
|
||||
configurationId="config-1"
|
||||
initialBusinessContext=""
|
||||
/>,
|
||||
);
|
||||
|
||||
// When
|
||||
await user.type(
|
||||
|
||||
@@ -3,18 +3,20 @@
|
||||
import { Bot, Loader2, Save } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
import { updateLighthouseV2TenantConfiguration } from "@/app/(prowler)/lighthouse/_actions";
|
||||
import { updateLighthouseV2Configuration } from "@/app/(prowler)/lighthouse/_actions";
|
||||
import { BUSINESS_CONTEXT_LIMIT } from "@/app/(prowler)/lighthouse/_lib/config";
|
||||
import { Button } from "@/components/shadcn/button/button";
|
||||
import { Field, FieldError, FieldLabel } from "@/components/shadcn/field/field";
|
||||
import { Textarea } from "@/components/shadcn/textarea/textarea";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
// Tenant-wide business context. It is shared across every provider and chat, so
|
||||
// it is edited once here rather than per provider configuration.
|
||||
// Shared business context. The backend syncs it across every provider config, so
|
||||
// it is edited once here against any single configuration rather than per provider.
|
||||
export function LighthouseV2BusinessContextForm({
|
||||
configurationId,
|
||||
initialBusinessContext,
|
||||
}: {
|
||||
configurationId: string;
|
||||
initialBusinessContext: string;
|
||||
}) {
|
||||
const [businessContext, setBusinessContext] = useState(
|
||||
@@ -33,7 +35,7 @@ export function LighthouseV2BusinessContextForm({
|
||||
|
||||
setSaving(true);
|
||||
setError(null);
|
||||
const result = await updateLighthouseV2TenantConfiguration({
|
||||
const result = await updateLighthouseV2Configuration(configurationId, {
|
||||
businessContext,
|
||||
});
|
||||
setSaving(false);
|
||||
|
||||
@@ -14,14 +14,12 @@ const {
|
||||
deleteConfigurationMock,
|
||||
testConnectionMock,
|
||||
updateConfigurationMock,
|
||||
updateTenantConfigurationMock,
|
||||
toastMock,
|
||||
} = vi.hoisted(() => ({
|
||||
createConfigurationMock: vi.fn(),
|
||||
deleteConfigurationMock: vi.fn(),
|
||||
testConnectionMock: vi.fn(),
|
||||
updateConfigurationMock: vi.fn(),
|
||||
updateTenantConfigurationMock: vi.fn(),
|
||||
toastMock: vi.fn(),
|
||||
}));
|
||||
|
||||
@@ -40,7 +38,6 @@ vi.mock("@/app/(prowler)/lighthouse/_actions", () => ({
|
||||
deleteLighthouseV2Configuration: deleteConfigurationMock,
|
||||
testLighthouseV2ConfigurationConnection: testConnectionMock,
|
||||
updateLighthouseV2Configuration: updateConfigurationMock,
|
||||
updateLighthouseV2TenantConfiguration: updateTenantConfigurationMock,
|
||||
}));
|
||||
|
||||
const providers: LighthouseV2SupportedProvider[] = [
|
||||
@@ -55,6 +52,7 @@ const configurations: LighthouseV2Configuration[] = [
|
||||
providerType: "openai",
|
||||
baseUrl: null,
|
||||
defaultModel: "gpt-5.1",
|
||||
businessContext: "Production context",
|
||||
connected: true,
|
||||
connectionLastCheckedAt: "2026-06-24T10:00:00Z",
|
||||
insertedAt: "2026-06-24T09:00:00Z",
|
||||
@@ -65,6 +63,7 @@ const configurations: LighthouseV2Configuration[] = [
|
||||
providerType: "bedrock",
|
||||
baseUrl: null,
|
||||
defaultModel: "anthropic.claude-4",
|
||||
businessContext: "Production context",
|
||||
connected: false,
|
||||
connectionLastCheckedAt: "2026-06-23T10:00:00Z",
|
||||
insertedAt: "2026-06-23T09:00:00Z",
|
||||
@@ -78,16 +77,7 @@ describe("LighthouseV2ConfigPage", () => {
|
||||
deleteConfigurationMock.mockReset();
|
||||
testConnectionMock.mockReset();
|
||||
updateConfigurationMock.mockReset();
|
||||
updateTenantConfigurationMock.mockReset();
|
||||
toastMock.mockReset();
|
||||
updateTenantConfigurationMock.mockResolvedValue({
|
||||
data: {
|
||||
id: "tenant-config-1",
|
||||
businessContext: "Updated business context",
|
||||
defaultProvider: "",
|
||||
defaultModels: {},
|
||||
},
|
||||
});
|
||||
|
||||
createConfigurationMock.mockResolvedValue({ data: configurations[0] });
|
||||
deleteConfigurationMock.mockResolvedValue({ data: true });
|
||||
@@ -167,6 +157,17 @@ describe("LighthouseV2ConfigPage", () => {
|
||||
).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("hides the business context editor until a provider is configured", () => {
|
||||
// Given / When: no configurations exist yet
|
||||
renderPage({ configurations: [] });
|
||||
|
||||
// Then: the editor is replaced by a hint and the textarea is absent
|
||||
expect(
|
||||
screen.queryByRole("textbox", { name: /Business context/i }),
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.getByText(/Configure a provider first/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("updates an existing configuration without sending blank credentials or model defaults", async () => {
|
||||
// Given
|
||||
const user = userEvent.setup();
|
||||
@@ -199,6 +200,7 @@ describe("LighthouseV2ConfigPage", () => {
|
||||
providerType: "openai-compatible",
|
||||
baseUrl: "https://llm.example.com/v1",
|
||||
defaultModel: "llama-3.3",
|
||||
businessContext: "",
|
||||
connected: null,
|
||||
connectionLastCheckedAt: null,
|
||||
insertedAt: "2026-06-24T10:00:00Z",
|
||||
@@ -328,7 +330,6 @@ function renderPage(
|
||||
<LighthouseV2ConfigPage
|
||||
configurations={props?.configurations ?? configurations}
|
||||
providers={props?.providers ?? providers}
|
||||
tenantConfiguration={props?.tenantConfiguration}
|
||||
error={props?.error}
|
||||
/>,
|
||||
);
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
type LighthouseV2Configuration,
|
||||
type LighthouseV2ProviderType,
|
||||
type LighthouseV2SupportedProvider,
|
||||
type LighthouseV2TenantConfiguration,
|
||||
} from "@/app/(prowler)/lighthouse/_types";
|
||||
import { Card } from "@/components/shadcn/card/card";
|
||||
import { useToast } from "@/components/ui";
|
||||
@@ -24,14 +23,12 @@ import { LighthouseV2ProviderRail } from "./provider-rail";
|
||||
interface LighthouseV2ConfigPageProps {
|
||||
configurations: LighthouseV2Configuration[];
|
||||
providers: LighthouseV2SupportedProvider[];
|
||||
tenantConfiguration?: LighthouseV2TenantConfiguration;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export function LighthouseV2ConfigPage({
|
||||
configurations,
|
||||
providers,
|
||||
tenantConfiguration,
|
||||
error,
|
||||
}: LighthouseV2ConfigPageProps) {
|
||||
const { toast } = useToast();
|
||||
@@ -61,6 +58,10 @@ export function LighthouseV2ConfigPage({
|
||||
}
|
||||
});
|
||||
|
||||
// Business context is shared across every provider (the backend syncs it on
|
||||
// update), so it is edited once against any single configuration.
|
||||
const businessContextConfig = localConfigurations[0];
|
||||
|
||||
const selectedConfig = localConfigurations.find(
|
||||
(config) => config.providerType === selectedProvider,
|
||||
);
|
||||
@@ -131,9 +132,19 @@ export function LighthouseV2ConfigPage({
|
||||
aria-label="Lighthouse AI settings"
|
||||
className="w-full gap-0 overflow-hidden"
|
||||
>
|
||||
<LighthouseV2BusinessContextForm
|
||||
initialBusinessContext={tenantConfiguration?.businessContext ?? ""}
|
||||
/>
|
||||
{businessContextConfig ? (
|
||||
<LighthouseV2BusinessContextForm
|
||||
configurationId={businessContextConfig.id}
|
||||
initialBusinessContext={businessContextConfig.businessContext}
|
||||
/>
|
||||
) : (
|
||||
<section
|
||||
data-slot="lighthouse-v2-business-context-empty"
|
||||
className="border-border-neutral-secondary text-text-neutral-secondary border-b px-4 py-4 text-sm md:px-5"
|
||||
>
|
||||
Configure a provider first to add shared business context.
|
||||
</section>
|
||||
)}
|
||||
|
||||
<div className="grid min-h-0 flex-1 gap-0 xl:grid-cols-[320px_auto_minmax(0,1fr)]">
|
||||
<div className="min-w-0 p-4 md:p-5">
|
||||
|
||||
@@ -37,6 +37,7 @@ export interface LighthouseV2Configuration {
|
||||
providerType: LighthouseV2ProviderType;
|
||||
baseUrl: string | null;
|
||||
defaultModel: string | null;
|
||||
businessContext: string;
|
||||
connected: boolean | null;
|
||||
connectionLastCheckedAt: string | null;
|
||||
insertedAt: string;
|
||||
@@ -52,19 +53,8 @@ export interface LighthouseV2ConfigurationInput {
|
||||
export interface LighthouseV2ConfigurationUpdateInput {
|
||||
credentials?: LighthouseV2Credentials;
|
||||
baseUrl?: string | null;
|
||||
}
|
||||
|
||||
export interface LighthouseV2TenantConfiguration {
|
||||
id: string;
|
||||
businessContext: string;
|
||||
defaultProvider: LighthouseV2ProviderType | "";
|
||||
defaultModels: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface LighthouseV2TenantConfigurationUpdateInput {
|
||||
defaultModel?: string | null;
|
||||
businessContext?: string;
|
||||
defaultProvider?: LighthouseV2ProviderType | "";
|
||||
defaultModels?: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface LighthouseV2SupportedProvider {
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
getLighthouseV2Messages,
|
||||
getLighthouseV2Session,
|
||||
getLighthouseV2SupportedModels,
|
||||
getLighthouseV2TenantConfiguration,
|
||||
} from "@/app/(prowler)/lighthouse/_actions";
|
||||
import { LighthouseV2ChatPage } from "@/app/(prowler)/lighthouse/_components/chat";
|
||||
import { LighthouseV2NavigationModeSync } from "@/app/(prowler)/lighthouse/_components/navigation";
|
||||
@@ -37,9 +36,7 @@ export default async function AIChatbot({
|
||||
typeof params.session === "string" ? params.session : undefined;
|
||||
|
||||
if (isCloud()) {
|
||||
const [configurationsResult, tenantConfigurationResult] = await Promise.all(
|
||||
[getLighthouseV2Configurations(), getLighthouseV2TenantConfiguration()],
|
||||
);
|
||||
const configurationsResult = await getLighthouseV2Configurations();
|
||||
const configurations =
|
||||
"data" in configurationsResult ? configurationsResult.data : [];
|
||||
const connectedConfigurations = configurations.filter(
|
||||
@@ -92,11 +89,6 @@ export default async function AIChatbot({
|
||||
key={chatRouteKey}
|
||||
configurations={configurations}
|
||||
modelsByProvider={modelsByProvider}
|
||||
tenantConfiguration={
|
||||
"data" in tenantConfigurationResult
|
||||
? tenantConfigurationResult.data
|
||||
: undefined
|
||||
}
|
||||
initialSessionId={activeSessionId}
|
||||
initialMessages={
|
||||
"data" in initialMessages ? initialMessages.data : []
|
||||
|
||||
@@ -3,7 +3,6 @@ import { Spacer } from "@heroui/spacer";
|
||||
import {
|
||||
getLighthouseV2Configurations,
|
||||
getLighthouseV2SupportedProviders,
|
||||
getLighthouseV2TenantConfiguration,
|
||||
} from "@/app/(prowler)/lighthouse/_actions";
|
||||
import { LighthouseV2ConfigPage } from "@/app/(prowler)/lighthouse/_components/config";
|
||||
import {
|
||||
@@ -17,12 +16,10 @@ export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function LighthouseSettingsPage() {
|
||||
if (isCloud()) {
|
||||
const [configurationsResult, providersResult, tenantConfigurationResult] =
|
||||
await Promise.all([
|
||||
getLighthouseV2Configurations(),
|
||||
getLighthouseV2SupportedProviders(),
|
||||
getLighthouseV2TenantConfiguration(),
|
||||
]);
|
||||
const [configurationsResult, providersResult] = await Promise.all([
|
||||
getLighthouseV2Configurations(),
|
||||
getLighthouseV2SupportedProviders(),
|
||||
]);
|
||||
|
||||
const providers = "data" in providersResult ? providersResult.data : [];
|
||||
const error =
|
||||
@@ -39,11 +36,6 @@ export default async function LighthouseSettingsPage() {
|
||||
"data" in configurationsResult ? configurationsResult.data : []
|
||||
}
|
||||
providers={providers}
|
||||
tenantConfiguration={
|
||||
"data" in tenantConfigurationResult
|
||||
? tenantConfigurationResult.data
|
||||
: undefined
|
||||
}
|
||||
error={error}
|
||||
/>
|
||||
</ContentLayout>
|
||||
|
||||
Reference in New Issue
Block a user