Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions apps/desktop/src/chat/components/body/empty.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ export function ChatBodyEmpty({
}) {
const { chat } = useShell();
const openNew = useTabs((state) => state.openNew);
const isFloating = chat.mode === "FloatingOpen";
const isDarkSurface =
chat.mode === "FloatingOpen" || chat.mode === "RightPanelOpen";

const handleGoToSettings = useCallback(() => {
openNew({ type: "settings", state: { tab: "intelligence" } });
Expand All @@ -66,7 +67,7 @@ export function ChatBodyEmpty({
<span
className={cn([
"text-sm font-medium",
isFloating ? "text-white" : "text-neutral-800",
isDarkSurface ? "text-white" : "text-neutral-800",
])}
>
Anarlog AI
Expand All @@ -76,7 +77,7 @@ export function ChatBodyEmpty({
<p
className={cn([
"mb-2 text-sm",
isFloating ? "text-stone-200" : "text-neutral-700",
isDarkSurface ? "text-stone-200" : "text-neutral-700",
])}
>
Hi, I'm Anarlog AI. Set up a language model and I'll be ready to
Expand Down Expand Up @@ -104,7 +105,7 @@ export function ChatBodyEmpty({
<span
className={cn([
"text-sm font-medium",
isFloating ? "text-white" : "text-neutral-800",
isDarkSurface ? "text-white" : "text-neutral-800",
])}
>
Anarlog AI
Expand All @@ -114,7 +115,7 @@ export function ChatBodyEmpty({
<p
className={cn([
"mb-2 text-sm",
isFloating ? "text-stone-200" : "text-neutral-700",
isDarkSurface ? "text-stone-200" : "text-neutral-700",
])}
>
Hi, I'm Anarlog AI. I can help you pull context from your notes, find
Expand All @@ -128,7 +129,7 @@ export function ChatBodyEmpty({
onClick={() => handleSuggestionClick(prompt)}
className={cn([
"inline-flex items-center gap-1 rounded-full border px-2 py-1 text-[11px]",
isFloating
isDarkSurface
? "border-stone-600 bg-stone-700 text-stone-100 hover:bg-stone-600"
: "border-neutral-300 bg-white text-neutral-700 hover:bg-neutral-100",
"transition-colors",
Expand Down
70 changes: 70 additions & 0 deletions apps/desktop/src/chat/components/body/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { cleanup, render, screen } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";

const { shellState } = vi.hoisted(() => ({
shellState: {
mode: "FloatingOpen" as
| "FloatingClosed"
| "FloatingOpen"
| "RightPanelOpen",
},
}));

vi.mock("./empty", () => ({
ChatBodyEmpty: () => <div data-testid="chat-body-empty" />,
}));

vi.mock("./non-empty", () => ({
ChatBodyNonEmpty: () => <div data-testid="chat-body-non-empty" />,
}));

vi.mock("./use-chat-auto-scroll", () => ({
useChatAutoScroll: () => ({
contentRef: { current: null },
handleWheel: vi.fn(),
isAtBottom: true,
scrollRef: { current: null },
scrollToBottom: vi.fn(),
showGoToRecent: false,
updateAutoScrollState: vi.fn(),
}),
}));

vi.mock("~/contexts/shell", () => ({
useShell: () => ({
chat: {
mode: shellState.mode,
},
}),
}));

import { ChatBody } from "./index";

describe("ChatBody", () => {
beforeEach(() => {
cleanup();
shellState.mode = "FloatingOpen";
});

it("keeps horizontal content padding", () => {
render(<ChatBody messages={[]} status="ready" />);

const content = screen.getByTestId("chat-body-empty").parentElement;

expect(content?.className).toContain("px-2");
expect(content?.className).not.toContain("pr-0");
});

it("uses balanced content padding in the right panel", () => {
shellState.mode = "RightPanelOpen";

render(<ChatBody messages={[]} status="ready" />);

const content = screen.getByTestId("chat-body-empty").parentElement;

expect(content?.className).toContain("px-3");
expect(content?.className).toContain("py-5");
expect(content?.className).not.toContain("px-5");
expect(content?.className).not.toContain("px-2");
});
});
9 changes: 8 additions & 1 deletion apps/desktop/src/chat/components/body/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import type { ChatStatus } from "ai";
import { ChevronDownIcon } from "lucide-react";

import { Button } from "@hypr/ui/components/ui/button";
import { cn } from "@hypr/utils";

import { ChatBodyEmpty } from "./empty";
import { ChatBodyNonEmpty } from "./non-empty";
import { useChatAutoScroll } from "./use-chat-auto-scroll";

import type { ContextRef } from "~/chat/context/entities";
import type { HyprUIMessage } from "~/chat/types";
import { useShell } from "~/contexts/shell";

export function ChatBody({
messages,
Expand All @@ -31,6 +33,8 @@ export function ChatBody({
contextRefs?: ContextRef[],
) => void;
}) {
const { chat } = useShell();
const isRightPanel = chat.mode === "RightPanelOpen";
const {
contentRef,
isAtBottom,
Expand All @@ -51,7 +55,10 @@ export function ChatBody({
>
<div
ref={contentRef}
className="flex min-h-full flex-1 flex-col px-2 py-3"
className={cn([
"flex min-h-full flex-1 flex-col",
isRightPanel ? "px-3 py-5" : "px-2 py-3",
])}
>
<div className="flex-1" />
{messages.length === 0 ? (
Expand Down
71 changes: 71 additions & 0 deletions apps/desktop/src/chat/components/chat-panel.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { cleanup, render, screen } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";

const mocks = vi.hoisted(() => ({
chat: {
groupId: undefined as string | undefined,
selectChat: vi.fn(),
sessionId: "chat-session-id",
setGroupId: vi.fn(),
startNewChat: vi.fn(),
},
toolbarControls: vi.fn(),
}));

vi.mock("./toolbar-controls", () => ({
ChatToolbarControls: (props: {
layout?: "floating" | "right-panel";
surface?: "light" | "dark";
}) => {
mocks.toolbarControls(props);
return <div data-surface={props.surface} data-testid="chat-toolbar" />;
},
}));

vi.mock("./use-session-tab", () => ({
useSessionTab: () => ({ currentSessionId: "current-session-id" }),
}));

vi.mock("~/ai/hooks", () => ({
useLanguageModel: () => undefined,
}));

vi.mock("~/chat/store/use-chat-actions", () => ({
useChatActions: () => ({ handleSendMessage: vi.fn() }),
}));

vi.mock("~/contexts/shell", () => ({
useShell: () => ({ chat: mocks.chat }),
}));

vi.mock("~/store/tinybase/store/main", () => ({
STORE_ID: "main",
UI: {
useValues: () => ({}),
},
}));

import { ChatView } from "./chat-panel";

describe("ChatView", () => {
beforeEach(() => {
cleanup();
mocks.toolbarControls.mockClear();
});

it("uses the modal dark surface for the right panel layout", () => {
const { container } = render(<ChatView layout="right-panel" />);
const root = container.firstElementChild;

expect(root?.className).toContain("bg-stone-800");
expect(root?.className).toContain("text-white");
expect(root?.firstElementChild?.className).toContain("h-12");
expect(screen.getByTestId("chat-toolbar").dataset.surface).toBe("dark");
expect(mocks.toolbarControls).toHaveBeenCalledWith(
expect.objectContaining({
layout: "right-panel",
surface: "dark",
}),
);
});
});
9 changes: 4 additions & 5 deletions apps/desktop/src/chat/components/chat-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,14 @@ export function ChatView({
<div
className={cn([
"flex h-full min-h-0 flex-col overflow-hidden",
isFloating ? "bg-stone-800 text-white" : "bg-stone-50",
"bg-stone-800 text-white",
])}
>
<div
className={cn([
"flex shrink-0 items-center pr-0 pl-0",
isFloating
? "h-11 border-b border-stone-700/80"
: "h-10 border-b border-neutral-100",
isFloating ? "h-11" : "h-12",
"border-b border-stone-700/80",
])}
>
<ChatToolbarControls
Expand All @@ -65,7 +64,7 @@ export function ChatView({
onOpenFloating={onOpenFloating}
onOpenRightPanel={onOpenRightPanel}
onSelectChat={chat.selectChat}
surface={isFloating ? "dark" : "light"}
surface="dark"
/>
</div>
{user_id && (
Expand Down
24 changes: 22 additions & 2 deletions apps/desktop/src/chat/components/context-bar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,15 @@ import {
import type { ReactNode } from "react";
import { beforeEach, describe, expect, it, vi } from "vitest";

const { openNewMock, searchMock, storeState } = vi.hoisted(() => ({
const { openNewMock, searchMock, shellState, storeState } = vi.hoisted(() => ({
openNewMock: vi.fn(),
searchMock: vi.fn(),
shellState: {
mode: "FloatingOpen" as
| "FloatingClosed"
| "FloatingOpen"
| "RightPanelOpen",
},
storeState: {
rows: {} as Record<string, Record<string, unknown>>,
sessionIds: [] as string[],
Expand Down Expand Up @@ -40,7 +46,7 @@ vi.mock("@hypr/ui/components/ui/tooltip", () => ({
vi.mock("~/contexts/shell", () => ({
useShell: () => ({
chat: {
mode: "FloatingOpen",
mode: shellState.mode,
},
}),
}));
Expand Down Expand Up @@ -103,6 +109,7 @@ describe("ContextBar session picker", () => {
cleanup();
openNewMock.mockClear();
searchMock.mockReset();
shellState.mode = "FloatingOpen";
storeState.rows = {};
storeState.sessionIds = [];
globalThis.ResizeObserver = class {
Expand Down Expand Up @@ -179,4 +186,17 @@ describe("ContextBar session picker", () => {
expect(await screen.findByText("Hydrated Note")).toBeTruthy();
expect(screen.queryByText(/1970/)).toBeNull();
});

it("uses balanced context bar horizontal margin in the right panel", () => {
shellState.mode = "RightPanelOpen";

renderContextBar();

const outer = document.querySelector("[data-chat-context-bar]");

expect(outer?.className).toContain("mx-3");
expect(outer?.className).not.toContain("mx-5");
expect(outer?.className).not.toContain("mx-2");
expect(outer?.className).not.toContain("mr-0");
});
});
6 changes: 5 additions & 1 deletion apps/desktop/src/chat/components/context-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { cn, safeParseDate } from "@hypr/utils";
import type { ContextRef } from "~/chat/context/entities";
import { type ContextChipProps, renderChip } from "~/chat/context/registry";
import type { DisplayEntity } from "~/chat/context/use-chat-context-pipeline";
import { useShell } from "~/contexts/shell";
import { useSearchEngine } from "~/search/contexts/engine";
import { getSessionEvent } from "~/session/utils";
import * as main from "~/store/tinybase/store/main";
Expand Down Expand Up @@ -352,6 +353,8 @@ export function ContextBar({
onRemoveEntity?: (key: string) => void;
onAddEntity?: (ref: ContextRef) => void;
}) {
const { chat } = useShell();
const isRightPanel = chat.mode === "RightPanelOpen";
const chips = useMemo(
() =>
entities
Expand All @@ -372,9 +375,10 @@ export function ContextBar({

return (
<div
data-chat-context-bar
className={cn([
"shrink-0 rounded-t-xl border-t border-r border-l border-neutral-200 bg-white",
"mx-2",
isRightPanel ? "mx-3" : "mx-2",
])}
>
<div className="flex items-start gap-1.5 px-2 py-2">
Expand Down
Loading
Loading