diff --git a/ui/components/lighthouse-v2/chat/lighthouse-v2-chat-page.test.tsx b/ui/components/lighthouse-v2/chat/lighthouse-v2-chat-page.test.tsx new file mode 100644 index 0000000000..e62b01ca6b --- /dev/null +++ b/ui/components/lighthouse-v2/chat/lighthouse-v2-chat-page.test.tsx @@ -0,0 +1,176 @@ +import { render, screen, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import type { + LighthouseV2Configuration, + LighthouseV2SupportedModel, +} from "@/types/lighthouse-v2"; + +import { LighthouseV2ChatPage } from "./lighthouse-v2-chat-page"; + +const { + cancelRunMock, + createSessionMock, + getMessagesMock, + pushMock, + sendMessageMock, +} = vi.hoisted(() => ({ + cancelRunMock: vi.fn(), + createSessionMock: vi.fn(), + getMessagesMock: vi.fn(), + pushMock: vi.fn(), + sendMessageMock: vi.fn(), +})); + +vi.mock("next/navigation", () => ({ + useRouter: () => ({ + push: pushMock, + }), +})); + +vi.mock("@/actions/lighthouse-v2/lighthouse-v2", () => ({ + cancelLighthouseV2Run: cancelRunMock, + createLighthouseV2Session: createSessionMock, + getLighthouseV2Messages: getMessagesMock, + sendLighthouseV2Message: sendMessageMock, +})); + +const configurations: LighthouseV2Configuration[] = [ + { + id: "config-openai", + 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", + updatedAt: "2026-06-24T10:00:00Z", + }, + { + id: "config-bedrock", + providerType: "bedrock", + baseUrl: null, + defaultModel: "anthropic.claude-4", + businessContext: "AWS landing zone", + connected: true, + connectionLastCheckedAt: "2026-06-23T10:00:00Z", + insertedAt: "2026-06-23T09:00:00Z", + updatedAt: "2026-06-23T10:00:00Z", + }, +]; + +const modelsByProvider = { + openai: [model("gpt-5.1")], + bedrock: [model("anthropic.claude-4")], + "openai-compatible": [model("llama-3.3")], +}; + +describe("LighthouseV2ChatPage", () => { + beforeEach(() => { + vi.stubGlobal( + "ResizeObserver", + class ResizeObserver { + observe = vi.fn(); + unobserve = vi.fn(); + disconnect = vi.fn(); + }, + ); + cancelRunMock.mockReset(); + createSessionMock.mockReset(); + getMessagesMock.mockReset(); + pushMock.mockReset(); + sendMessageMock.mockReset(); + + createSessionMock.mockResolvedValue({ + data: { + id: "session-1", + title: "Summarize findings", + isArchived: false, + insertedAt: "2026-06-24T10:00:00Z", + updatedAt: "2026-06-24T10:00:00Z", + activeTaskId: null, + }, + }); + getMessagesMock.mockResolvedValue({ data: [] }); + sendMessageMock.mockResolvedValue({ + data: { + task: { + id: "task-1", + name: "lighthouse-run", + state: "executing", + }, + }, + }); + }); + + it("uses the neutral page background instead of the global app background token", () => { + // Given / When + const { container } = renderPage(); + + // Then + expect(container.firstElementChild).toHaveClass("bg-bg-neutral-primary"); + expect(container.firstElementChild).not.toHaveClass("bg-background"); + }); + + it("does not render provider or model selectors in the chat composer", () => { + // Given / When + renderPage(); + + // Then + expect(screen.queryByRole("combobox")).not.toBeInTheDocument(); + expect( + screen.getByRole("link", { name: "Lighthouse settings" }), + ).toHaveAttribute("href", "/lighthouse/config"); + }); + + it("sends messages with the connected default provider and model from configuration", async () => { + // Given + const user = userEvent.setup(); + renderPage(); + + // When + await user.type( + screen.getByRole("textbox", { name: "Message" }), + ["Summarize findings", "{Enter}"].join(""), + ); + + // Then + await waitFor(() => + expect(sendMessageMock).toHaveBeenCalledWith({ + sessionId: "session-1", + text: "Summarize findings", + provider: "openai", + model: "gpt-5.1", + }), + ); + expect(createSessionMock).toHaveBeenCalledWith("Summarize findings"); + expect(pushMock).toHaveBeenCalledWith("/lighthouse?session=session-1"); + }); +}); + +function renderPage( + props?: Partial[0]>, +) { + return render( + , + ); +} + +function model(id: string): LighthouseV2SupportedModel { + return { + id, + maxInputTokens: null, + maxOutputTokens: null, + supportsFunctionCalling: null, + supportsVision: null, + supportsReasoning: null, + }; +} diff --git a/ui/components/lighthouse-v2/chat/lighthouse-v2-chat-page.tsx b/ui/components/lighthouse-v2/chat/lighthouse-v2-chat-page.tsx index 1903ccfa59..dbb2b211b7 100644 --- a/ui/components/lighthouse-v2/chat/lighthouse-v2-chat-page.tsx +++ b/ui/components/lighthouse-v2/chat/lighthouse-v2-chat-page.tsx @@ -29,13 +29,6 @@ import { } from "@/components/ai-elements/conversation"; import { LighthouseIcon } from "@/components/icons/Icons"; import { Button } from "@/components/shadcn/button/button"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/shadcn/select/select"; import { Textarea } from "@/components/shadcn/textarea/textarea"; import { useMountEffect } from "@/hooks/use-mount-effect"; import { @@ -104,17 +97,13 @@ export function LighthouseV2ChatPage({ const connectedConfigurations = configurations.filter( (configuration) => configuration.connected === true, ); - const initialProvider = - connectedConfigurations[0]?.providerType ?? - configurations[0]?.providerType ?? - LIGHTHOUSE_V2_PROVIDER_TYPE.OPENAI; - const [selectedProvider, setSelectedProvider] = - useState(initialProvider); - const [selectedModel, setSelectedModel] = useState( - connectedConfigurations[0]?.defaultModel ?? - modelsByProvider[initialProvider]?.[0]?.id ?? - "", - ); + const selectedConfiguration = connectedConfigurations[0] ?? configurations[0]; + const selectedProvider = + selectedConfiguration?.providerType ?? LIGHTHOUSE_V2_PROVIDER_TYPE.OPENAI; + const selectedModel = + selectedConfiguration?.defaultModel ?? + modelsByProvider[selectedProvider]?.[0]?.id ?? + ""; const [activeSessionId, setActiveSessionId] = useState( initialSessionId ?? null, ); @@ -129,25 +118,11 @@ export function LighthouseV2ChatPage({ createInitialLighthouseV2StreamState(), ); - const selectedConfiguration = configurations.find( - (configuration) => configuration.providerType === selectedProvider, - ); - const providerModels = modelsByProvider[selectedProvider] ?? []; const canSend = selectedConfiguration?.connected === true && !streamState.activeTaskId && !blockedByConflict; - const handleProviderChange = (provider: LighthouseV2ProviderType) => { - const nextConfig = configurations.find( - (configuration) => configuration.providerType === provider, - ); - setSelectedProvider(provider); - setSelectedModel( - nextConfig?.defaultModel ?? modelsByProvider[provider]?.[0]?.id ?? "", - ); - }; - const refreshMessages = async (sessionId: string) => { const result = await getLighthouseV2Messages(sessionId); if ("data" in result) { @@ -316,7 +291,7 @@ export function LighthouseV2ChatPage({ messages.length > 0 || Boolean(streamState.assistantText); return ( -
+
{hasConversation ? (
@@ -330,7 +305,7 @@ export function LighthouseV2ChatPage({ -
+
void; - onModelChange: (value: string) => void; - onProviderChange: (provider: LighthouseV2ProviderType) => void; onStop: () => void; onSubmit: (event: FormEvent) => void; onSubmitText: (text: string) => Promise; @@ -488,23 +445,17 @@ interface LighthouseV2ComposerProps { function LighthouseV2Composer({ canSend, - configurations, input, isStreaming, - models, selectedConfigurationConnected, - selectedModel, - selectedProvider, onInputChange, - onModelChange, - onProviderChange, onStop, onSubmit, onSubmitText, }: LighthouseV2ComposerProps) { return (