mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-06-13 14:23:58 +00:00
fix(cli): refresh installed apps after settings changes
This commit is contained in:
parent
7a6cc657db
commit
9efdce276f
@ -69,6 +69,7 @@ import {
|
||||
updateSettings,
|
||||
updateWebSearchSettings,
|
||||
} from "@/lib/api";
|
||||
import { notifyCliAppsChanged } from "@/lib/cli-app-events";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useClient } from "@/providers/ClientProvider";
|
||||
import type {
|
||||
@ -626,6 +627,9 @@ export function SettingsView({
|
||||
try {
|
||||
const payload = await runCliAppAction(token, action, name);
|
||||
setCliApps(payload);
|
||||
if (action !== "test") {
|
||||
notifyCliAppsChanged(payload);
|
||||
}
|
||||
setCliAppsMessage(payload.last_action?.message ?? null);
|
||||
setCliAppsFocusName(action === "uninstall" ? null : name);
|
||||
} catch (err) {
|
||||
|
||||
@ -20,6 +20,11 @@ import { ThreadViewport } from "@/components/thread/ThreadViewport";
|
||||
import { useNanobotStream, type SendImage, type SendOptions } from "@/hooks/useNanobotStream";
|
||||
import { useSessionHistory } from "@/hooks/useSessions";
|
||||
import { fetchCliApps, listSlashCommands } from "@/lib/api";
|
||||
import {
|
||||
CLI_APPS_CHANGED_EVENT,
|
||||
installedCliAppsFromPayload,
|
||||
isCliAppsPayload,
|
||||
} from "@/lib/cli-app-events";
|
||||
import type { ChatSummary, CliAppInfo, SlashCommand, UIMessage } from "@/lib/types";
|
||||
import { normalizeLegacyLongTaskMessages } from "@/lib/thread-display-compat";
|
||||
import { scrubSubagentUiMessages } from "@/lib/subagent-channel-display";
|
||||
@ -251,7 +256,7 @@ export function ThreadShell({
|
||||
const refreshCliApps = useCallback(async () => {
|
||||
try {
|
||||
const payload = await fetchCliApps(token);
|
||||
setCliApps(payload.apps.filter((app) => app.installed));
|
||||
setCliApps(installedCliAppsFromPayload(payload));
|
||||
} catch {
|
||||
setCliApps([]);
|
||||
}
|
||||
@ -262,7 +267,7 @@ export function ThreadShell({
|
||||
const load = async () => {
|
||||
try {
|
||||
const payload = await fetchCliApps(token);
|
||||
if (!cancelled) setCliApps(payload.apps.filter((app) => app.installed));
|
||||
if (!cancelled) setCliApps(installedCliAppsFromPayload(payload));
|
||||
} catch {
|
||||
if (!cancelled) setCliApps([]);
|
||||
}
|
||||
@ -275,10 +280,20 @@ export function ThreadShell({
|
||||
};
|
||||
window.addEventListener("focus", refreshOnFocus);
|
||||
document.addEventListener("visibilitychange", refreshOnFocus);
|
||||
const refreshOnCliAppsChanged = (event: Event) => {
|
||||
const payload = (event as CustomEvent<unknown>).detail;
|
||||
if (isCliAppsPayload(payload)) {
|
||||
setCliApps(installedCliAppsFromPayload(payload));
|
||||
return;
|
||||
}
|
||||
void refreshCliApps();
|
||||
};
|
||||
window.addEventListener(CLI_APPS_CHANGED_EVENT, refreshOnCliAppsChanged);
|
||||
return () => {
|
||||
cancelled = true;
|
||||
window.removeEventListener("focus", refreshOnFocus);
|
||||
document.removeEventListener("visibilitychange", refreshOnFocus);
|
||||
window.removeEventListener(CLI_APPS_CHANGED_EVENT, refreshOnCliAppsChanged);
|
||||
};
|
||||
}, [refreshCliApps, token]);
|
||||
|
||||
|
||||
22
webui/src/lib/cli-app-events.ts
Normal file
22
webui/src/lib/cli-app-events.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import type { CliAppInfo, CliAppsPayload } from "@/lib/types";
|
||||
|
||||
export const CLI_APPS_CHANGED_EVENT = "nanobot:cli-apps-changed";
|
||||
|
||||
export function isCliAppsPayload(value: unknown): value is CliAppsPayload {
|
||||
return (
|
||||
!!value &&
|
||||
typeof value === "object" &&
|
||||
Array.isArray((value as { apps?: unknown }).apps)
|
||||
);
|
||||
}
|
||||
|
||||
export function installedCliAppsFromPayload(payload: CliAppsPayload): CliAppInfo[] {
|
||||
return payload.apps.filter((app) => app.installed);
|
||||
}
|
||||
|
||||
export function notifyCliAppsChanged(payload: CliAppsPayload): void {
|
||||
if (typeof window === "undefined") return;
|
||||
window.dispatchEvent(new CustomEvent<CliAppsPayload>(CLI_APPS_CHANGED_EVENT, {
|
||||
detail: payload,
|
||||
}));
|
||||
}
|
||||
@ -3,8 +3,9 @@ import type { ReactNode } from "react";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { ThreadShell } from "@/components/thread/ThreadShell";
|
||||
import { CLI_APPS_CHANGED_EVENT } from "@/lib/cli-app-events";
|
||||
import { ClientProvider } from "@/providers/ClientProvider";
|
||||
import type { UIMessage } from "@/lib/types";
|
||||
import type { CliAppsPayload, UIMessage } from "@/lib/types";
|
||||
function makeClient() {
|
||||
const errorHandlers = new Set<(err: { kind: string }) => void>();
|
||||
const chatHandlers = new Map<string, Set<(ev: import("@/lib/types").InboundEvent) => void>>();
|
||||
@ -994,4 +995,50 @@ describe("ThreadShell", () => {
|
||||
await waitFor(() => expect(screen.getByText("from chat b")).toBeInTheDocument());
|
||||
expect(screen.queryByText("from chat a")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("updates @ CLI app suggestions when settings broadcasts an install", async () => {
|
||||
const client = makeClient();
|
||||
render(wrap(
|
||||
client,
|
||||
<ThreadShell
|
||||
session={session("chat-cli-apps")}
|
||||
title="Chat chat-cli-apps"
|
||||
onToggleSidebar={() => {}}
|
||||
onGoHome={() => {}}
|
||||
onNewChat={() => {}}
|
||||
/>,
|
||||
));
|
||||
|
||||
const input = await screen.findByLabelText("Message input");
|
||||
expect(screen.queryByRole("listbox", { name: "CLI Apps" })).not.toBeInTheDocument();
|
||||
|
||||
const payload: CliAppsPayload = {
|
||||
apps: [{
|
||||
name: "gimp",
|
||||
display_name: "GIMP",
|
||||
category: "image",
|
||||
description: "Image editing",
|
||||
requires: "",
|
||||
source: "harness",
|
||||
entry_point: "cli-anything-gimp",
|
||||
install_supported: true,
|
||||
installed: true,
|
||||
available: true,
|
||||
status: "installed",
|
||||
logo_url: null,
|
||||
brand_color: "#5C5543",
|
||||
skill_installed: true,
|
||||
}],
|
||||
installed_count: 1,
|
||||
catalog_updated_at: "2026-04-18",
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
window.dispatchEvent(new CustomEvent(CLI_APPS_CHANGED_EVENT, { detail: payload }));
|
||||
});
|
||||
fireEvent.change(input, { target: { value: "@", selectionStart: 1 } });
|
||||
|
||||
expect(screen.getByRole("listbox", { name: "CLI Apps" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("option", { name: /@gimp/i })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user