fix(ui): landing cleanup (#475) (thanks @rahthakor)
This commit is contained in:
@@ -68,6 +68,7 @@
|
|||||||
- Sessions: support session `label` in store/list/UI and allow `sessions_send` lookup by label. (#570) — thanks @azade-c
|
- Sessions: support session `label` in store/list/UI and allow `sessions_send` lookup by label. (#570) — thanks @azade-c
|
||||||
- Control UI: show/patch per-session reasoning level and render extracted reasoning in chat.
|
- Control UI: show/patch per-session reasoning level and render extracted reasoning in chat.
|
||||||
- Control UI: queue outgoing chat messages, add Enter-to-send, and show queued items. (#527) — thanks @YuriNachos
|
- Control UI: queue outgoing chat messages, add Enter-to-send, and show queued items. (#527) — thanks @YuriNachos
|
||||||
|
- Control UI: refactor chat layout with tool sidebar, grouped messages, and nav improvements. (#475) — thanks @rahthakor
|
||||||
- Control UI: drop explicit `ui:install` step; `ui:build` now auto-installs UI deps (docs + update flow).
|
- Control UI: drop explicit `ui:install` step; `ui:build` now auto-installs UI deps (docs + update flow).
|
||||||
- Telegram: retry long-polling conflicts with backoff to avoid fatal exits.
|
- Telegram: retry long-polling conflicts with backoff to avoid fatal exits.
|
||||||
- Telegram: fix grammY fetch type mismatch when injecting `fetch`. (#512) — thanks @YuriNachos
|
- Telegram: fix grammY fetch type mismatch when injecting `fetch`. (#512) — thanks @YuriNachos
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { beforeEach, describe, expect, it } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||||
import { resetProcessRegistryForTests } from "./bash-process-registry.js";
|
import { resetProcessRegistryForTests } from "./bash-process-registry.js";
|
||||||
import {
|
import {
|
||||||
bashTool,
|
bashTool,
|
||||||
@@ -50,6 +50,16 @@ beforeEach(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("bash tool backgrounding", () => {
|
describe("bash tool backgrounding", () => {
|
||||||
|
const originalShell = process.env.SHELL;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
if (!isWin) process.env.SHELL = "/bin/bash";
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
if (!isWin) process.env.SHELL = originalShell;
|
||||||
|
});
|
||||||
|
|
||||||
it(
|
it(
|
||||||
"backgrounds after yield and can be polled",
|
"backgrounds after yield and can be polled",
|
||||||
async () => {
|
async () => {
|
||||||
@@ -171,9 +181,7 @@ describe("bash tool backgrounding", () => {
|
|||||||
expect(text).toContain("hi");
|
expect(text).toContain("hi");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Skip: Fails when user's shell config (.zshenv) sources files that don't exist in test env,
|
it("logs line-based slices and defaults to last lines", async () => {
|
||||||
// adding extra lines to stdout and breaking line count assertions.
|
|
||||||
it.skip("logs line-based slices and defaults to last lines", async () => {
|
|
||||||
const result = await bashTool.execute("call1", {
|
const result = await bashTool.execute("call1", {
|
||||||
command: echoLines(["one", "two", "three"]),
|
command: echoLines(["one", "two", "three"]),
|
||||||
background: true,
|
background: true,
|
||||||
@@ -193,9 +201,7 @@ describe("bash tool backgrounding", () => {
|
|||||||
expect(status).toBe("completed");
|
expect(status).toBe("completed");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Skip: Fails when user's shell config (.zshenv) sources files that don't exist in test env,
|
it("supports line offsets for log slices", async () => {
|
||||||
// adding extra lines to stdout and breaking offset assertions.
|
|
||||||
it.skip("supports line offsets for log slices", async () => {
|
|
||||||
const result = await bashTool.execute("call1", {
|
const result = await bashTool.execute("call1", {
|
||||||
command: echoLines(["alpha", "beta", "gamma"]),
|
command: echoLines(["alpha", "beta", "gamma"]),
|
||||||
background: true,
|
background: true,
|
||||||
|
|||||||
@@ -6,13 +6,9 @@
|
|||||||
<title>Clawdbot Control</title>
|
<title>Clawdbot Control</title>
|
||||||
<meta name="color-scheme" content="dark light" />
|
<meta name="color-scheme" content="dark light" />
|
||||||
<link rel="icon" href="/favicon.ico" sizes="any" />
|
<link rel="icon" href="/favicon.ico" sizes="any" />
|
||||||
<!-- Flaticon Uicons - Free icon font -->
|
|
||||||
<link rel="stylesheet" href="https://cdn-uicons.flaticon.com/2.6.0/uicons-regular-rounded/css/uicons-regular-rounded.css" />
|
|
||||||
<link rel="stylesheet" href="https://cdn-uicons.flaticon.com/2.6.0/uicons-solid-rounded/css/uicons-solid-rounded.css" />
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<clawdbot-app></clawdbot-app>
|
<clawdbot-app></clawdbot-app>
|
||||||
<script type="module" src="/src/main.ts"></script>
|
<script type="module" src="/src/main.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
--shell-pad: 16px;
|
--shell-pad: 16px;
|
||||||
--shell-gap: 16px;
|
--shell-gap: 16px;
|
||||||
--shell-nav-width: 220px;
|
--shell-nav-width: 220px;
|
||||||
--shell-nav-collapsed-width: 56px;
|
|
||||||
--shell-topbar-height: 56px;
|
--shell-topbar-height: 56px;
|
||||||
--shell-focus-duration: 220ms;
|
--shell-focus-duration: 220ms;
|
||||||
--shell-focus-ease: cubic-bezier(0.2, 0.85, 0.25, 1);
|
--shell-focus-ease: cubic-bezier(0.2, 0.85, 0.25, 1);
|
||||||
@@ -19,7 +18,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.shell--nav-collapsed {
|
.shell--nav-collapsed {
|
||||||
grid-template-columns: var(--shell-nav-collapsed-width) minmax(0, 1fr);
|
grid-template-columns: 0px minmax(0, 1fr);
|
||||||
}
|
}
|
||||||
|
|
||||||
.shell--chat-focus {
|
.shell--chat-focus {
|
||||||
@@ -298,13 +297,12 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
height: calc(100vh - var(--shell-pad) * 2 - var(--topbar-height, 80px) - var(--shell-gap));
|
|
||||||
overflow-y: auto; /* Enable vertical scrolling for pages with long content */
|
overflow-y: auto; /* Enable vertical scrolling for pages with long content */
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shell--chat .content {
|
.shell--chat .content {
|
||||||
height: calc(100vh - var(--shell-pad) * 2 - var(--topbar-height, 80px) - var(--shell-gap));
|
/* No-op: keep chat layout consistent with other tabs */
|
||||||
}
|
}
|
||||||
|
|
||||||
.docs-link {
|
.docs-link {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { html, nothing } from "lit";
|
|||||||
import type { GatewayBrowserClient, GatewayHelloOk } from "./gateway";
|
import type { GatewayBrowserClient, GatewayHelloOk } from "./gateway";
|
||||||
import {
|
import {
|
||||||
TAB_GROUPS,
|
TAB_GROUPS,
|
||||||
iconClassForTab,
|
iconForTab,
|
||||||
pathForTab,
|
pathForTab,
|
||||||
subtitleForTab,
|
subtitleForTab,
|
||||||
titleForTab,
|
titleForTab,
|
||||||
@@ -608,7 +608,7 @@ function renderTab(state: AppViewState, tab: Tab) {
|
|||||||
}}
|
}}
|
||||||
title=${titleForTab(tab)}
|
title=${titleForTab(tab)}
|
||||||
>
|
>
|
||||||
<i class="nav-item__icon ${iconClassForTab(tab)}"></i>
|
<span class="nav-item__icon" aria-hidden="true">${iconForTab(tab)}</span>
|
||||||
<span class="nav-item__text">${titleForTab(tab)}</span>
|
<span class="nav-item__text">${titleForTab(tab)}</span>
|
||||||
</a>
|
</a>
|
||||||
`;
|
`;
|
||||||
@@ -620,8 +620,9 @@ function renderChatControls(state: AppViewState) {
|
|||||||
const listIcon = html`<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="8" y1="6" x2="21" y2="6"></line><line x1="8" y1="12" x2="21" y2="12"></line><line x1="8" y1="18" x2="21" y2="18"></line><line x1="3" y1="6" x2="3.01" y2="6"></line><line x1="3" y1="12" x2="3.01" y2="12"></line><line x1="3" y1="18" x2="3.01" y2="18"></line></svg>`;
|
const listIcon = html`<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="8" y1="6" x2="21" y2="6"></line><line x1="8" y1="12" x2="21" y2="12"></line><line x1="8" y1="18" x2="21" y2="18"></line><line x1="3" y1="6" x2="3.01" y2="6"></line><line x1="3" y1="12" x2="3.01" y2="12"></line><line x1="3" y1="18" x2="3.01" y2="18"></line></svg>`;
|
||||||
// Icon for grouped view
|
// Icon for grouped view
|
||||||
const groupIcon = html`<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7"></rect><rect x="14" y="3" width="7" height="7"></rect><rect x="14" y="14" width="7" height="7"></rect><rect x="3" y="14" width="7" height="7"></rect></svg>`;
|
const groupIcon = html`<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7"></rect><rect x="14" y="3" width="7" height="7"></rect><rect x="14" y="14" width="7" height="7"></rect><rect x="3" y="14" width="7" height="7"></rect></svg>`;
|
||||||
// Refresh icon (Flaticon style)
|
// Refresh icon
|
||||||
const refreshIcon = html`<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8"></path><path d="M21 3v5h-5"></path></svg>`;
|
const refreshIcon = html`<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8"></path><path d="M21 3v5h-5"></path></svg>`;
|
||||||
|
const focusIcon = html`<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7V4h3"></path><path d="M20 7V4h-3"></path><path d="M4 17v3h3"></path><path d="M20 17v3h-3"></path><circle cx="12" cy="12" r="3"></circle></svg>`;
|
||||||
return html`
|
return html`
|
||||||
<div class="chat-controls">
|
<div class="chat-controls">
|
||||||
<label class="field chat-controls__session">
|
<label class="field chat-controls__session">
|
||||||
@@ -637,7 +638,11 @@ function renderChatControls(state: AppViewState) {
|
|||||||
state.chatRunId = null;
|
state.chatRunId = null;
|
||||||
state.resetToolStream();
|
state.resetToolStream();
|
||||||
state.resetChatScroll();
|
state.resetChatScroll();
|
||||||
state.applySettings({ ...state.settings, sessionKey: next });
|
state.applySettings({
|
||||||
|
...state.settings,
|
||||||
|
sessionKey: next,
|
||||||
|
lastActiveSessionKey: next,
|
||||||
|
});
|
||||||
void loadChatHistory(state);
|
void loadChatHistory(state);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -661,6 +666,18 @@ function renderChatControls(state: AppViewState) {
|
|||||||
${refreshIcon}
|
${refreshIcon}
|
||||||
</button>
|
</button>
|
||||||
<span class="chat-controls__separator">|</span>
|
<span class="chat-controls__separator">|</span>
|
||||||
|
<button
|
||||||
|
class="btn btn--sm btn--icon ${state.settings.chatFocusMode ? "active" : ""}"
|
||||||
|
@click=${() =>
|
||||||
|
state.applySettings({
|
||||||
|
...state.settings,
|
||||||
|
chatFocusMode: !state.settings.chatFocusMode,
|
||||||
|
})}
|
||||||
|
aria-pressed=${state.settings.chatFocusMode}
|
||||||
|
title="Toggle focus mode (hide sidebar + page header)"
|
||||||
|
>
|
||||||
|
${focusIcon}
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
class="btn btn--sm btn--icon ${state.settings.useNewChatLayout ? "active" : ""}"
|
class="btn btn--sm btn--icon ${state.settings.useNewChatLayout ? "active" : ""}"
|
||||||
@click=${() =>
|
@click=${() =>
|
||||||
|
|||||||
@@ -205,6 +205,7 @@ export class ClawdbotApp extends LitElement {
|
|||||||
@state() eventLog: EventLogEntry[] = [];
|
@state() eventLog: EventLogEntry[] = [];
|
||||||
private eventLogBuffer: EventLogEntry[] = [];
|
private eventLogBuffer: EventLogEntry[] = [];
|
||||||
private toolStreamSyncTimer: number | null = null;
|
private toolStreamSyncTimer: number | null = null;
|
||||||
|
private sidebarCloseTimer: number | null = null;
|
||||||
|
|
||||||
@state() sessionKey = this.settings.sessionKey;
|
@state() sessionKey = this.settings.sessionKey;
|
||||||
@state() chatLoading = false;
|
@state() chatLoading = false;
|
||||||
@@ -1156,6 +1157,10 @@ export class ClawdbotApp extends LitElement {
|
|||||||
|
|
||||||
// Sidebar handlers for tool output viewing
|
// Sidebar handlers for tool output viewing
|
||||||
handleOpenSidebar(content: string) {
|
handleOpenSidebar(content: string) {
|
||||||
|
if (this.sidebarCloseTimer != null) {
|
||||||
|
window.clearTimeout(this.sidebarCloseTimer);
|
||||||
|
this.sidebarCloseTimer = null;
|
||||||
|
}
|
||||||
this.sidebarContent = content;
|
this.sidebarContent = content;
|
||||||
this.sidebarError = null;
|
this.sidebarError = null;
|
||||||
this.sidebarOpen = true;
|
this.sidebarOpen = true;
|
||||||
@@ -1164,9 +1169,14 @@ export class ClawdbotApp extends LitElement {
|
|||||||
handleCloseSidebar() {
|
handleCloseSidebar() {
|
||||||
this.sidebarOpen = false;
|
this.sidebarOpen = false;
|
||||||
// Clear content after transition
|
// Clear content after transition
|
||||||
setTimeout(() => {
|
if (this.sidebarCloseTimer != null) {
|
||||||
|
window.clearTimeout(this.sidebarCloseTimer);
|
||||||
|
}
|
||||||
|
this.sidebarCloseTimer = window.setTimeout(() => {
|
||||||
|
if (this.sidebarOpen) return;
|
||||||
this.sidebarContent = null;
|
this.sidebarContent = null;
|
||||||
this.sidebarError = null;
|
this.sidebarError = null;
|
||||||
|
this.sidebarCloseTimer = null;
|
||||||
}, 200);
|
}, 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,21 +16,24 @@ beforeEach(() => {
|
|||||||
// no-op: avoid real gateway WS connections in browser tests
|
// no-op: avoid real gateway WS connections in browser tests
|
||||||
};
|
};
|
||||||
window.__CLAWDBOT_CONTROL_UI_BASE_PATH__ = undefined;
|
window.__CLAWDBOT_CONTROL_UI_BASE_PATH__ = undefined;
|
||||||
|
localStorage.clear();
|
||||||
document.body.innerHTML = "";
|
document.body.innerHTML = "";
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
ClawdbotApp.prototype.connect = originalConnect;
|
ClawdbotApp.prototype.connect = originalConnect;
|
||||||
window.__CLAWDBOT_CONTROL_UI_BASE_PATH__ = undefined;
|
window.__CLAWDBOT_CONTROL_UI_BASE_PATH__ = undefined;
|
||||||
|
localStorage.clear();
|
||||||
document.body.innerHTML = "";
|
document.body.innerHTML = "";
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("chat markdown rendering", () => {
|
describe("chat markdown rendering", () => {
|
||||||
// Skip: Tool card rendering was refactored to use sidebar-based output display.
|
it("renders markdown inside tool result cards", async () => {
|
||||||
// The .chat-tool-card__output class is only in the legacy renderer and requires
|
localStorage.setItem(
|
||||||
// the <details> element to be expanded. New layout uses renderToolCard() which
|
"clawdbot.control.settings.v1",
|
||||||
// shows preview/inline text without the __output wrapper.
|
JSON.stringify({ useNewChatLayout: false }),
|
||||||
it.skip("renders markdown inside tool result cards", async () => {
|
);
|
||||||
|
|
||||||
const app = mountApp("/chat");
|
const app = mountApp("/chat");
|
||||||
await app.updateComplete;
|
await app.updateComplete;
|
||||||
|
|
||||||
|
|||||||
@@ -28,10 +28,7 @@ afterEach(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("chat focus mode", () => {
|
describe("chat focus mode", () => {
|
||||||
// Skip: Focus mode toggle button was moved to settings panel, no longer in chat view.
|
it("collapses header + sidebar on chat tab only", async () => {
|
||||||
// The shell--chat-focus class still works when settings.chatFocusMode is true,
|
|
||||||
// but there's no in-chat toggle button to test.
|
|
||||||
it.skip("collapses header + sidebar on chat tab only", async () => {
|
|
||||||
const app = mountApp("/chat");
|
const app = mountApp("/chat");
|
||||||
await app.updateComplete;
|
await app.updateComplete;
|
||||||
|
|
||||||
@@ -68,4 +65,3 @@ describe("chat focus mode", () => {
|
|||||||
expect(shell?.classList.contains("shell--chat-focus")).toBe(true);
|
expect(shell?.classList.contains("shell--chat-focus")).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest";
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
TAB_GROUPS,
|
TAB_GROUPS,
|
||||||
iconClassForTab,
|
iconForTab,
|
||||||
inferBasePathFromPathname,
|
inferBasePathFromPathname,
|
||||||
normalizeBasePath,
|
normalizeBasePath,
|
||||||
normalizePath,
|
normalizePath,
|
||||||
@@ -16,33 +16,34 @@ import {
|
|||||||
/** All valid tab identifiers derived from TAB_GROUPS */
|
/** All valid tab identifiers derived from TAB_GROUPS */
|
||||||
const ALL_TABS: Tab[] = TAB_GROUPS.flatMap((group) => group.tabs) as Tab[];
|
const ALL_TABS: Tab[] = TAB_GROUPS.flatMap((group) => group.tabs) as Tab[];
|
||||||
|
|
||||||
describe("iconClassForTab", () => {
|
describe("iconForTab", () => {
|
||||||
it("returns a non-empty string for every tab", () => {
|
it("returns a non-empty string for every tab", () => {
|
||||||
for (const tab of ALL_TABS) {
|
for (const tab of ALL_TABS) {
|
||||||
const icon = iconClassForTab(tab);
|
const icon = iconForTab(tab);
|
||||||
expect(icon).toBeTruthy();
|
expect(icon).toBeTruthy();
|
||||||
expect(typeof icon).toBe("string");
|
expect(typeof icon).toBe("string");
|
||||||
expect(icon.length).toBeGreaterThan(0);
|
expect(icon.length).toBeGreaterThan(0);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns expected icon classes for each tab", () => {
|
it("returns stable icons for known tabs", () => {
|
||||||
expect(iconClassForTab("chat")).toBe("fi fi-rr-comment");
|
expect(iconForTab("chat")).toBe("💬");
|
||||||
expect(iconClassForTab("overview")).toBe("fi fi-rr-chart-histogram");
|
expect(iconForTab("overview")).toBe("📊");
|
||||||
expect(iconClassForTab("connections")).toBe("fi fi-rr-link");
|
expect(iconForTab("connections")).toBe("🔗");
|
||||||
expect(iconClassForTab("instances")).toBe("fi fi-rr-radar");
|
expect(iconForTab("instances")).toBe("📡");
|
||||||
expect(iconClassForTab("sessions")).toBe("fi fi-rr-document");
|
expect(iconForTab("sessions")).toBe("📄");
|
||||||
expect(iconClassForTab("cron")).toBe("fi fi-rr-clock");
|
expect(iconForTab("cron")).toBe("⏰");
|
||||||
expect(iconClassForTab("skills")).toBe("fi fi-rr-bolt");
|
expect(iconForTab("skills")).toBe("⚡️");
|
||||||
expect(iconClassForTab("nodes")).toBe("fi fi-rr-computer");
|
expect(iconForTab("nodes")).toBe("🖥️");
|
||||||
expect(iconClassForTab("config")).toBe("fi fi-rr-settings");
|
expect(iconForTab("config")).toBe("⚙️");
|
||||||
expect(iconClassForTab("debug")).toBe("fi fi-rr-bug");
|
expect(iconForTab("debug")).toBe("🐞");
|
||||||
|
expect(iconForTab("logs")).toBe("🧾");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns fallback icon class for unknown tab", () => {
|
it("returns a fallback icon for unknown tab", () => {
|
||||||
// TypeScript won't allow this normally, but runtime could receive unexpected values
|
// TypeScript won't allow this normally, but runtime could receive unexpected values
|
||||||
const unknownTab = "unknown" as Tab;
|
const unknownTab = "unknown" as Tab;
|
||||||
expect(iconClassForTab(unknownTab)).toBe("fi fi-rr-file");
|
expect(iconForTab(unknownTab)).toBe("📁");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -98,42 +98,35 @@ export function inferBasePathFromPathname(pathname: string): string {
|
|||||||
return `/${segments.join("/")}`;
|
return `/${segments.join("/")}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the Flaticon uicons class for a tab icon */
|
export function iconForTab(tab: Tab): string {
|
||||||
export function iconClassForTab(tab: Tab): string {
|
|
||||||
switch (tab) {
|
switch (tab) {
|
||||||
case "chat":
|
case "chat":
|
||||||
return "fi fi-rr-comment"; // chat bubble
|
return "💬";
|
||||||
case "overview":
|
case "overview":
|
||||||
return "fi fi-rr-chart-histogram"; // bar chart
|
return "📊";
|
||||||
case "connections":
|
case "connections":
|
||||||
return "fi fi-rr-link"; // link
|
return "🔗";
|
||||||
case "instances":
|
case "instances":
|
||||||
return "fi fi-rr-radar"; // radar/satellite
|
return "📡";
|
||||||
case "sessions":
|
case "sessions":
|
||||||
return "fi fi-rr-document"; // document
|
return "📄";
|
||||||
case "cron":
|
case "cron":
|
||||||
return "fi fi-rr-clock"; // clock
|
return "⏰";
|
||||||
case "skills":
|
case "skills":
|
||||||
return "fi fi-rr-bolt"; // lightning bolt
|
return "⚡️";
|
||||||
case "nodes":
|
case "nodes":
|
||||||
return "fi fi-rr-computer"; // computer
|
return "🖥️";
|
||||||
case "config":
|
case "config":
|
||||||
return "fi fi-rr-settings"; // gear
|
return "⚙️";
|
||||||
case "debug":
|
case "debug":
|
||||||
return "fi fi-rr-bug"; // bug icon
|
return "🐞";
|
||||||
case "logs":
|
case "logs":
|
||||||
return "fi fi-rr-file-code"; // file with code
|
return "🧾";
|
||||||
default:
|
default:
|
||||||
return "fi fi-rr-file"; // generic file
|
return "📁";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @deprecated Use iconClassForTab for better icon styling */
|
|
||||||
export function iconForTab(tab: Tab): string {
|
|
||||||
// Keep backward compatibility - return empty string, icons now use CSS classes
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
export function titleForTab(tab: Tab) {
|
export function titleForTab(tab: Tab) {
|
||||||
switch (tab) {
|
switch (tab) {
|
||||||
case "overview":
|
case "overview":
|
||||||
|
|||||||
Reference in New Issue
Block a user