mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
fix(ui): address review follow-ups
This commit is contained in:
@@ -8,6 +8,11 @@ import {
|
||||
import { DeletedMessages } from "../ui/src/ui/chat/deleted-messages.ts";
|
||||
import { buildChatMarkdown } from "../ui/src/ui/chat/export.ts";
|
||||
import { getPinnedMessageSummary } from "../ui/src/ui/chat/pinned-summary.ts";
|
||||
import { messageMatchesSearchQuery } from "../ui/src/ui/chat/search-match.ts";
|
||||
import {
|
||||
MAX_CACHED_CHAT_SESSIONS,
|
||||
getOrCreateSessionCacheValue,
|
||||
} from "../ui/src/ui/chat/session-cache.ts";
|
||||
import type { GatewayBrowserClient } from "../ui/src/ui/gateway.ts";
|
||||
|
||||
function createStorageMock(): Storage {
|
||||
@@ -68,6 +73,38 @@ function createHost(overrides: Partial<ChatHost> = {}): ChatHost & Record<string
|
||||
};
|
||||
}
|
||||
|
||||
function createSettingsHost() {
|
||||
return {
|
||||
settings: {
|
||||
gatewayUrl: "",
|
||||
token: "",
|
||||
sessionKey: "main",
|
||||
lastActiveSessionKey: "main",
|
||||
theme: "claw",
|
||||
themeMode: "system",
|
||||
chatFocusMode: false,
|
||||
chatShowThinking: true,
|
||||
splitRatio: 0.6,
|
||||
navCollapsed: false,
|
||||
navWidth: 220,
|
||||
navGroupsCollapsed: {},
|
||||
},
|
||||
theme: "claw",
|
||||
themeMode: "system",
|
||||
themeResolved: "dark",
|
||||
applySessionKey: "main",
|
||||
sessionKey: "main",
|
||||
tab: "chat",
|
||||
connected: false,
|
||||
chatHasAutoScrolled: false,
|
||||
logsAtBottom: false,
|
||||
eventLog: [],
|
||||
eventLogBuffer: [],
|
||||
basePath: "",
|
||||
systemThemeCleanup: null,
|
||||
} as Record<string, unknown>;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetModules();
|
||||
const { window, document } = parseHTML("<html><body></body></html>");
|
||||
@@ -149,6 +186,77 @@ describe("chat regressions", () => {
|
||||
expect(markdown).toContain("general kenobi");
|
||||
});
|
||||
|
||||
it("matches chat search against extracted structured message text", () => {
|
||||
expect(
|
||||
messageMatchesSearchQuery(
|
||||
{
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "Structured search target" }],
|
||||
},
|
||||
"search target",
|
||||
),
|
||||
).toBe(true);
|
||||
expect(
|
||||
messageMatchesSearchQuery(
|
||||
{
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "Structured search target" }],
|
||||
},
|
||||
"missing",
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("bounds cached per-session chat state", () => {
|
||||
const cache = new Map<string, number>();
|
||||
for (let i = 0; i < MAX_CACHED_CHAT_SESSIONS; i++) {
|
||||
getOrCreateSessionCacheValue(cache, `session-${i}`, () => i);
|
||||
}
|
||||
|
||||
expect(cache.size).toBe(MAX_CACHED_CHAT_SESSIONS);
|
||||
expect(getOrCreateSessionCacheValue(cache, "session-0", () => -1)).toBe(0);
|
||||
|
||||
getOrCreateSessionCacheValue(cache, `session-${MAX_CACHED_CHAT_SESSIONS}`, () => 99);
|
||||
|
||||
expect(cache.size).toBe(MAX_CACHED_CHAT_SESSIONS);
|
||||
expect(cache.has("session-0")).toBe(true);
|
||||
expect(cache.has("session-1")).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps the command palette in sync with slash commands", async () => {
|
||||
const { getPaletteItems } = await import("../ui/src/ui/views/command-palette.ts");
|
||||
const labels = getPaletteItems().map((item) => item.label);
|
||||
|
||||
expect(labels).toContain("/agents");
|
||||
expect(labels).toContain("/clear");
|
||||
expect(labels).toContain("/kill");
|
||||
expect(labels).toContain("/skill");
|
||||
expect(labels).toContain("/steer");
|
||||
});
|
||||
|
||||
it("falls back to addListener/removeListener for system theme changes", async () => {
|
||||
const { attachThemeListener, detachThemeListener } =
|
||||
await import("../ui/src/ui/app-settings.ts");
|
||||
const host = createSettingsHost();
|
||||
const addListener = vi.fn();
|
||||
const removeListener = vi.fn();
|
||||
|
||||
vi.stubGlobal(
|
||||
"matchMedia",
|
||||
vi.fn(() => ({
|
||||
matches: false,
|
||||
addListener,
|
||||
removeListener,
|
||||
})),
|
||||
);
|
||||
|
||||
attachThemeListener(host);
|
||||
expect(addListener).toHaveBeenCalledTimes(1);
|
||||
|
||||
detachThemeListener(host);
|
||||
expect(removeListener).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("queues local slash commands that would mutate session state during an active run", async () => {
|
||||
const { handleSendChat } = await import("../ui/src/ui/app-chat.ts");
|
||||
const request = vi.fn();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { setTabFromRoute } from "./app-settings.ts";
|
||||
import { attachThemeListener, detachThemeListener, setTabFromRoute } from "./app-settings.ts";
|
||||
import type { Tab } from "./navigation.ts";
|
||||
|
||||
type SettingsHost = Parameters<typeof setTabFromRoute>[0] & {
|
||||
@@ -13,14 +13,17 @@ const createHost = (tab: Tab): SettingsHost => ({
|
||||
token: "",
|
||||
sessionKey: "main",
|
||||
lastActiveSessionKey: "main",
|
||||
theme: "system",
|
||||
theme: "claw",
|
||||
themeMode: "system",
|
||||
chatFocusMode: false,
|
||||
chatShowThinking: true,
|
||||
splitRatio: 0.6,
|
||||
navCollapsed: false,
|
||||
navWidth: 220,
|
||||
navGroupsCollapsed: {},
|
||||
},
|
||||
theme: "system",
|
||||
theme: "claw",
|
||||
themeMode: "system",
|
||||
themeResolved: "dark",
|
||||
applySessionKey: "main",
|
||||
sessionKey: "main",
|
||||
@@ -35,6 +38,7 @@ const createHost = (tab: Tab): SettingsHost => ({
|
||||
themeMediaHandler: null,
|
||||
logsPollInterval: null,
|
||||
debugPollInterval: null,
|
||||
systemThemeCleanup: null,
|
||||
});
|
||||
|
||||
describe("setTabFromRoute", () => {
|
||||
@@ -67,4 +71,24 @@ describe("setTabFromRoute", () => {
|
||||
setTabFromRoute(host, "chat");
|
||||
expect(host.debugPollInterval).toBeNull();
|
||||
});
|
||||
|
||||
it("falls back to addListener/removeListener for older MediaQueryList implementations", () => {
|
||||
const host = createHost("chat");
|
||||
const addListener = vi.fn();
|
||||
const removeListener = vi.fn();
|
||||
vi.stubGlobal(
|
||||
"matchMedia",
|
||||
vi.fn(() => ({
|
||||
matches: false,
|
||||
addListener,
|
||||
removeListener,
|
||||
})),
|
||||
);
|
||||
|
||||
attachThemeListener(host);
|
||||
expect(addListener).toHaveBeenCalledTimes(1);
|
||||
|
||||
detachThemeListener(host);
|
||||
expect(removeListener).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -334,8 +334,15 @@ function syncSystemThemeListener(host: SettingsHost) {
|
||||
}
|
||||
applyResolvedTheme(host, resolveTheme(host.theme, "system"));
|
||||
};
|
||||
mql.addEventListener("change", onChange);
|
||||
host.systemThemeCleanup = () => mql.removeEventListener("change", onChange);
|
||||
if (typeof mql.addEventListener === "function") {
|
||||
mql.addEventListener("change", onChange);
|
||||
host.systemThemeCleanup = () => mql.removeEventListener("change", onChange);
|
||||
return;
|
||||
}
|
||||
if (typeof mql.addListener === "function") {
|
||||
mql.addListener(onChange);
|
||||
host.systemThemeCleanup = () => mql.removeListener(onChange);
|
||||
}
|
||||
}
|
||||
|
||||
export function syncTabWithLocation(host: SettingsHost, replace: boolean) {
|
||||
|
||||
10
ui/src/ui/chat/search-match.ts
Normal file
10
ui/src/ui/chat/search-match.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { extractTextCached } from "./message-extract.ts";
|
||||
|
||||
export function messageMatchesSearchQuery(message: unknown, query: string): boolean {
|
||||
const normalizedQuery = query.trim().toLowerCase();
|
||||
if (!normalizedQuery) {
|
||||
return true;
|
||||
}
|
||||
const text = (extractTextCached(message) ?? "").toLowerCase();
|
||||
return text.includes(normalizedQuery);
|
||||
}
|
||||
26
ui/src/ui/chat/session-cache.ts
Normal file
26
ui/src/ui/chat/session-cache.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
export const MAX_CACHED_CHAT_SESSIONS = 20;
|
||||
|
||||
export function getOrCreateSessionCacheValue<T>(
|
||||
map: Map<string, T>,
|
||||
sessionKey: string,
|
||||
create: () => T,
|
||||
): T {
|
||||
if (map.has(sessionKey)) {
|
||||
const existing = map.get(sessionKey) as T;
|
||||
// Refresh insertion order so recently used sessions stay cached.
|
||||
map.delete(sessionKey);
|
||||
map.set(sessionKey, existing);
|
||||
return existing;
|
||||
}
|
||||
|
||||
const created = create();
|
||||
map.set(sessionKey, created);
|
||||
while (map.size > MAX_CACHED_CHAT_SESSIONS) {
|
||||
const oldest = map.keys().next().value;
|
||||
if (typeof oldest !== "string") {
|
||||
break;
|
||||
}
|
||||
map.delete(oldest);
|
||||
}
|
||||
return created;
|
||||
}
|
||||
@@ -62,6 +62,24 @@ function expectedGatewayUrl(basePath: string): string {
|
||||
return `${proto}://${location.host}${basePath}`;
|
||||
}
|
||||
|
||||
function createSettings(overrides: Record<string, unknown> = {}) {
|
||||
return {
|
||||
gatewayUrl: "wss://gateway.example:8443/openclaw",
|
||||
token: "",
|
||||
sessionKey: "main",
|
||||
lastActiveSessionKey: "main",
|
||||
theme: "claw",
|
||||
themeMode: "system",
|
||||
chatFocusMode: false,
|
||||
chatShowThinking: true,
|
||||
splitRatio: 0.6,
|
||||
navCollapsed: false,
|
||||
navWidth: 220,
|
||||
navGroupsCollapsed: {},
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe("loadSettings default gateway URL derivation", () => {
|
||||
beforeEach(() => {
|
||||
vi.resetModules();
|
||||
@@ -128,11 +146,13 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||
gatewayUrl: "wss://gateway.example:8443/openclaw",
|
||||
sessionKey: "agent",
|
||||
lastActiveSessionKey: "agent",
|
||||
theme: "system",
|
||||
theme: "claw",
|
||||
themeMode: "system",
|
||||
chatFocusMode: false,
|
||||
chatShowThinking: true,
|
||||
splitRatio: 0.6,
|
||||
navCollapsed: false,
|
||||
navWidth: 220,
|
||||
navGroupsCollapsed: {},
|
||||
});
|
||||
expect(sessionStorage.length).toBe(0);
|
||||
@@ -146,18 +166,7 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||
});
|
||||
|
||||
const { loadSettings, saveSettings } = await import("./storage.ts");
|
||||
saveSettings({
|
||||
gatewayUrl: "wss://gateway.example:8443/openclaw",
|
||||
token: "session-token",
|
||||
sessionKey: "main",
|
||||
lastActiveSessionKey: "main",
|
||||
theme: "system",
|
||||
chatFocusMode: false,
|
||||
chatShowThinking: true,
|
||||
splitRatio: 0.6,
|
||||
navCollapsed: false,
|
||||
navGroupsCollapsed: {},
|
||||
});
|
||||
saveSettings(createSettings({ token: "session-token" }));
|
||||
|
||||
expect(loadSettings()).toMatchObject({
|
||||
gatewayUrl: "wss://gateway.example:8443/openclaw",
|
||||
@@ -173,18 +182,7 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||
});
|
||||
|
||||
const { loadSettings, saveSettings } = await import("./storage.ts");
|
||||
saveSettings({
|
||||
gatewayUrl: "wss://gateway.example:8443/openclaw",
|
||||
token: "gateway-a-token",
|
||||
sessionKey: "main",
|
||||
lastActiveSessionKey: "main",
|
||||
theme: "system",
|
||||
chatFocusMode: false,
|
||||
chatShowThinking: true,
|
||||
splitRatio: 0.6,
|
||||
navCollapsed: false,
|
||||
navGroupsCollapsed: {},
|
||||
});
|
||||
saveSettings(createSettings({ token: "gateway-a-token" }));
|
||||
|
||||
localStorage.setItem(
|
||||
"openclaw.control.settings.v1",
|
||||
@@ -192,11 +190,13 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||
gatewayUrl: "wss://other-gateway.example:8443/openclaw",
|
||||
sessionKey: "main",
|
||||
lastActiveSessionKey: "main",
|
||||
theme: "system",
|
||||
theme: "claw",
|
||||
themeMode: "system",
|
||||
chatFocusMode: false,
|
||||
chatShowThinking: true,
|
||||
splitRatio: 0.6,
|
||||
navCollapsed: false,
|
||||
navWidth: 220,
|
||||
navGroupsCollapsed: {},
|
||||
}),
|
||||
);
|
||||
@@ -215,18 +215,7 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||
});
|
||||
|
||||
const { loadSettings, saveSettings } = await import("./storage.ts");
|
||||
saveSettings({
|
||||
gatewayUrl: "wss://gateway.example:8443/openclaw",
|
||||
token: "memory-only-token",
|
||||
sessionKey: "main",
|
||||
lastActiveSessionKey: "main",
|
||||
theme: "system",
|
||||
chatFocusMode: false,
|
||||
chatShowThinking: true,
|
||||
splitRatio: 0.6,
|
||||
navCollapsed: false,
|
||||
navGroupsCollapsed: {},
|
||||
});
|
||||
saveSettings(createSettings({ token: "memory-only-token" }));
|
||||
expect(loadSettings()).toMatchObject({
|
||||
gatewayUrl: "wss://gateway.example:8443/openclaw",
|
||||
token: "memory-only-token",
|
||||
@@ -236,16 +225,41 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||
gatewayUrl: "wss://gateway.example:8443/openclaw",
|
||||
sessionKey: "main",
|
||||
lastActiveSessionKey: "main",
|
||||
theme: "system",
|
||||
theme: "claw",
|
||||
themeMode: "system",
|
||||
chatFocusMode: false,
|
||||
chatShowThinking: true,
|
||||
splitRatio: 0.6,
|
||||
navCollapsed: false,
|
||||
navWidth: 220,
|
||||
navGroupsCollapsed: {},
|
||||
});
|
||||
expect(sessionStorage.length).toBe(1);
|
||||
});
|
||||
|
||||
it("persists theme mode and nav width", async () => {
|
||||
setTestLocation({
|
||||
protocol: "https:",
|
||||
host: "gateway.example:8443",
|
||||
pathname: "/",
|
||||
});
|
||||
|
||||
const { loadSettings, saveSettings } = await import("./storage.ts");
|
||||
saveSettings(
|
||||
createSettings({
|
||||
theme: "dash",
|
||||
themeMode: "light",
|
||||
navWidth: 360,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(loadSettings()).toMatchObject({
|
||||
theme: "dash",
|
||||
themeMode: "light",
|
||||
navWidth: 360,
|
||||
});
|
||||
});
|
||||
|
||||
it("clears the current-tab token when saving an empty token", async () => {
|
||||
setTestLocation({
|
||||
protocol: "https:",
|
||||
@@ -254,30 +268,8 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||
});
|
||||
|
||||
const { loadSettings, saveSettings } = await import("./storage.ts");
|
||||
saveSettings({
|
||||
gatewayUrl: "wss://gateway.example:8443/openclaw",
|
||||
token: "stale-token",
|
||||
sessionKey: "main",
|
||||
lastActiveSessionKey: "main",
|
||||
theme: "system",
|
||||
chatFocusMode: false,
|
||||
chatShowThinking: true,
|
||||
splitRatio: 0.6,
|
||||
navCollapsed: false,
|
||||
navGroupsCollapsed: {},
|
||||
});
|
||||
saveSettings({
|
||||
gatewayUrl: "wss://gateway.example:8443/openclaw",
|
||||
token: "",
|
||||
sessionKey: "main",
|
||||
lastActiveSessionKey: "main",
|
||||
theme: "system",
|
||||
chatFocusMode: false,
|
||||
chatShowThinking: true,
|
||||
splitRatio: 0.6,
|
||||
navCollapsed: false,
|
||||
navGroupsCollapsed: {},
|
||||
});
|
||||
saveSettings(createSettings({ token: "stale-token" }));
|
||||
saveSettings(createSettings());
|
||||
|
||||
expect(loadSettings().token).toBe("");
|
||||
expect(sessionStorage.length).toBe(0);
|
||||
|
||||
@@ -194,10 +194,12 @@ function persistSettings(next: UiSettings) {
|
||||
sessionKey: next.sessionKey,
|
||||
lastActiveSessionKey: next.lastActiveSessionKey,
|
||||
theme: next.theme,
|
||||
themeMode: next.themeMode,
|
||||
chatFocusMode: next.chatFocusMode,
|
||||
chatShowThinking: next.chatShowThinking,
|
||||
splitRatio: next.splitRatio,
|
||||
navCollapsed: next.navCollapsed,
|
||||
navWidth: next.navWidth,
|
||||
navGroupsCollapsed: next.navGroupsCollapsed,
|
||||
...(next.locale ? { locale: next.locale } : {}),
|
||||
};
|
||||
|
||||
@@ -16,6 +16,8 @@ import { InputHistory } from "../chat/input-history.ts";
|
||||
import { normalizeMessage, normalizeRoleForGrouping } from "../chat/message-normalizer.ts";
|
||||
import { PinnedMessages } from "../chat/pinned-messages.ts";
|
||||
import { getPinnedMessageSummary } from "../chat/pinned-summary.ts";
|
||||
import { messageMatchesSearchQuery } from "../chat/search-match.ts";
|
||||
import { getOrCreateSessionCacheValue } from "../chat/session-cache.ts";
|
||||
import {
|
||||
CATEGORY_LABELS,
|
||||
SLASH_COMMANDS,
|
||||
@@ -117,30 +119,23 @@ const pinnedMessagesMap = new Map<string, PinnedMessages>();
|
||||
const deletedMessagesMap = new Map<string, DeletedMessages>();
|
||||
|
||||
function getInputHistory(sessionKey: string): InputHistory {
|
||||
let h = inputHistories.get(sessionKey);
|
||||
if (!h) {
|
||||
h = new InputHistory();
|
||||
inputHistories.set(sessionKey, h);
|
||||
}
|
||||
return h;
|
||||
return getOrCreateSessionCacheValue(inputHistories, sessionKey, () => new InputHistory());
|
||||
}
|
||||
|
||||
function getPinnedMessages(sessionKey: string): PinnedMessages {
|
||||
let p = pinnedMessagesMap.get(sessionKey);
|
||||
if (!p) {
|
||||
p = new PinnedMessages(sessionKey);
|
||||
pinnedMessagesMap.set(sessionKey, p);
|
||||
}
|
||||
return p;
|
||||
return getOrCreateSessionCacheValue(
|
||||
pinnedMessagesMap,
|
||||
sessionKey,
|
||||
() => new PinnedMessages(sessionKey),
|
||||
);
|
||||
}
|
||||
|
||||
function getDeletedMessages(sessionKey: string): DeletedMessages {
|
||||
let d = deletedMessagesMap.get(sessionKey);
|
||||
if (!d) {
|
||||
d = new DeletedMessages(sessionKey);
|
||||
deletedMessagesMap.set(sessionKey, d);
|
||||
}
|
||||
return d;
|
||||
return getOrCreateSessionCacheValue(
|
||||
deletedMessagesMap,
|
||||
sessionKey,
|
||||
() => new DeletedMessages(sessionKey),
|
||||
);
|
||||
}
|
||||
|
||||
// Module-level ephemeral UI state (reset on navigation away)
|
||||
@@ -1361,11 +1356,8 @@ function buildChatItems(props: ChatProps): Array<ChatItem | MessageGroup> {
|
||||
}
|
||||
|
||||
// Apply search filter if active
|
||||
if (searchOpen && searchQuery.trim()) {
|
||||
const text = typeof normalized.content === "string" ? normalized.content : "";
|
||||
if (!text.toLowerCase().includes(searchQuery.toLowerCase())) {
|
||||
continue;
|
||||
}
|
||||
if (searchOpen && searchQuery.trim() && !messageMatchesSearchQuery(msg, searchQuery)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
items.push({
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { html, nothing } from "lit";
|
||||
import { ref } from "lit/directives/ref.js";
|
||||
import { t } from "../../i18n/index.ts";
|
||||
import { SLASH_COMMANDS } from "../chat/slash-commands.ts";
|
||||
import { icons, type IconName } from "../icons.ts";
|
||||
|
||||
type PaletteItem = {
|
||||
@@ -12,55 +13,17 @@ type PaletteItem = {
|
||||
description?: string;
|
||||
};
|
||||
|
||||
const SLASH_PALETTE_ITEMS: PaletteItem[] = SLASH_COMMANDS.map((command) => ({
|
||||
id: `slash:${command.name}`,
|
||||
label: `/${command.name}`,
|
||||
icon: command.icon ?? "terminal",
|
||||
category: "search",
|
||||
action: `/${command.name}`,
|
||||
description: command.description,
|
||||
}));
|
||||
|
||||
const PALETTE_ITEMS: PaletteItem[] = [
|
||||
{
|
||||
id: "status",
|
||||
label: "/status",
|
||||
icon: "radio",
|
||||
category: "search",
|
||||
action: "/status",
|
||||
description: "Show current status",
|
||||
},
|
||||
{
|
||||
id: "models",
|
||||
label: "/model",
|
||||
icon: "monitor",
|
||||
category: "search",
|
||||
action: "/model",
|
||||
description: "Show/set model",
|
||||
},
|
||||
{
|
||||
id: "usage",
|
||||
label: "/usage",
|
||||
icon: "barChart",
|
||||
category: "search",
|
||||
action: "/usage",
|
||||
description: "Show usage",
|
||||
},
|
||||
{
|
||||
id: "think",
|
||||
label: "/think",
|
||||
icon: "brain",
|
||||
category: "search",
|
||||
action: "/think",
|
||||
description: "Set thinking level",
|
||||
},
|
||||
{
|
||||
id: "reset",
|
||||
label: "/reset",
|
||||
icon: "loader",
|
||||
category: "search",
|
||||
action: "/reset",
|
||||
description: "Reset session",
|
||||
},
|
||||
{
|
||||
id: "help",
|
||||
label: "/help",
|
||||
icon: "book",
|
||||
category: "search",
|
||||
action: "/help",
|
||||
description: "Show help",
|
||||
},
|
||||
...SLASH_PALETTE_ITEMS,
|
||||
{
|
||||
id: "nav-overview",
|
||||
label: "Overview",
|
||||
@@ -115,6 +78,10 @@ const PALETTE_ITEMS: PaletteItem[] = [
|
||||
},
|
||||
];
|
||||
|
||||
export function getPaletteItems(): readonly PaletteItem[] {
|
||||
return PALETTE_ITEMS;
|
||||
}
|
||||
|
||||
export type CommandPaletteProps = {
|
||||
open: boolean;
|
||||
query: string;
|
||||
|
||||
Reference in New Issue
Block a user