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
14 changes: 10 additions & 4 deletions desktop/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,16 @@ import { LoginScreen } from "@/components/LoginScreen";
import { NotificationToasts } from "@/components/NotificationToast";
import { NotificationCentre } from "@/components/NotificationCentre";
import { useNotificationStore } from "@/stores/notification-store";
import { TaosAssistantPanel } from "@/components/TaosAssistantPanel";
import { useTaosAgentStore } from "@/stores/taos-agent-store";

interface SystemShortcutsProps {
toggleSearch: () => void;
toggleLaunchpad: () => void;
toggleAssistant: () => void;
}

function SystemShortcuts({ toggleSearch, toggleLaunchpad }: SystemShortcutsProps) {
function SystemShortcuts({ toggleSearch, toggleLaunchpad, toggleAssistant }: SystemShortcutsProps) {
const windows = useProcessStore((s) => s.windows);
const closeWindow = useProcessStore((s) => s.closeWindow);
const minimizeWindow = useProcessStore((s) => s.minimizeWindow);
Expand Down Expand Up @@ -72,6 +75,7 @@ function SystemShortcuts({ toggleSearch, toggleLaunchpad }: SystemShortcutsProps

useShortcut("Ctrl+Space", toggleSearch, "Toggle search palette", "system");
useShortcut("Ctrl+l", toggleLaunchpad, "Toggle launchpad", "system");
useShortcut("Ctrl+/", toggleAssistant, "Toggle taOS Assistant", "system");
useShortcut("Ctrl+w", closeFocused, "Close focused window", "system");
useShortcut("Ctrl+m", minimizeFocused, "Minimize focused window", "system");
useShortcut("Ctrl+f", maximizeFocused, "Maximize/restore focused window", "system");
Expand Down Expand Up @@ -135,6 +139,7 @@ export function App() {

const toggleLaunchpad = useCallback(() => setLaunchpadOpen((v) => !v), []);
const toggleSearch = useCallback(() => setSearchOpen((v) => !v), []);
const toggleAssistant = useCallback(() => useTaosAgentStore.getState().togglePanel(), []);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Pass an open action to the top-bar button, not a toggle.

TopBar advertises onAssistantOpen, but this wiring closes the panel when it is already open. That makes the "Open taOS Assistant" button behave like a hidden toggle and diverges from the documented close affordances.

Suggested change
-  const toggleAssistant = useCallback(() => useTaosAgentStore.getState().togglePanel(), []);
+  const toggleAssistant = useCallback(() => useTaosAgentStore.getState().togglePanel(), []);
+  const openAssistant = useCallback(() => useTaosAgentStore.getState().openPanel(), []);
...
-              <TopBar onSearchOpen={toggleSearch} onAssistantOpen={toggleAssistant} />
+              <TopBar onSearchOpen={toggleSearch} onAssistantOpen={openAssistant} />

Also applies to: 235-235

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@desktop/src/App.tsx` at line 142, The top-bar button is wired to the toggle
action (toggleAssistant -> useTaosAgentStore.getState().togglePanel()) which
will close the panel if it's already open; change it to call an explicit "open"
action on the store instead so TopBar's onAssistantOpen always opens the
assistant. Replace the toggleAssistant callback to call the store's open method
(e.g., useTaosAgentStore.getState().openPanel() or setPanelOpen(true) /
openAssistant()) rather than togglePanel(), and update the same pattern where
used at the other occurrence (line ~235) so onAssistantOpen consistently invokes
the open action.


// Listen for launchpad open event from context menu
useEffect(() => {
Expand Down Expand Up @@ -213,7 +218,7 @@ export function App() {
if (mode === "desktop") {
return (
<ShortcutProvider>
<SystemShortcuts toggleSearch={toggleSearch} toggleLaunchpad={toggleLaunchpad} />
<SystemShortcuts toggleSearch={toggleSearch} toggleLaunchpad={toggleLaunchpad} toggleAssistant={toggleAssistant} />
<LoginGate>
{!launched && <LoginScreen onLaunch={() => setLaunched(true)} />}
{launched && !isFullscreen && (
Expand All @@ -227,13 +232,14 @@ export function App() {
)}
<div className={`transition-all duration-500 ${launched ? "opacity-100 scale-100" : "opacity-0 scale-95"}`}>
<div className="h-screen w-screen flex flex-col overflow-hidden bg-shell-bg text-shell-text">
<TopBar onSearchOpen={toggleSearch} />
<TopBar onSearchOpen={toggleSearch} onAssistantOpen={toggleAssistant} />
<Desktop />
<Dock onLaunchpadOpen={toggleLaunchpad} />
<Launchpad open={launchpadOpen} onClose={() => setLaunchpadOpen(false)} onOpenApp={(wid) => setActiveWindowId(wid)} />
<SearchPalette open={searchOpen} onClose={() => setSearchOpen(false)} onOpenApp={(wid) => setActiveWindowId(wid)} />
<NotificationToasts />
<NotificationCentre />
<TaosAssistantPanel />
</div>
</div>
</LoginGate>
Expand All @@ -244,7 +250,7 @@ export function App() {
// Mobile/Tablet layout — no login gate, no fullscreen button (PWA is already fullscreen)
return (
<ShortcutProvider>
<SystemShortcuts toggleSearch={toggleSearch} toggleLaunchpad={toggleLaunchpad} />
<SystemShortcuts toggleSearch={toggleSearch} toggleLaunchpad={toggleLaunchpad} toggleAssistant={toggleAssistant} />
Comment on lines 252 to +253
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Don't register the assistant shortcut on layouts that never render the assistant.

In the mobile/tablet branch, SystemShortcuts still binds Ctrl+/, but <TaosAssistantPanel /> is desktop-only. On devices with a hardware keyboard this becomes a silent no-op UI-wise while still mutating store state.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@desktop/src/App.tsx` around lines 252 - 253, SystemShortcuts is registering
the assistant hotkey even on mobile/tablet where TaosAssistantPanel never
renders, so update the usage or registration so the assistant shortcut is only
bound on layouts that actually show the assistant: add a layout-aware condition
(e.g. isDesktop or showAssistant) in App and either (A) only pass
toggleAssistant into <SystemShortcuts> when that condition is true, or (B) add a
prop like enableAssistantShortcut to SystemShortcuts and inside SystemShortcuts
only register the Ctrl+/ handler when that prop is true; reference
SystemShortcuts, toggleAssistant, and TaosAssistantPanel to locate the relevant
code.

<div className="taos-wallpaper h-screen w-screen flex flex-col text-shell-text" style={{ backgroundColor: wallpaperFallback, ["--wallpaper-desktop" as never]: wallpaperImage, ["--wallpaper-mobile" as never]: wallpaperMobileImage }}>
<div className={`flex-1 flex flex-col overflow-hidden transition-all duration-500 ${launched ? "opacity-100 scale-100" : "opacity-0 scale-95"}`}>
<MobileTopBar
Expand Down
94 changes: 94 additions & 0 deletions desktop/src/components/TaosAssistantPanel.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { render, screen, fireEvent } from "@testing-library/react";
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { TaosAssistantPanel } from "./TaosAssistantPanel";
import { useTaosAgentStore } from "@/stores/taos-agent-store";

// Mock child that opens a modal — keeps the test surface narrow
vi.mock("./TaosAssistantSettings", () => ({
TaosAssistantSettings: ({ open }: { open: boolean }) =>
open ? <div data-testid="settings-modal" /> : null,
}));

// Mock fetch so settings load doesn't throw in jsdom
const mockFetch = vi.fn().mockResolvedValue({
ok: true,
json: async () => ({ model: null }),
});
vi.stubGlobal("fetch", mockFetch);

function resetStore() {
useTaosAgentStore.setState({
isOpen: true,
messages: [],
model: null,
streaming: false,
});
}

describe("TaosAssistantPanel", () => {
beforeEach(() => {
resetStore();
mockFetch.mockClear();
});

afterEach(() => {
vi.clearAllMocks();
});

it("renders nothing when closed", () => {
useTaosAgentStore.setState({ isOpen: false });
const { container } = render(<TaosAssistantPanel />);
expect(container.firstChild).toBeNull();
});

it("shows title when open", () => {
render(<TaosAssistantPanel />);
expect(screen.getByRole("dialog", { name: /taOS Assistant/i })).toBeInTheDocument();
expect(screen.getByText("taOS Assistant")).toBeInTheDocument();
});

it("shows empty state with pick-model button when no model", () => {
render(<TaosAssistantPanel />);
expect(screen.getByText("Pick a model to get started")).toBeInTheDocument();
expect(screen.getByRole("button", { name: /choose a model/i })).toBeInTheDocument();
});

it("shows message input disabled when no model", () => {
render(<TaosAssistantPanel />);
const textarea = screen.getByRole("textbox", { name: /message taOS Assistant/i });
expect(textarea).toBeDisabled();
});

it("shows hint when model is set and no messages", () => {
useTaosAgentStore.setState({ model: "qwen3" });
render(<TaosAssistantPanel />);
expect(screen.getByText(/ask me anything about taOS/i)).toBeInTheDocument();
});

it("renders user and assistant messages", () => {
useTaosAgentStore.setState({
model: "qwen3",
messages: [
{ role: "user", content: "Hello there", ts: 1 },
{ role: "assistant", content: "Hi! How can I help?", ts: 2 },
],
});
render(<TaosAssistantPanel />);
expect(screen.getByText("Hello there")).toBeInTheDocument();
expect(screen.getByText("Hi! How can I help?")).toBeInTheDocument();
});

it("close button calls closePanel", () => {
render(<TaosAssistantPanel />);
const closeBtn = screen.getByRole("button", { name: /close taOS Assistant/i });
fireEvent.click(closeBtn);
expect(useTaosAgentStore.getState().isOpen).toBe(false);
});

it("settings cog opens settings modal", () => {
render(<TaosAssistantPanel />);
const cogBtn = screen.getByRole("button", { name: /assistant settings/i });
fireEvent.click(cogBtn);
expect(screen.getByTestId("settings-modal")).toBeInTheDocument();
});
});
Loading
Loading