diff --git a/src/components/chat-composer.tsx b/src/components/chat-composer.tsx index 48177bd..baa7665 100644 --- a/src/components/chat-composer.tsx +++ b/src/components/chat-composer.tsx @@ -150,7 +150,7 @@ export function ChatComposer({
>;
readonly isDisabled?: boolean;
- readonly isDiagramActionDisabled?: boolean;
readonly isSending?: boolean;
readonly messages?: readonly ChatMessageView[];
readonly needsLogin?: boolean;
@@ -93,21 +91,18 @@ export type ChatMessageListProps = {
) => void;
readonly pendingCitationId?: string | null;
readonly pendingStatusText?: string | null;
- readonly onCreateDiagram?: (message: ChatMessageView) => void;
readonly sourceTitlesByDocumentId?: Readonly>;
};
export function ChatMessageList({
diagramStatesByMessageId = {},
isDisabled = false,
- isDiagramActionDisabled = false,
isSending = false,
messages = [],
needsLogin = false,
onCitationClick,
pendingCitationId = null,
pendingStatusText = null,
- onCreateDiagram,
sourceTitlesByDocumentId = {},
}: ChatMessageListProps): ReactElement {
const {
@@ -148,8 +143,6 @@ export function ChatMessageList({
diagramStatesByMessageId[getVirtualMessage(virtualItem)?.id ?? ""]
}
onCitationClick={onCitationClick}
- isDiagramActionDisabled={isDiagramActionDisabled}
- onCreateDiagram={onCreateDiagram}
pendingCitationId={pendingCitationId}
sourceTitlesByDocumentId={sourceTitlesByDocumentId}
/>
@@ -219,8 +212,6 @@ function VirtualMessageRow({
message,
measureElement,
onCitationClick,
- isDiagramActionDisabled,
- onCreateDiagram,
pendingCitationId,
sourceTitlesByDocumentId,
}: {
@@ -232,8 +223,6 @@ function VirtualMessageRow({
citation: ChatCitationView,
citationId: string,
) => void;
- readonly isDiagramActionDisabled: boolean;
- readonly onCreateDiagram?: (message: ChatMessageView) => void;
readonly pendingCitationId?: string | null;
readonly sourceTitlesByDocumentId: Readonly>;
}): ReactElement | null {
@@ -256,10 +245,8 @@ function VirtualMessageRow({
>
@@ -295,21 +282,17 @@ function EmptyChat({
function MessageBubble({
diagramState,
- isDiagramActionDisabled,
message,
onCitationClick,
- onCreateDiagram,
pendingCitationId,
sourceTitlesByDocumentId,
}: {
readonly diagramState: ChatDiagramState;
- readonly isDiagramActionDisabled: boolean;
readonly message: ChatMessageView;
readonly onCitationClick?: (
citation: ChatCitationView,
citationId: string,
) => void;
- readonly onCreateDiagram?: (message: ChatMessageView) => void;
readonly pendingCitationId?: string | null;
readonly sourceTitlesByDocumentId: Readonly>;
}): ReactElement {
@@ -359,12 +342,6 @@ function MessageBubble({
)}
-
{displayImageCitations.length > 0 && (
@@ -403,49 +380,6 @@ function MessageBubble({
);
}
-function AssistantDiagramAction({
- isDisabled,
- message,
- onCreateDiagram,
- state,
-}: {
- readonly isDisabled: boolean;
- readonly message: ChatMessageView;
- readonly onCreateDiagram?: (message: ChatMessageView) => void;
- readonly state: ChatDiagramState;
-}): ReactElement | null {
- if (
- !onCreateDiagram ||
- state.status === "loading" ||
- state.status === "ready" ||
- message.content.trim().length === 0
- ) {
- return null;
- }
-
- const label =
- state.status === "empty" || state.status === "error"
- ? "Try diagram again"
- : "Create diagram";
-
- return (
-
-
-
- );
-}
-
function AssistantDiagram({
state,
}: {
@@ -707,7 +641,7 @@ function CitationChip({
disabled={!onCitationClick || isPending}
onClick={() => onCitationClick?.(citation, citationId)}
aria-busy={isPending}
- className="inline-flex h-8 max-w-[250px] cursor-pointer items-center rounded-md border border-transparent bg-[#5c606b] px-3 text-left font-mono text-xs font-semibold leading-none text-[#cfd3dc] shadow-none transition-[background-color,border-color,color,box-shadow,transform] hover:border-[#8f96a8] hover:bg-[#4f535e] hover:text-white hover:shadow-[0_0_0_2px_rgba(143,150,168,0.22)] active:translate-y-px active:bg-[#454955] focus:outline-none focus:ring-4 focus:ring-ring/15 focus:ring-offset-2 focus:ring-offset-background disabled:cursor-wait disabled:opacity-75 disabled:hover:border-transparent disabled:hover:bg-[#5c606b] disabled:hover:text-[#cfd3dc] disabled:hover:shadow-none"
+ className="inline-flex h-8 max-w-[250px] cursor-pointer items-center rounded-md border border-primary/20 bg-primary/10 px-3 text-left font-mono text-xs font-semibold leading-none text-primary shadow-[0_1px_0_rgba(15,23,42,0.06)] transition-[background-color,border-color,color,box-shadow,transform] hover:border-primary/35 hover:bg-primary/15 hover:text-primary hover:shadow-[0_0_0_2px_rgba(37,99,235,0.12)] active:translate-y-px active:bg-primary/20 focus:outline-none focus:ring-4 focus:ring-ring/15 focus:ring-offset-2 focus:ring-offset-background disabled:cursor-wait disabled:opacity-75 disabled:hover:border-primary/20 disabled:hover:bg-primary/10 disabled:hover:text-primary disabled:hover:shadow-[0_1px_0_rgba(15,23,42,0.06)] dark:border-transparent dark:bg-[#5c606b] dark:text-[#cfd3dc] dark:shadow-none dark:hover:border-[#8f96a8] dark:hover:bg-[#4f535e] dark:hover:text-white dark:hover:shadow-[0_0_0_2px_rgba(143,150,168,0.22)] dark:active:bg-[#454955] dark:disabled:hover:border-transparent dark:disabled:hover:bg-[#5c606b] dark:disabled:hover:text-[#cfd3dc] dark:disabled:hover:shadow-none"
aria-label={`Open source ${label}`}
>
{label}
diff --git a/src/components/chat-panel.test.ts b/src/components/chat-panel.test.ts
index 6005947..54ccce5 100644
--- a/src/components/chat-panel.test.ts
+++ b/src/components/chat-panel.test.ts
@@ -116,6 +116,11 @@ describe("ChatPanel", () => {
expect(
screen.queryByRole("button", { name: "Create diagram" }),
).toBeNull();
+ expect(
+ screen.queryByRole("button", {
+ name: "Create diagram for this answer",
+ }),
+ ).toBeNull();
await user.click(screen.getByRole("button", { name: "Create" }));
await user.click(
@@ -133,17 +138,13 @@ describe("ChatPanel", () => {
).toBeTruthy();
});
- it("creates a diagram directly from an assistant answer", async () => {
+ it("shows a friendly no-diagram state for non-chartable answers", async () => {
const user = userEvent.setup();
vi.mocked(workspaceClient.createChatDiagram).mockResolvedValue({
diagram: {
- type: "column",
- source: "chart-visualization-skills",
- title: "Revenue by Segment",
- data: [
- { category: "Cloud", value: 42 },
- { category: "Ads", value: 28 },
- ],
+ type: "none",
+ reason:
+ "No clear chartable data was found. Ask for a table or numeric comparison first.",
},
});
@@ -153,54 +154,22 @@ describe("ChatPanel", () => {
{
id: "assistant_1",
role: "assistant",
- content: "Cloud revenue was 42 and Ads revenue was 28.",
+ content: "This is a qualitative summary without comparable numbers.",
},
],
}),
);
- await user.click(
- screen.getByRole("button", {
- name: "Create diagram for this answer",
- }),
- );
-
- expect(workspaceClient.createChatDiagram).toHaveBeenCalledWith({
- answer: "Cloud revenue was 42 and Ads revenue was 28.",
- });
- expect(await screen.findByText("Revenue by Segment")).toBeTruthy();
expect(
screen.queryByRole("button", {
name: "Create diagram for this answer",
}),
).toBeNull();
- });
-
- it("shows a friendly no-diagram state for non-chartable answers", async () => {
- const user = userEvent.setup();
- vi.mocked(workspaceClient.createChatDiagram).mockResolvedValue({
- diagram: {
- type: "none",
- reason:
- "No clear chartable data was found. Ask for a table or numeric comparison first.",
- },
- });
-
- render(
- React.createElement(C, {
- messages: [
- {
- id: "assistant_1",
- role: "assistant",
- content: "This is a qualitative summary without comparable numbers.",
- },
- ],
- }),
- );
+ await user.click(screen.getByRole("button", { name: "Create" }));
await user.click(
- screen.getByRole("button", {
- name: "Create diagram for this answer",
+ screen.getByRole("menuitem", {
+ name: "Create diagram from latest answer",
}),
);
@@ -211,10 +180,10 @@ describe("ChatPanel", () => {
),
).toBeTruthy();
expect(
- screen.getByRole("button", {
+ screen.queryByRole("button", {
name: "Try diagram again for this answer",
}),
- ).toBeTruthy();
+ ).toBeNull();
});
it("treats slash diagram text as a local command instead of a chat message", async () => {
@@ -502,14 +471,15 @@ describe("ChatPanel", () => {
expect(citationButton.className).toContain("h-8");
expect(citationButton.className).toContain("max-w-[250px]");
expect(citationButton.className).toContain("font-mono");
- expect(citationButton.className).toContain("text-[#cfd3dc]");
- expect(citationButton.className).toContain("bg-[#5c606b]");
- expect(citationButton.className).toContain("border-transparent");
- expect(citationButton.className).toContain("hover:border-[#8f96a8]");
- expect(citationButton.className).toContain("hover:bg-[#4f535e]");
- expect(citationButton.className).toContain("hover:text-white");
+ expect(citationButton.className).toContain("text-primary");
+ expect(citationButton.className).toContain("bg-primary/10");
+ expect(citationButton.className).toContain("border-primary/20");
+ expect(citationButton.className).toContain("hover:border-primary/35");
+ expect(citationButton.className).toContain("hover:bg-primary/15");
+ expect(citationButton.className).toContain("dark:bg-[#5c606b]");
+ expect(citationButton.className).toContain("dark:text-[#cfd3dc]");
expect(citationButton.className).toContain(
- "hover:shadow-[0_0_0_2px_rgba(143,150,168,0.22)]",
+ "hover:shadow-[0_0_0_2px_rgba(37,99,235,0.12)]",
);
expect(citationButton.className).not.toContain("border-border");
expect(citationButton.className).not.toContain("bg-background/80");
diff --git a/src/components/chat-panel.tsx b/src/components/chat-panel.tsx
index 7600cf0..92d3cbf 100644
--- a/src/components/chat-panel.tsx
+++ b/src/components/chat-panel.tsx
@@ -273,12 +273,10 @@ export function ChatPanel({