+ {/* Chips wrap to a new line instead of squeezing the model control. */}
+
{modelSelector}
- {contextControl}
+ {contextControl && (
+
+ {contextControl}
+
+ )}
{isStreaming ? (
+
+
+ {providerName}
+
+
+ {modelName}
+
+
+ );
+}
diff --git a/ui/app/(prowler)/lighthouse/_components/chat/lighthouse-v2-chat-view.tsx b/ui/app/(prowler)/lighthouse/_components/chat/lighthouse-v2-chat-view.tsx
index 839949fa81..ea4c2f1ca0 100644
--- a/ui/app/(prowler)/lighthouse/_components/chat/lighthouse-v2-chat-view.tsx
+++ b/ui/app/(prowler)/lighthouse/_components/chat/lighthouse-v2-chat-view.tsx
@@ -29,17 +29,13 @@ import {
} from "@/app/(prowler)/lighthouse/_types";
import { LighthouseCurrentContextBadge } from "@/components/lighthouse/context-chip";
import { Card } from "@/components/shadcn";
-import {
- Combobox,
- type ComboboxGroup,
-} from "@/components/shadcn/combobox/combobox";
+import { type ComboboxGroup } from "@/components/shadcn/combobox/combobox";
import { Skeleton } from "@/components/shadcn/skeleton/skeleton";
import { useLighthouseCurrentContext } from "@/hooks/use-lighthouse-context";
import { useMountEffect } from "@/hooks/use-mount-effect";
-import { ProviderIcon } from "../config/provider-icon";
-
import { ChatComposerPanel } from "./composer";
+import { CurrentModelDisplay } from "./current-model-display";
import { ChatEmptyState } from "./empty-state";
import { useLighthouseChatStore } from "./lighthouse-chat-store-provider";
import {
@@ -47,6 +43,7 @@ import {
type LighthouseFeedbackSurvey,
} from "./lighthouse-feedback-survey";
import { MessageBubble } from "./message-bubble";
+import { ModelSelect } from "./model-select";
import { SkillComposerPill } from "./skill-composer-pill";
import { SkillRunProgress } from "./skill-run-progress";
import { StreamingAssistantMessage } from "./streaming-message";
@@ -183,19 +180,12 @@ export function LighthouseV2ChatView({
modelName={selectedModelLabel}
/>
) : (
-
-
-
+
),
selectedConfigurationConnected: selectedConfiguration?.connected === true,
onInputChange: setInput,
@@ -323,36 +313,6 @@ function SessionLoadingState() {
);
}
-interface CurrentModelDisplayProps {
- provider: LighthouseV2ProviderType;
- providerName: string;
- modelName: string;
-}
-
-function CurrentModelDisplay({
- provider,
- providerName,
- modelName,
-}: CurrentModelDisplayProps) {
- return (
-
-
-
- {providerName}
-
-
- {modelName}
-
-
- );
-}
-
function buildModelSelectorGroups(
connectedConfigurations: LighthouseV2Configuration[],
modelsByProvider: Record<
diff --git a/ui/app/(prowler)/lighthouse/_components/chat/model-select.tsx b/ui/app/(prowler)/lighthouse/_components/chat/model-select.tsx
new file mode 100644
index 0000000000..ea598bc45a
--- /dev/null
+++ b/ui/app/(prowler)/lighthouse/_components/chat/model-select.tsx
@@ -0,0 +1,35 @@
+import {
+ Combobox,
+ type ComboboxGroup,
+} from "@/components/shadcn/combobox/combobox";
+
+interface ModelSelectProps {
+ value: string;
+ onValueChange: (value: string) => void;
+ groups: ComboboxGroup[];
+ isSaving: boolean;
+}
+
+// Keeps a minimum width so the composer chips wrap instead of squeezing it.
+export function ModelSelect({
+ value,
+ onValueChange,
+ groups,
+ isSaving,
+}: ModelSelectProps) {
+ return (
+
+
+
+ );
+}
diff --git a/ui/app/(prowler)/lighthouse/_components/chat/skill-message-card.test.tsx b/ui/app/(prowler)/lighthouse/_components/chat/skill-message-card.test.tsx
new file mode 100644
index 0000000000..5724973957
--- /dev/null
+++ b/ui/app/(prowler)/lighthouse/_components/chat/skill-message-card.test.tsx
@@ -0,0 +1,44 @@
+import { render, screen } from "@testing-library/react";
+import { describe, expect, it } from "vitest";
+
+import type { LighthouseContextEnvelope } from "@/types/lighthouse-context";
+
+import { SkillMessageCard } from "./skill-message-card";
+
+describe("SkillMessageCard", () => {
+ it("names the provider and wizard step a connection guide was asked for", () => {
+ // Given
+ const context: LighthouseContextEnvelope = {
+ schemaVersion: 1,
+ transport: "inline",
+ items: [
+ {
+ kind: "provider",
+ id: "provider-wizard",
+ source: "focused",
+ scopeKey: "providers:wizard",
+ label: "Amazon Web Services · Link a Provider",
+ providerType: "aws",
+ },
+ ],
+ };
+
+ // When
+ render(
+
,
+ );
+
+ // Then
+ expect(screen.getByText("Connection Guide")).toBeInTheDocument();
+ expect(
+ screen.getByText("Amazon Web Services · Link a Provider"),
+ ).toBeInTheDocument();
+ });
+});
diff --git a/ui/app/(prowler)/lighthouse/_components/chat/skill-message-card.tsx b/ui/app/(prowler)/lighthouse/_components/chat/skill-message-card.tsx
index 6852bda8cc..a8981a951d 100644
--- a/ui/app/(prowler)/lighthouse/_components/chat/skill-message-card.tsx
+++ b/ui/app/(prowler)/lighthouse/_components/chat/skill-message-card.tsx
@@ -18,8 +18,11 @@ interface SkillMessageCardProps {
export function SkillMessageCard({ skillRef, context }: SkillMessageCardProps) {
const definition = getSkillById(skillRef.skillId);
const Icon = definition?.icon ?? Sparkles;
- const findingLabel = context?.items.find(
- (item) => item.kind === LIGHTHOUSE_CONTEXT_KIND.FINDING,
+ // The subject the skill ran on: a finding, or the provider being connected.
+ const subjectLabel = context?.items.find(
+ (item) =>
+ item.kind === LIGHTHOUSE_CONTEXT_KIND.FINDING ||
+ item.kind === LIGHTHOUSE_CONTEXT_KIND.PROVIDER,
)?.label;
return (
@@ -34,9 +37,9 @@ export function SkillMessageCard({ skillRef, context }: SkillMessageCardProps) {
{skillRef.name}
- {findingLabel && (
+ {subjectLabel && (
- {findingLabel}
+ {subjectLabel}
)}
diff --git a/ui/app/(prowler)/lighthouse/_components/panel/lighthouse-panel-chat.test.tsx b/ui/app/(prowler)/lighthouse/_components/panel/lighthouse-panel-chat.test.tsx
index 90b178d755..5f44b574aa 100644
--- a/ui/app/(prowler)/lighthouse/_components/panel/lighthouse-panel-chat.test.tsx
+++ b/ui/app/(prowler)/lighthouse/_components/panel/lighthouse-panel-chat.test.tsx
@@ -20,6 +20,7 @@ import {
} from "@/lib/lighthouse/context/contributions";
import { useLighthouseContextStore } from "@/store/lighthouse-context/store";
import { resetLighthouseContextStore } from "@/store/lighthouse-context/store.test-utils";
+import { useSidePanelStore } from "@/store/side-panel";
import type { LighthouseContextEnvelope } from "@/types/lighthouse-context";
import {
@@ -112,6 +113,7 @@ describe("LighthousePanelChat", () => {
resetPanelChatStoreForTests();
resetPanelChatConfigCacheForTests();
resetLighthouseContextStore();
+ useSidePanelStore.setState({ modalHostCount: 0 });
getConfigurationsMock.mockResolvedValue({ data: configurations });
getSupportedProvidersMock.mockResolvedValue({
@@ -182,6 +184,30 @@ describe("LighthousePanelChat", () => {
).toHaveAttribute("href", "/lighthouse/settings");
});
+ it("opens its links in a new tab while a dialog shares the screen with the panel", async () => {
+ // Given: the add-provider wizard is open beside the panel
+ getConfigurationsMock.mockResolvedValue({
+ data: [{ ...configurations[0], connected: false }],
+ });
+ useSidePanelStore.getState().registerModalHost();
+
+ // When
+ render(
+ <>
+
+ >,
+ );
+
+ // Then: following a link never navigates the wizard away
+ expect(
+ await screen.findByRole("link", { name: "Connect an LLM provider" }),
+ ).toHaveAttribute("target", "_blank");
+ expect(
+ screen.getByRole("link", { name: "Open Lighthouse AI full page" }),
+ ).toHaveAttribute("target", "_blank");
+ });
+
it("renders the chat composer and recent chats once ready", async () => {
// Given
getSessionsMock.mockResolvedValue({
diff --git a/ui/app/(prowler)/lighthouse/_components/panel/lighthouse-panel-chat.tsx b/ui/app/(prowler)/lighthouse/_components/panel/lighthouse-panel-chat.tsx
index 0cb7531b31..690b6f9080 100644
--- a/ui/app/(prowler)/lighthouse/_components/panel/lighthouse-panel-chat.tsx
+++ b/ui/app/(prowler)/lighthouse/_components/panel/lighthouse-panel-chat.tsx
@@ -45,6 +45,7 @@ import {
} from "../chat/lighthouse-v2-chat-view";
import { LighthousePanelChatSkeleton } from "./lighthouse-panel-chat-skeleton";
+import { usePanelLinkTarget } from "./use-panel-link-target";
const PANEL_CHAT_STATUS = {
LOADING: "loading",
@@ -301,6 +302,7 @@ function PanelChatError({ message, onRetry }: PanelChatErrorProps) {
}
function PanelChatConnectCta() {
+ const linkTarget = usePanelLinkTarget();
return (