diff --git a/ui/src/ui/app-channels.ts b/ui/src/ui/app-channels.ts index 53b599a21c..c1e0c32a36 100644 --- a/ui/src/ui/app-channels.ts +++ b/ui/src/ui/app-channels.ts @@ -36,15 +36,23 @@ export async function handleChannelConfigReload(host: OpenClawApp) { } function parseValidationErrors(details: unknown): Record { - if (!Array.isArray(details)) {return {};} + if (!Array.isArray(details)) { + return {}; + } const errors: Record = {}; for (const entry of details) { - if (typeof entry !== "string") {continue;} + if (typeof entry !== "string") { + continue; + } const [rawField, ...rest] = entry.split(":"); - if (!rawField || rest.length === 0) {continue;} + if (!rawField || rest.length === 0) { + continue; + } const field = rawField.trim(); const message = rest.join(":").trim(); - if (field && message) {errors[field] = message;} + if (field && message) { + errors[field] = message; + } } return errors; } @@ -78,7 +86,9 @@ export function handleNostrProfileFieldChange( value: string, ) { const state = host.nostrProfileFormState; - if (!state) {return;} + if (!state) { + return; + } host.nostrProfileFormState = { ...state, values: { @@ -94,7 +104,9 @@ export function handleNostrProfileFieldChange( export function handleNostrProfileToggleAdvanced(host: OpenClawApp) { const state = host.nostrProfileFormState; - if (!state) {return;} + if (!state) { + return; + } host.nostrProfileFormState = { ...state, showAdvanced: !state.showAdvanced, @@ -103,7 +115,9 @@ export function handleNostrProfileToggleAdvanced(host: OpenClawApp) { export async function handleNostrProfileSave(host: OpenClawApp) { const state = host.nostrProfileFormState; - if (!state || state.saving) {return;} + if (!state || state.saving) { + return; + } const accountId = resolveNostrAccountId(host); host.nostrProfileFormState = { @@ -172,7 +186,9 @@ export async function handleNostrProfileSave(host: OpenClawApp) { export async function handleNostrProfileImport(host: OpenClawApp) { const state = host.nostrProfileFormState; - if (!state || state.importing) {return;} + if (!state || state.importing) { + return; + } const accountId = resolveNostrAccountId(host); host.nostrProfileFormState = { diff --git a/ui/src/ui/app-chat.ts b/ui/src/ui/app-chat.ts index bd2d1348e9..58113fd4c2 100644 --- a/ui/src/ui/app-chat.ts +++ b/ui/src/ui/app-chat.ts @@ -32,9 +32,13 @@ export function isChatBusy(host: ChatHost) { export function isChatStopCommand(text: string) { const trimmed = text.trim(); - if (!trimmed) {return false;} + if (!trimmed) { + return false; + } const normalized = trimmed.toLowerCase(); - if (normalized === "/stop") {return true;} + if (normalized === "/stop") { + return true; + } return ( normalized === "stop" || normalized === "esc" || @@ -46,14 +50,20 @@ export function isChatStopCommand(text: string) { function isChatResetCommand(text: string) { const trimmed = text.trim(); - if (!trimmed) {return false;} + if (!trimmed) { + return false; + } const normalized = trimmed.toLowerCase(); - if (normalized === "/new" || normalized === "/reset") {return true;} + if (normalized === "/new" || normalized === "/reset") { + return true; + } return normalized.startsWith("/new ") || normalized.startsWith("/reset "); } export async function handleAbortChat(host: ChatHost) { - if (!host.connected) {return;} + if (!host.connected) { + return; + } host.chatMessage = ""; await abortChatRun(host as unknown as OpenClawApp); } @@ -66,7 +76,9 @@ function enqueueChatMessage( ) { const trimmed = text.trim(); const hasAttachments = Boolean(attachments && attachments.length > 0); - if (!trimmed && !hasAttachments) {return;} + if (!trimmed && !hasAttachments) { + return; + } host.chatQueue = [ ...host.chatQueue, { @@ -123,9 +135,13 @@ async function sendChatMessageNow( } async function flushChatQueue(host: ChatHost) { - if (!host.connected || isChatBusy(host)) {return;} + if (!host.connected || isChatBusy(host)) { + return; + } const [next, ...rest] = host.chatQueue; - if (!next) {return;} + if (!next) { + return; + } host.chatQueue = rest; const ok = await sendChatMessageNow(host, next.text, { attachments: next.attachments, @@ -145,7 +161,9 @@ export async function handleSendChat( messageOverride?: string, opts?: { restoreDraft?: boolean }, ) { - if (!host.connected) {return;} + if (!host.connected) { + return; + } const previousDraft = host.chatMessage; const message = (messageOverride ?? host.chatMessage).trim(); const attachments = host.chatAttachments ?? []; @@ -153,7 +171,9 @@ export async function handleSendChat( const hasAttachments = attachmentsToSend.length > 0; // Allow sending with just attachments (no message text required) - if (!message && !hasAttachments) {return;} + if (!message && !hasAttachments) { + return; + } if (isChatStopCommand(message)) { await handleAbortChat(host); @@ -201,7 +221,9 @@ type SessionDefaultsSnapshot = { function resolveAgentIdForSession(host: ChatHost): string | null { const parsed = parseAgentSessionKey(host.sessionKey); - if (parsed?.agentId) {return parsed.agentId;} + if (parsed?.agentId) { + return parsed.agentId; + } const snapshot = host.hello?.snapshot as | { sessionDefaults?: SessionDefaultsSnapshot } | undefined; diff --git a/ui/src/ui/app-gateway.ts b/ui/src/ui/app-gateway.ts index 7ef63f9883..4c4b4c3b10 100644 --- a/ui/src/ui/app-gateway.ts +++ b/ui/src/ui/app-gateway.ts @@ -64,8 +64,12 @@ function normalizeSessionKeyForDefaults( ): string { const raw = (value ?? "").trim(); const mainSessionKey = defaults.mainSessionKey?.trim(); - if (!mainSessionKey) {return raw;} - if (!raw) {return mainSessionKey;} + if (!mainSessionKey) { + return raw; + } + if (!raw) { + return mainSessionKey; + } const mainKey = defaults.mainKey?.trim() || "main"; const defaultAgentId = defaults.defaultAgentId?.trim(); const isAlias = @@ -77,7 +81,9 @@ function normalizeSessionKeyForDefaults( } function applySessionDefaults(host: GatewayHost, defaults?: SessionDefaultsSnapshot) { - if (!defaults?.mainSessionKey) {return;} + if (!defaults?.mainSessionKey) { + return; + } const resolvedSessionKey = normalizeSessionKeyForDefaults(host.sessionKey, defaults); const resolvedSettingsSessionKey = normalizeSessionKeyForDefaults( host.settings.sessionKey, @@ -168,7 +174,9 @@ function handleGatewayEventUnsafe(host: GatewayHost, evt: GatewayEventFrame) { } if (evt.event === "agent") { - if (host.onboarding) {return;} + if (host.onboarding) { + return; + } handleAgentEvent( host as unknown as Parameters[0], evt.payload as AgentEventPayload | undefined, @@ -198,7 +206,9 @@ function handleGatewayEventUnsafe(host: GatewayHost, evt: GatewayEventFrame) { } } } - if (state === "final") {void loadChatHistory(host as unknown as OpenClawApp);} + if (state === "final") { + void loadChatHistory(host as unknown as OpenClawApp); + } return; } diff --git a/ui/src/ui/app-lifecycle.ts b/ui/src/ui/app-lifecycle.ts index d66eec7a6a..76e0696c14 100644 --- a/ui/src/ui/app-lifecycle.ts +++ b/ui/src/ui/app-lifecycle.ts @@ -75,10 +75,7 @@ export function handleUpdated(host: LifecycleHost, changed: Map[0], forcedByTab || forcedByLoad || !host.chatHasAutoScrolled, diff --git a/ui/src/ui/app-polling.ts b/ui/src/ui/app-polling.ts index 7aad75a2aa..8fb79124df 100644 --- a/ui/src/ui/app-polling.ts +++ b/ui/src/ui/app-polling.ts @@ -11,7 +11,9 @@ type PollingHost = { }; export function startNodesPolling(host: PollingHost) { - if (host.nodesPollInterval != null) {return;} + if (host.nodesPollInterval != null) { + return; + } host.nodesPollInterval = window.setInterval( () => void loadNodes(host as unknown as OpenClawApp, { quiet: true }), 5000, @@ -19,35 +21,49 @@ export function startNodesPolling(host: PollingHost) { } export function stopNodesPolling(host: PollingHost) { - if (host.nodesPollInterval == null) {return;} + if (host.nodesPollInterval == null) { + return; + } clearInterval(host.nodesPollInterval); host.nodesPollInterval = null; } export function startLogsPolling(host: PollingHost) { - if (host.logsPollInterval != null) {return;} + if (host.logsPollInterval != null) { + return; + } host.logsPollInterval = window.setInterval(() => { - if (host.tab !== "logs") {return;} + if (host.tab !== "logs") { + return; + } void loadLogs(host as unknown as OpenClawApp, { quiet: true }); }, 2000); } export function stopLogsPolling(host: PollingHost) { - if (host.logsPollInterval == null) {return;} + if (host.logsPollInterval == null) { + return; + } clearInterval(host.logsPollInterval); host.logsPollInterval = null; } export function startDebugPolling(host: PollingHost) { - if (host.debugPollInterval != null) {return;} + if (host.debugPollInterval != null) { + return; + } host.debugPollInterval = window.setInterval(() => { - if (host.tab !== "debug") {return;} + if (host.tab !== "debug") { + return; + } void loadDebug(host as unknown as OpenClawApp); }, 3000); } export function stopDebugPolling(host: PollingHost) { - if (host.debugPollInterval == null) {return;} + if (host.debugPollInterval == null) { + return; + } clearInterval(host.debugPollInterval); host.debugPollInterval = null; } diff --git a/ui/src/ui/app-render.helpers.ts b/ui/src/ui/app-render.helpers.ts index 5a8ade23b3..ac6b3d8ef0 100644 --- a/ui/src/ui/app-render.helpers.ts +++ b/ui/src/ui/app-render.helpers.ts @@ -134,7 +134,9 @@ export function renderChatControls(state: AppViewState) { class="btn btn--sm btn--icon ${showThinking ? "active" : ""}" ?disabled=${disableThinkingToggle} @click=${() => { - if (disableThinkingToggle) {return;} + if (disableThinkingToggle) { + return; + } state.applySettings({ ...state.settings, chatShowThinking: !state.settings.chatShowThinking, @@ -153,7 +155,9 @@ export function renderChatControls(state: AppViewState) { class="btn btn--sm btn--icon ${focusActive ? "active" : ""}" ?disabled=${disableFocusToggle} @click=${() => { - if (disableFocusToggle) {return;} + if (disableFocusToggle) { + return; + } state.applySettings({ ...state.settings, chatFocusMode: !state.settings.chatFocusMode, @@ -183,18 +187,28 @@ function resolveMainSessionKey( ): string | null { const snapshot = hello?.snapshot as { sessionDefaults?: SessionDefaultsSnapshot } | undefined; const mainSessionKey = snapshot?.sessionDefaults?.mainSessionKey?.trim(); - if (mainSessionKey) {return mainSessionKey;} + if (mainSessionKey) { + return mainSessionKey; + } const mainKey = snapshot?.sessionDefaults?.mainKey?.trim(); - if (mainKey) {return mainKey;} - if (sessions?.sessions?.some((row) => row.key === "main")) {return "main";} + if (mainKey) { + return mainKey; + } + if (sessions?.sessions?.some((row) => row.key === "main")) { + return "main"; + } return null; } function resolveSessionDisplayName(key: string, row?: SessionsListResult["sessions"][number]) { const label = row?.label?.trim(); - if (label) {return `${label} (${key})`;} + if (label) { + return `${label} (${key})`; + } const displayName = row?.displayName?.trim(); - if (displayName) {return displayName;} + if (displayName) { + return displayName; + } return key; } diff --git a/ui/src/ui/app-render.ts b/ui/src/ui/app-render.ts index 7d7cf7331a..2c03dd1ab2 100644 --- a/ui/src/ui/app-render.ts +++ b/ui/src/ui/app-render.ts @@ -1,24 +1,5 @@ import { html, nothing } from "lit"; import type { AppViewState } from "./app-view-state"; -import type { GatewayBrowserClient, GatewayHelloOk } from "./gateway"; -import type { UiSettings } from "./storage"; -import type { ThemeMode } from "./theme"; -import type { ThemeTransitionContext } from "./theme-transition"; -import type { - ConfigSnapshot, - CronJob, - CronRunLogEntry, - CronStatus, - HealthSnapshot, - LogEntry, - LogLevel, - PresenceEntry, - ChannelsStatusSnapshot, - SessionsListResult, - SkillStatusReport, - StatusSummary, -} from "./types"; -import type { ChatQueueItem, CronFormState } from "./ui-types"; import { parseAgentSessionKey } from "../../../src/routing/session-key.js"; import { refreshChatAvatar } from "./app-chat"; import { renderChatControls, renderTab, renderThemeToggle } from "./app-render.helpers"; @@ -63,17 +44,9 @@ import { saveSkillApiKey, updateSkillEdit, updateSkillEnabled, - type SkillMessage, } from "./controllers/skills"; import { icons } from "./icons"; -import { - TAB_GROUPS, - iconForTab, - pathForTab, - subtitleForTab, - titleForTab, - type Tab, -} from "./navigation"; +import { TAB_GROUPS, subtitleForTab, titleForTab } from "./navigation"; import { renderChannels } from "./views/channels"; import { renderChat } from "./views/chat"; import { renderConfig } from "./views/config"; @@ -98,8 +71,12 @@ function resolveAssistantAvatarUrl(state: AppViewState): string | undefined { const agent = list.find((entry) => entry.id === agentId); const identity = agent?.identity; const candidate = identity?.avatarUrl ?? identity?.avatar; - if (!candidate) {return undefined;} - if (AVATAR_DATA_RE.test(candidate) || AVATAR_HTTP_RE.test(candidate)) {return candidate;} + if (!candidate) { + return undefined; + } + if (AVATAR_DATA_RE.test(candidate) || AVATAR_HTTP_RE.test(candidate)) { + return candidate; + } return identity?.avatarUrl; } @@ -486,7 +463,9 @@ export function renderApp(state: AppViewState) { return Promise.all([loadChatHistory(state), refreshChatAvatar(state)]); }, onToggleFocusMode: () => { - if (state.onboarding) {return;} + if (state.onboarding) { + return; + } state.applySettings({ ...state.settings, chatFocusMode: !state.settings.chatFocusMode, diff --git a/ui/src/ui/app-scroll.ts b/ui/src/ui/app-scroll.ts index 85814e9c1b..b589cf2d59 100644 --- a/ui/src/ui/app-scroll.ts +++ b/ui/src/ui/app-scroll.ts @@ -12,7 +12,9 @@ type ScrollHost = { }; export function scheduleChatScroll(host: ScrollHost, force = false) { - if (host.chatScrollFrame) {cancelAnimationFrame(host.chatScrollFrame);} + if (host.chatScrollFrame) { + cancelAnimationFrame(host.chatScrollFrame); + } if (host.chatScrollTimeout != null) { clearTimeout(host.chatScrollTimeout); host.chatScrollTimeout = null; @@ -25,7 +27,9 @@ export function scheduleChatScroll(host: ScrollHost, force = false) { overflowY === "auto" || overflowY === "scroll" || container.scrollHeight - container.clientHeight > 1; - if (canScroll) {return container;} + if (canScroll) { + return container; + } } return (document.scrollingElement ?? document.documentElement) as HTMLElement | null; }; @@ -34,22 +38,32 @@ export function scheduleChatScroll(host: ScrollHost, force = false) { host.chatScrollFrame = requestAnimationFrame(() => { host.chatScrollFrame = null; const target = pickScrollTarget(); - if (!target) {return;} + if (!target) { + return; + } const distanceFromBottom = target.scrollHeight - target.scrollTop - target.clientHeight; const shouldStick = force || host.chatUserNearBottom || distanceFromBottom < 200; - if (!shouldStick) {return;} - if (force) {host.chatHasAutoScrolled = true;} + if (!shouldStick) { + return; + } + if (force) { + host.chatHasAutoScrolled = true; + } target.scrollTop = target.scrollHeight; host.chatUserNearBottom = true; const retryDelay = force ? 150 : 120; host.chatScrollTimeout = window.setTimeout(() => { host.chatScrollTimeout = null; const latest = pickScrollTarget(); - if (!latest) {return;} + if (!latest) { + return; + } const latestDistanceFromBottom = latest.scrollHeight - latest.scrollTop - latest.clientHeight; const shouldStickRetry = force || host.chatUserNearBottom || latestDistanceFromBottom < 200; - if (!shouldStickRetry) {return;} + if (!shouldStickRetry) { + return; + } latest.scrollTop = latest.scrollHeight; host.chatUserNearBottom = true; }, retryDelay); @@ -58,16 +72,22 @@ export function scheduleChatScroll(host: ScrollHost, force = false) { } export function scheduleLogsScroll(host: ScrollHost, force = false) { - if (host.logsScrollFrame) {cancelAnimationFrame(host.logsScrollFrame);} + if (host.logsScrollFrame) { + cancelAnimationFrame(host.logsScrollFrame); + } void host.updateComplete.then(() => { host.logsScrollFrame = requestAnimationFrame(() => { host.logsScrollFrame = null; const container = host.querySelector(".log-stream") as HTMLElement | null; - if (!container) {return;} + if (!container) { + return; + } const distanceFromBottom = container.scrollHeight - container.scrollTop - container.clientHeight; const shouldStick = force || distanceFromBottom < 80; - if (!shouldStick) {return;} + if (!shouldStick) { + return; + } container.scrollTop = container.scrollHeight; }); }); @@ -75,14 +95,18 @@ export function scheduleLogsScroll(host: ScrollHost, force = false) { export function handleChatScroll(host: ScrollHost, event: Event) { const container = event.currentTarget as HTMLElement | null; - if (!container) {return;} + if (!container) { + return; + } const distanceFromBottom = container.scrollHeight - container.scrollTop - container.clientHeight; host.chatUserNearBottom = distanceFromBottom < 200; } export function handleLogsScroll(host: ScrollHost, event: Event) { const container = event.currentTarget as HTMLElement | null; - if (!container) {return;} + if (!container) { + return; + } const distanceFromBottom = container.scrollHeight - container.scrollTop - container.clientHeight; host.logsAtBottom = distanceFromBottom < 80; } @@ -93,7 +117,9 @@ export function resetChatScroll(host: ScrollHost) { } export function exportLogs(lines: string[], label: string) { - if (lines.length === 0) {return;} + if (lines.length === 0) { + return; + } const blob = new Blob([`${lines.join("\n")}\n`], { type: "text/plain" }); const url = URL.createObjectURL(blob); const anchor = document.createElement("a"); @@ -105,9 +131,13 @@ export function exportLogs(lines: string[], label: string) { } export function observeTopbar(host: ScrollHost) { - if (typeof ResizeObserver === "undefined") {return;} + if (typeof ResizeObserver === "undefined") { + return; + } const topbar = host.querySelector(".topbar"); - if (!topbar) {return;} + if (!topbar) { + return; + } const update = () => { const { height } = topbar.getBoundingClientRect(); host.style.setProperty("--topbar-height", `${height}px`); diff --git a/ui/src/ui/app-settings.ts b/ui/src/ui/app-settings.ts index 7f0d01071f..58ae113fc8 100644 --- a/ui/src/ui/app-settings.ts +++ b/ui/src/ui/app-settings.ts @@ -64,13 +64,19 @@ export function applySettings(host: SettingsHost, next: UiSettings) { export function setLastActiveSessionKey(host: SettingsHost, next: string) { const trimmed = next.trim(); - if (!trimmed) {return;} - if (host.settings.lastActiveSessionKey === trimmed) {return;} + if (!trimmed) { + return; + } + if (host.settings.lastActiveSessionKey === trimmed) { + return; + } applySettings(host, { ...host.settings, lastActiveSessionKey: trimmed }); } export function applySettingsFromUrl(host: SettingsHost) { - if (!window.location.search) {return;} + if (!window.location.search) { + return; + } const params = new URLSearchParams(window.location.search); const tokenRaw = params.get("token"); const passwordRaw = params.get("password"); @@ -117,20 +123,31 @@ export function applySettingsFromUrl(host: SettingsHost) { shouldCleanUrl = true; } - if (!shouldCleanUrl) {return;} + if (!shouldCleanUrl) { + return; + } const url = new URL(window.location.href); url.search = params.toString(); window.history.replaceState({}, "", url.toString()); } export function setTab(host: SettingsHost, next: Tab) { - if (host.tab !== next) {host.tab = next;} - if (next === "chat") {host.chatHasAutoScrolled = false;} - if (next === "logs") {startLogsPolling(host as unknown as Parameters[0]);} - else {stopLogsPolling(host as unknown as Parameters[0]);} - if (next === "debug") - {startDebugPolling(host as unknown as Parameters[0]);} - else {stopDebugPolling(host as unknown as Parameters[0]);} + if (host.tab !== next) { + host.tab = next; + } + if (next === "chat") { + host.chatHasAutoScrolled = false; + } + if (next === "logs") { + startLogsPolling(host as unknown as Parameters[0]); + } else { + stopLogsPolling(host as unknown as Parameters[0]); + } + if (next === "debug") { + startDebugPolling(host as unknown as Parameters[0]); + } else { + stopDebugPolling(host as unknown as Parameters[0]); + } void refreshActiveTab(host); syncUrlWithTab(host, next, false); } @@ -150,12 +167,24 @@ export function setTheme(host: SettingsHost, next: ThemeMode, context?: ThemeTra } export async function refreshActiveTab(host: SettingsHost) { - if (host.tab === "overview") {await loadOverview(host);} - if (host.tab === "channels") {await loadChannelsTab(host);} - if (host.tab === "instances") {await loadPresence(host as unknown as OpenClawApp);} - if (host.tab === "sessions") {await loadSessions(host as unknown as OpenClawApp);} - if (host.tab === "cron") {await loadCron(host);} - if (host.tab === "skills") {await loadSkills(host as unknown as OpenClawApp);} + if (host.tab === "overview") { + await loadOverview(host); + } + if (host.tab === "channels") { + await loadChannelsTab(host); + } + if (host.tab === "instances") { + await loadPresence(host as unknown as OpenClawApp); + } + if (host.tab === "sessions") { + await loadSessions(host as unknown as OpenClawApp); + } + if (host.tab === "cron") { + await loadCron(host); + } + if (host.tab === "skills") { + await loadSkills(host as unknown as OpenClawApp); + } if (host.tab === "nodes") { await loadNodes(host as unknown as OpenClawApp); await loadDevices(host as unknown as OpenClawApp); @@ -185,7 +214,9 @@ export async function refreshActiveTab(host: SettingsHost) { } export function inferBasePath() { - if (typeof window === "undefined") {return "";} + if (typeof window === "undefined") { + return ""; + } const configured = window.__OPENCLAW_CONTROL_UI_BASE_PATH__; if (typeof configured === "string" && configured.trim()) { return normalizeBasePath(configured); @@ -200,17 +231,23 @@ export function syncThemeWithSettings(host: SettingsHost) { export function applyResolvedTheme(host: SettingsHost, resolved: ResolvedTheme) { host.themeResolved = resolved; - if (typeof document === "undefined") {return;} + if (typeof document === "undefined") { + return; + } const root = document.documentElement; root.dataset.theme = resolved; root.style.colorScheme = resolved; } export function attachThemeListener(host: SettingsHost) { - if (typeof window === "undefined" || typeof window.matchMedia !== "function") {return;} + if (typeof window === "undefined" || typeof window.matchMedia !== "function") { + return; + } host.themeMedia = window.matchMedia("(prefers-color-scheme: dark)"); host.themeMediaHandler = (event) => { - if (host.theme !== "system") {return;} + if (host.theme !== "system") { + return; + } applyResolvedTheme(host, event.matches ? "dark" : "light"); }; if (typeof host.themeMedia.addEventListener === "function") { @@ -224,7 +261,9 @@ export function attachThemeListener(host: SettingsHost) { } export function detachThemeListener(host: SettingsHost) { - if (!host.themeMedia || !host.themeMediaHandler) {return;} + if (!host.themeMedia || !host.themeMediaHandler) { + return; + } if (typeof host.themeMedia.removeEventListener === "function") { host.themeMedia.removeEventListener("change", host.themeMediaHandler); return; @@ -238,16 +277,22 @@ export function detachThemeListener(host: SettingsHost) { } export function syncTabWithLocation(host: SettingsHost, replace: boolean) { - if (typeof window === "undefined") {return;} + if (typeof window === "undefined") { + return; + } const resolved = tabFromPath(window.location.pathname, host.basePath) ?? "chat"; setTabFromRoute(host, resolved); syncUrlWithTab(host, resolved, replace); } export function onPopState(host: SettingsHost) { - if (typeof window === "undefined") {return;} + if (typeof window === "undefined") { + return; + } const resolved = tabFromPath(window.location.pathname, host.basePath); - if (!resolved) {return;} + if (!resolved) { + return; + } const url = new URL(window.location.href); const session = url.searchParams.get("session")?.trim(); @@ -264,18 +309,31 @@ export function onPopState(host: SettingsHost) { } export function setTabFromRoute(host: SettingsHost, next: Tab) { - if (host.tab !== next) {host.tab = next;} - if (next === "chat") {host.chatHasAutoScrolled = false;} - if (next === "logs") {startLogsPolling(host as unknown as Parameters[0]);} - else {stopLogsPolling(host as unknown as Parameters[0]);} - if (next === "debug") - {startDebugPolling(host as unknown as Parameters[0]);} - else {stopDebugPolling(host as unknown as Parameters[0]);} - if (host.connected) {void refreshActiveTab(host);} + if (host.tab !== next) { + host.tab = next; + } + if (next === "chat") { + host.chatHasAutoScrolled = false; + } + if (next === "logs") { + startLogsPolling(host as unknown as Parameters[0]); + } else { + stopLogsPolling(host as unknown as Parameters[0]); + } + if (next === "debug") { + startDebugPolling(host as unknown as Parameters[0]); + } else { + stopDebugPolling(host as unknown as Parameters[0]); + } + if (host.connected) { + void refreshActiveTab(host); + } } export function syncUrlWithTab(host: SettingsHost, tab: Tab, replace: boolean) { - if (typeof window === "undefined") {return;} + if (typeof window === "undefined") { + return; + } const targetPath = normalizePath(pathForTab(tab, host.basePath)); const currentPath = normalizePath(window.location.pathname); const url = new URL(window.location.href); @@ -298,11 +356,16 @@ export function syncUrlWithTab(host: SettingsHost, tab: Tab, replace: boolean) { } export function syncUrlWithSessionKey(host: SettingsHost, sessionKey: string, replace: boolean) { - if (typeof window === "undefined") {return;} + if (typeof window === "undefined") { + return; + } const url = new URL(window.location.href); url.searchParams.set("session", sessionKey); - if (replace) {window.history.replaceState({}, "", url.toString());} - else {window.history.pushState({}, "", url.toString());} + if (replace) { + window.history.replaceState({}, "", url.toString()); + } else { + window.history.pushState({}, "", url.toString()); + } } export async function loadOverview(host: SettingsHost) { diff --git a/ui/src/ui/app-tool-stream.ts b/ui/src/ui/app-tool-stream.ts index 7f47a30dbc..3439b90ea0 100644 --- a/ui/src/ui/app-tool-stream.ts +++ b/ui/src/ui/app-tool-stream.ts @@ -35,25 +35,39 @@ type ToolStreamHost = { }; function extractToolOutputText(value: unknown): string | null { - if (!value || typeof value !== "object") {return null;} + if (!value || typeof value !== "object") { + return null; + } const record = value as Record; - if (typeof record.text === "string") {return record.text;} + if (typeof record.text === "string") { + return record.text; + } const content = record.content; - if (!Array.isArray(content)) {return null;} + if (!Array.isArray(content)) { + return null; + } const parts = content .map((item) => { - if (!item || typeof item !== "object") {return null;} + if (!item || typeof item !== "object") { + return null; + } const entry = item as Record; - if (entry.type === "text" && typeof entry.text === "string") {return entry.text;} + if (entry.type === "text" && typeof entry.text === "string") { + return entry.text; + } return null; }) .filter((part): part is string => Boolean(part)); - if (parts.length === 0) {return null;} + if (parts.length === 0) { + return null; + } return parts.join("\n"); } function formatToolOutput(value: unknown): string | null { - if (value === null || value === undefined) {return null;} + if (value === null || value === undefined) { + return null; + } if (typeof value === "number" || typeof value === "boolean") { return String(value); } @@ -67,11 +81,14 @@ function formatToolOutput(value: unknown): string | null { try { text = JSON.stringify(value, null, 2); } catch { + // oxlint-disable typescript/no-base-to-string text = String(value); } } const truncated = truncateText(text, TOOL_OUTPUT_CHAR_LIMIT); - if (!truncated.truncated) {return truncated.text;} + if (!truncated.truncated) { + return truncated.text; + } return `${truncated.text}\n\n… truncated (${truncated.total} chars, showing first ${truncated.text.length}).`; } @@ -99,10 +116,14 @@ function buildToolStreamMessage(entry: ToolStreamEntry): Record } function trimToolStream(host: ToolStreamHost) { - if (host.toolStreamOrder.length <= TOOL_STREAM_LIMIT) {return;} + if (host.toolStreamOrder.length <= TOOL_STREAM_LIMIT) { + return; + } const overflow = host.toolStreamOrder.length - TOOL_STREAM_LIMIT; const removed = host.toolStreamOrder.splice(0, overflow); - for (const id of removed) {host.toolStreamById.delete(id);} + for (const id of removed) { + host.toolStreamById.delete(id); + } } function syncToolStreamMessages(host: ToolStreamHost) { @@ -124,7 +145,9 @@ export function scheduleToolStreamSync(host: ToolStreamHost, force = false) { flushToolStreamSync(host); return; } - if (host.toolStreamSyncTimer != null) {return;} + if (host.toolStreamSyncTimer != null) { + return; + } host.toolStreamSyncTimer = window.setTimeout( () => flushToolStreamSync(host), TOOL_STREAM_THROTTLE_MS, @@ -182,7 +205,9 @@ export function handleCompactionEvent(host: CompactionHost, payload: AgentEventP } export function handleAgentEvent(host: ToolStreamHost, payload?: AgentEventPayload) { - if (!payload) {return;} + if (!payload) { + return; + } // Handle compaction events if (payload.stream === "compaction") { @@ -190,17 +215,29 @@ export function handleAgentEvent(host: ToolStreamHost, payload?: AgentEventPaylo return; } - if (payload.stream !== "tool") {return;} + if (payload.stream !== "tool") { + return; + } const sessionKey = typeof payload.sessionKey === "string" ? payload.sessionKey : undefined; - if (sessionKey && sessionKey !== host.sessionKey) {return;} + if (sessionKey && sessionKey !== host.sessionKey) { + return; + } // Fallback: only accept session-less events for the active run. - if (!sessionKey && host.chatRunId && payload.runId !== host.chatRunId) {return;} - if (host.chatRunId && payload.runId !== host.chatRunId) {return;} - if (!host.chatRunId) {return;} + if (!sessionKey && host.chatRunId && payload.runId !== host.chatRunId) { + return; + } + if (host.chatRunId && payload.runId !== host.chatRunId) { + return; + } + if (!host.chatRunId) { + return; + } const data = payload.data ?? {}; const toolCallId = typeof data.toolCallId === "string" ? data.toolCallId : ""; - if (!toolCallId) {return;} + if (!toolCallId) { + return; + } const name = typeof data.name === "string" ? data.name : "tool"; const phase = typeof data.phase === "string" ? data.phase : ""; const args = phase === "start" ? data.args : undefined; @@ -229,8 +266,12 @@ export function handleAgentEvent(host: ToolStreamHost, payload?: AgentEventPaylo host.toolStreamOrder.push(toolCallId); } else { entry.name = name; - if (args !== undefined) {entry.args = args;} - if (output !== undefined) {entry.output = output;} + if (args !== undefined) { + entry.args = args; + } + if (output !== undefined) { + entry.output = output; + } entry.updatedAt = now; } diff --git a/ui/src/ui/app.ts b/ui/src/ui/app.ts index 3052dab03e..15d6c564ff 100644 --- a/ui/src/ui/app.ts +++ b/ui/src/ui/app.ts @@ -1,4 +1,4 @@ -import { LitElement, html, nothing } from "lit"; +import { LitElement } from "lit"; import { customElement, state } from "lit/decorators.js"; import type { EventLogEntry } from "./app-events"; import type { DevicePairingList } from "./controllers/devices"; @@ -84,10 +84,14 @@ declare global { const injectedAssistantIdentity = resolveInjectedAssistantIdentity(); function resolveOnboardingMode(): boolean { - if (!window.location.search) {return false;} + if (!window.location.search) { + return false; + } const params = new URLSearchParams(window.location.search); const raw = params.get("onboarding"); - if (!raw) {return false;} + if (!raw) { + return false; + } const normalized = raw.trim().toLowerCase(); return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on"; } @@ -406,7 +410,9 @@ export class OpenClawApp extends LitElement { async handleExecApprovalDecision(decision: "allow-once" | "allow-always" | "deny") { const active = this.execApprovalQueue[0]; - if (!active || !this.client || this.execApprovalBusy) {return;} + if (!active || !this.client || this.execApprovalBusy) { + return; + } this.execApprovalBusy = true; this.execApprovalError = null; try { @@ -424,7 +430,9 @@ export class OpenClawApp extends LitElement { handleGatewayUrlConfirm() { const nextGatewayUrl = this.pendingGatewayUrl; - if (!nextGatewayUrl) {return;} + if (!nextGatewayUrl) { + return; + } this.pendingGatewayUrl = null; applySettingsInternal(this as unknown as Parameters[0], { ...this.settings, @@ -455,7 +463,9 @@ export class OpenClawApp extends LitElement { window.clearTimeout(this.sidebarCloseTimer); } this.sidebarCloseTimer = window.setTimeout(() => { - if (this.sidebarOpen) {return;} + if (this.sidebarOpen) { + return; + } this.sidebarContent = null; this.sidebarError = null; this.sidebarCloseTimer = null; diff --git a/ui/src/ui/assistant-identity.ts b/ui/src/ui/assistant-identity.ts index 68c65db1be..4b9fef54e1 100644 --- a/ui/src/ui/assistant-identity.ts +++ b/ui/src/ui/assistant-identity.ts @@ -18,10 +18,16 @@ declare global { } function coerceIdentityValue(value: string | undefined, maxLength: number): string | undefined { - if (typeof value !== "string") {return undefined;} + if (typeof value !== "string") { + return undefined; + } const trimmed = value.trim(); - if (!trimmed) {return undefined;} - if (trimmed.length <= maxLength) {return trimmed;} + if (!trimmed) { + return undefined; + } + if (trimmed.length <= maxLength) { + return trimmed; + } return trimmed.slice(0, maxLength); } diff --git a/ui/src/ui/chat-markdown.browser.test.ts b/ui/src/ui/chat-markdown.browser.test.ts index 86057f3544..990ce066b9 100644 --- a/ui/src/ui/chat-markdown.browser.test.ts +++ b/ui/src/ui/chat-markdown.browser.test.ts @@ -1,6 +1,7 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { OpenClawApp } from "./app"; +// oxlint-disable-next-line typescript/unbound-method const originalConnect = OpenClawApp.prototype.connect; function mountApp(pathname: string) { diff --git a/ui/src/ui/chat/copy-as-markdown.ts b/ui/src/ui/chat/copy-as-markdown.ts index 49fc1763b1..2cd1dc4c26 100644 --- a/ui/src/ui/chat/copy-as-markdown.ts +++ b/ui/src/ui/chat/copy-as-markdown.ts @@ -13,7 +13,9 @@ type CopyButtonOptions = { }; async function copyTextToClipboard(text: string): Promise { - if (!text) {return false;} + if (!text) { + return false; + } try { await navigator.clipboard.writeText(text); @@ -38,16 +40,19 @@ function createCopyButton(options: CopyButtonOptions): TemplateResult { aria-label=${idleLabel} @click=${async (e: Event) => { const btn = e.currentTarget as HTMLButtonElement | null; - const iconContainer = btn?.querySelector(".chat-copy-btn__icon") as HTMLElement | null; - if (!btn || btn.dataset.copying === "1") {return;} + if (!btn || btn.dataset.copying === "1") { + return; + } btn.dataset.copying = "1"; btn.setAttribute("aria-busy", "true"); btn.disabled = true; const copied = await copyTextToClipboard(options.text()); - if (!btn.isConnected) {return;} + if (!btn.isConnected) { + return; + } delete btn.dataset.copying; btn.removeAttribute("aria-busy"); @@ -58,7 +63,9 @@ function createCopyButton(options: CopyButtonOptions): TemplateResult { setButtonLabel(btn, ERROR_LABEL); window.setTimeout(() => { - if (!btn.isConnected) {return;} + if (!btn.isConnected) { + return; + } delete btn.dataset.error; setButtonLabel(btn, idleLabel); }, ERROR_FOR_MS); @@ -69,7 +76,9 @@ function createCopyButton(options: CopyButtonOptions): TemplateResult { setButtonLabel(btn, COPIED_LABEL); window.setTimeout(() => { - if (!btn.isConnected) {return;} + if (!btn.isConnected) { + return; + } delete btn.dataset.copied; setButtonLabel(btn, idleLabel); }, COPIED_FOR_MS); diff --git a/ui/src/ui/chat/grouped-render.ts b/ui/src/ui/chat/grouped-render.ts index e7cb006f69..7a13d4baf2 100644 --- a/ui/src/ui/chat/grouped-render.ts +++ b/ui/src/ui/chat/grouped-render.ts @@ -24,7 +24,9 @@ function extractImages(message: unknown): ImageBlock[] { if (Array.isArray(content)) { for (const block of content) { - if (typeof block !== "object" || block === null) {continue;} + if (typeof block !== "object" || block === null) { + continue; + } const b = block as Record; if (b.type === "image") { @@ -188,12 +190,14 @@ function renderAvatar(role: string, assistant?: Pick @@ -251,7 +255,9 @@ function renderGroupedMessage( return html`${toolCards.map((card) => renderToolCardSidebar(card, onOpenSidebar))}`; } - if (!markdown && !hasToolCards && !hasImages) {return nothing;} + if (!markdown && !hasToolCards && !hasImages) { + return nothing; + } return html`
diff --git a/ui/src/ui/chat/message-extract.ts b/ui/src/ui/chat/message-extract.ts index 418692a6b7..547744bd6b 100644 --- a/ui/src/ui/chat/message-extract.ts +++ b/ui/src/ui/chat/message-extract.ts @@ -20,16 +20,24 @@ const textCache = new WeakMap(); const thinkingCache = new WeakMap(); function looksLikeEnvelopeHeader(header: string): boolean { - if (/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}Z\b/.test(header)) {return true;} - if (/\d{4}-\d{2}-\d{2} \d{2}:\d{2}\b/.test(header)) {return true;} + if (/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}Z\b/.test(header)) { + return true; + } + if (/\d{4}-\d{2}-\d{2} \d{2}:\d{2}\b/.test(header)) { + return true; + } return ENVELOPE_CHANNELS.some((label) => header.startsWith(`${label} `)); } export function stripEnvelope(text: string): string { const match = text.match(ENVELOPE_PREFIX); - if (!match) {return text;} + if (!match) { + return text; + } const header = match[1] ?? ""; - if (!looksLikeEnvelopeHeader(header)) {return text;} + if (!looksLikeEnvelopeHeader(header)) { + return text; + } return text.slice(match[0].length); } @@ -45,7 +53,9 @@ export function extractText(message: unknown): string | null { const parts = content .map((p) => { const item = p as Record; - if (item.type === "text" && typeof item.text === "string") {return item.text;} + if (item.type === "text" && typeof item.text === "string") { + return item.text; + } return null; }) .filter((v): v is string => typeof v === "string"); @@ -63,9 +73,13 @@ export function extractText(message: unknown): string | null { } export function extractTextCached(message: unknown): string | null { - if (!message || typeof message !== "object") {return extractText(message);} + if (!message || typeof message !== "object") { + return extractText(message); + } const obj = message; - if (textCache.has(obj)) {return textCache.get(obj) ?? null;} + if (textCache.has(obj)) { + return textCache.get(obj) ?? null; + } const value = extractText(message); textCache.set(obj, value); return value; @@ -80,15 +94,21 @@ export function extractThinking(message: unknown): string | null { const item = p as Record; if (item.type === "thinking" && typeof item.thinking === "string") { const cleaned = item.thinking.trim(); - if (cleaned) {parts.push(cleaned);} + if (cleaned) { + parts.push(cleaned); + } } } } - if (parts.length > 0) {return parts.join("\n");} + if (parts.length > 0) { + return parts.join("\n"); + } // Back-compat: older logs may still have tags inside text blocks. const rawText = extractRawText(message); - if (!rawText) {return null;} + if (!rawText) { + return null; + } const matches = [ ...rawText.matchAll(/<\s*think(?:ing)?\s*>([\s\S]*?)<\s*\/\s*think(?:ing)?\s*>/gi), ]; @@ -97,9 +117,13 @@ export function extractThinking(message: unknown): string | null { } export function extractThinkingCached(message: unknown): string | null { - if (!message || typeof message !== "object") {return extractThinking(message);} + if (!message || typeof message !== "object") { + return extractThinking(message); + } const obj = message; - if (thinkingCache.has(obj)) {return thinkingCache.get(obj) ?? null;} + if (thinkingCache.has(obj)) { + return thinkingCache.get(obj) ?? null; + } const value = extractThinking(message); thinkingCache.set(obj, value); return value; @@ -108,24 +132,34 @@ export function extractThinkingCached(message: unknown): string | null { export function extractRawText(message: unknown): string | null { const m = message as Record; const content = m.content; - if (typeof content === "string") {return content;} + if (typeof content === "string") { + return content; + } if (Array.isArray(content)) { const parts = content .map((p) => { const item = p as Record; - if (item.type === "text" && typeof item.text === "string") {return item.text;} + if (item.type === "text" && typeof item.text === "string") { + return item.text; + } return null; }) .filter((v): v is string => typeof v === "string"); - if (parts.length > 0) {return parts.join("\n");} + if (parts.length > 0) { + return parts.join("\n"); + } + } + if (typeof m.text === "string") { + return m.text; } - if (typeof m.text === "string") {return m.text;} return null; } export function formatReasoningMarkdown(text: string): string { const trimmed = text.trim(); - if (!trimmed) {return "";} + if (!trimmed) { + return ""; + } const lines = trimmed .split(/\r?\n/) .map((line) => line.trim()) diff --git a/ui/src/ui/chat/message-normalizer.ts b/ui/src/ui/chat/message-normalizer.ts index d585a484ec..748e1566d1 100644 --- a/ui/src/ui/chat/message-normalizer.ts +++ b/ui/src/ui/chat/message-normalizer.ts @@ -21,13 +21,11 @@ export function normalizeMessage(message: unknown): NormalizedMessage { Array.isArray(contentItems) && contentItems.some((item) => { const x = item as Record; - const t = String(x.type ?? "").toLowerCase(); + const t = (typeof x.type === "string" ? x.type : "").toLowerCase(); return t === "toolresult" || t === "tool_result"; }); - const hasToolName = - typeof (m).toolName === "string" || - typeof (m).tool_name === "string"; + const hasToolName = typeof m.toolName === "string" || typeof m.tool_name === "string"; if (hasToolId || hasToolContent || hasToolName) { role = "toolResult"; @@ -61,9 +59,15 @@ export function normalizeMessage(message: unknown): NormalizedMessage { export function normalizeRoleForGrouping(role: string): string { const lower = role.toLowerCase(); // Preserve original casing when it's already a core role. - if (role === "user" || role === "User") {return role;} - if (role === "assistant") {return "assistant";} - if (role === "system") {return "system";} + if (role === "user" || role === "User") { + return role; + } + if (role === "assistant") { + return "assistant"; + } + if (role === "system") { + return "system"; + } // Keep tool-related roles distinct so the UI can style/toggle them. if ( lower === "toolresult" || diff --git a/ui/src/ui/chat/tool-cards.ts b/ui/src/ui/chat/tool-cards.ts index dbe2231323..15a692060d 100644 --- a/ui/src/ui/chat/tool-cards.ts +++ b/ui/src/ui/chat/tool-cards.ts @@ -13,7 +13,7 @@ export function extractToolCards(message: unknown): ToolCard[] { const cards: ToolCard[] = []; for (const item of content) { - const kind = String(item.type ?? "").toLowerCase(); + const kind = (typeof item.type === "string" ? item.type : "").toLowerCase(); const isToolCall = ["toolcall", "tool_call", "tooluse", "tool_use"].includes(kind) || (typeof item.name === "string" && item.arguments != null); @@ -27,8 +27,10 @@ export function extractToolCards(message: unknown): ToolCard[] { } for (const item of content) { - const kind = String(item.type ?? "").toLowerCase(); - if (kind !== "toolresult" && kind !== "tool_result") {continue;} + const kind = (typeof item.type === "string" ? item.type : "").toLowerCase(); + if (kind !== "toolresult" && kind !== "tool_result") { + continue; + } const text = extractToolText(item); const name = typeof item.name === "string" ? item.name : "tool"; cards.push({ kind: "result", name, text }); @@ -79,7 +81,9 @@ export function renderToolCardSidebar(card: ToolCard, onOpenSidebar?: (content: @keydown=${ canClick ? (e: KeyboardEvent) => { - if (e.key !== "Enter" && e.key !== " ") {return;} + if (e.key !== "Enter" && e.key !== " ") { + return; + } e.preventDefault(); handleClick?.(); } @@ -117,15 +121,23 @@ export function renderToolCardSidebar(card: ToolCard, onOpenSidebar?: (content: } function normalizeContent(content: unknown): Array> { - if (!Array.isArray(content)) {return [];} + if (!Array.isArray(content)) { + return []; + } return content.filter(Boolean) as Array>; } function coerceArgs(value: unknown): unknown { - if (typeof value !== "string") {return value;} + if (typeof value !== "string") { + return value; + } const trimmed = value.trim(); - if (!trimmed) {return value;} - if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {return value;} + if (!trimmed) { + return value; + } + if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) { + return value; + } try { return JSON.parse(trimmed); } catch { @@ -134,7 +146,11 @@ function coerceArgs(value: unknown): unknown { } function extractToolText(item: Record): string | undefined { - if (typeof item.text === "string") {return item.text;} - if (typeof item.content === "string") {return item.content;} + if (typeof item.text === "string") { + return item.text; + } + if (typeof item.content === "string") { + return item.content; + } return undefined; } diff --git a/ui/src/ui/components/resizable-divider.ts b/ui/src/ui/components/resizable-divider.ts index d44cf79b7f..f38cd7fe84 100644 --- a/ui/src/ui/components/resizable-divider.ts +++ b/ui/src/ui/components/resizable-divider.ts @@ -74,10 +74,14 @@ export class ResizableDivider extends LitElement { }; private handleMouseMove = (e: MouseEvent) => { - if (!this.isDragging) {return;} + if (!this.isDragging) { + return; + } const container = this.parentElement; - if (!container) {return;} + if (!container) { + return; + } const containerWidth = container.getBoundingClientRect().width; const deltaX = e.clientX - this.startX; diff --git a/ui/src/ui/config-form.browser.test.ts b/ui/src/ui/config-form.browser.test.ts index 168a5dc644..37c98258d7 100644 --- a/ui/src/ui/config-form.browser.test.ts +++ b/ui/src/ui/config-form.browser.test.ts @@ -53,7 +53,9 @@ describe("config form renderer", () => { const tokenInput = container.querySelector("input[type='password']"); expect(tokenInput).not.toBeNull(); - if (!tokenInput) {return;} + if (!tokenInput) { + return; + } tokenInput.value = "abc123"; tokenInput.dispatchEvent(new Event("input", { bubbles: true })); expect(onPatch).toHaveBeenCalledWith(["gateway", "auth", "token"], "abc123"); @@ -67,7 +69,9 @@ describe("config form renderer", () => { const checkbox = container.querySelector("input[type='checkbox']"); expect(checkbox).not.toBeNull(); - if (!checkbox) {return;} + if (!checkbox) { + return; + } checkbox.checked = true; checkbox.dispatchEvent(new Event("change", { bubbles: true })); expect(onPatch).toHaveBeenCalledWith(["enabled"], true); @@ -93,9 +97,7 @@ describe("config form renderer", () => { addButton?.dispatchEvent(new MouseEvent("click", { bubbles: true })); expect(onPatch).toHaveBeenCalledWith(["allowFrom"], ["+1", ""]); - const removeButton = container.querySelector( - ".cfg-array__item-remove", - ); + const removeButton = container.querySelector(".cfg-array__item-remove"); expect(removeButton).not.toBeUndefined(); removeButton?.dispatchEvent(new MouseEvent("click", { bubbles: true })); expect(onPatch).toHaveBeenCalledWith(["allowFrom"], []); @@ -150,9 +152,7 @@ describe("config form renderer", () => { container, ); - const removeButton = container.querySelector( - ".cfg-map__item-remove", - ); + const removeButton = container.querySelector(".cfg-map__item-remove"); expect(removeButton).not.toBeUndefined(); removeButton?.dispatchEvent(new MouseEvent("click", { bubbles: true })); expect(onPatch).toHaveBeenCalledWith(["slack"], {}); diff --git a/ui/src/ui/controllers/agents.ts b/ui/src/ui/controllers/agents.ts index 93287c5d26..5d90a2b185 100644 --- a/ui/src/ui/controllers/agents.ts +++ b/ui/src/ui/controllers/agents.ts @@ -10,13 +10,19 @@ export type AgentsState = { }; export async function loadAgents(state: AgentsState) { - if (!state.client || !state.connected) {return;} - if (state.agentsLoading) {return;} + if (!state.client || !state.connected) { + return; + } + if (state.agentsLoading) { + return; + } state.agentsLoading = true; state.agentsError = null; try { - const res = (await state.client.request("agents.list", {})); - if (res) {state.agentsList = res;} + const res = await state.client.request("agents.list", {}); + if (res) { + state.agentsList = res; + } } catch (err) { state.agentsError = String(err); } finally { diff --git a/ui/src/ui/controllers/assistant-identity.ts b/ui/src/ui/controllers/assistant-identity.ts index a6f12c97de..a644495bf1 100644 --- a/ui/src/ui/controllers/assistant-identity.ts +++ b/ui/src/ui/controllers/assistant-identity.ts @@ -1,5 +1,5 @@ import type { GatewayBrowserClient } from "../gateway"; -import { normalizeAssistantIdentity, type AssistantIdentity } from "../assistant-identity"; +import { normalizeAssistantIdentity } from "../assistant-identity"; export type AssistantIdentityState = { client: GatewayBrowserClient | null; @@ -14,12 +14,16 @@ export async function loadAssistantIdentity( state: AssistantIdentityState, opts?: { sessionKey?: string }, ) { - if (!state.client || !state.connected) {return;} + if (!state.client || !state.connected) { + return; + } const sessionKey = opts?.sessionKey?.trim() || state.sessionKey.trim(); const params = sessionKey ? { sessionKey } : {}; try { - const res = (await state.client.request("agent.identity.get", params)); - if (!res) {return;} + const res = await state.client.request("agent.identity.get", params); + if (!res) { + return; + } const normalized = normalizeAssistantIdentity(res); state.assistantName = normalized.name; state.assistantAvatar = normalized.avatar; diff --git a/ui/src/ui/controllers/channels.ts b/ui/src/ui/controllers/channels.ts index 765e289151..2952faebc5 100644 --- a/ui/src/ui/controllers/channels.ts +++ b/ui/src/ui/controllers/channels.ts @@ -1,18 +1,21 @@ -import type { ChannelsStatusSnapshot } from "../types"; import type { ChannelsState } from "./channels.types"; export type { ChannelsState }; export async function loadChannels(state: ChannelsState, probe: boolean) { - if (!state.client || !state.connected) {return;} - if (state.channelsLoading) {return;} + if (!state.client || !state.connected) { + return; + } + if (state.channelsLoading) { + return; + } state.channelsLoading = true; state.channelsError = null; try { - const res = (await state.client.request("channels.status", { + const res = await state.client.request("channels.status", { probe, timeoutMs: 8000, - })); + }); state.channelsSnapshot = res; state.channelsLastSuccess = Date.now(); } catch (err) { @@ -23,13 +26,15 @@ export async function loadChannels(state: ChannelsState, probe: boolean) { } export async function startWhatsAppLogin(state: ChannelsState, force: boolean) { - if (!state.client || !state.connected || state.whatsappBusy) {return;} + if (!state.client || !state.connected || state.whatsappBusy) { + return; + } state.whatsappBusy = true; try { - const res = (await state.client.request("web.login.start", { + const res = await state.client.request("web.login.start", { force, timeoutMs: 30000, - })); + }); state.whatsappLoginMessage = res.message ?? null; state.whatsappLoginQrDataUrl = res.qrDataUrl ?? null; state.whatsappLoginConnected = null; @@ -43,15 +48,19 @@ export async function startWhatsAppLogin(state: ChannelsState, force: boolean) { } export async function waitWhatsAppLogin(state: ChannelsState) { - if (!state.client || !state.connected || state.whatsappBusy) {return;} + if (!state.client || !state.connected || state.whatsappBusy) { + return; + } state.whatsappBusy = true; try { - const res = (await state.client.request("web.login.wait", { + const res = await state.client.request("web.login.wait", { timeoutMs: 120000, - })); + }); state.whatsappLoginMessage = res.message ?? null; state.whatsappLoginConnected = res.connected ?? null; - if (res.connected) {state.whatsappLoginQrDataUrl = null;} + if (res.connected) { + state.whatsappLoginQrDataUrl = null; + } } catch (err) { state.whatsappLoginMessage = String(err); state.whatsappLoginConnected = null; @@ -61,7 +70,9 @@ export async function waitWhatsAppLogin(state: ChannelsState) { } export async function logoutWhatsApp(state: ChannelsState) { - if (!state.client || !state.connected || state.whatsappBusy) {return;} + if (!state.client || !state.connected || state.whatsappBusy) { + return; + } state.whatsappBusy = true; try { await state.client.request("channels.logout", { channel: "whatsapp" }); diff --git a/ui/src/ui/controllers/chat.ts b/ui/src/ui/controllers/chat.ts index 63d908ac0b..1d8437e1c7 100644 --- a/ui/src/ui/controllers/chat.ts +++ b/ui/src/ui/controllers/chat.ts @@ -28,14 +28,16 @@ export type ChatEventPayload = { }; export async function loadChatHistory(state: ChatState) { - if (!state.client || !state.connected) {return;} + if (!state.client || !state.connected) { + return; + } state.chatLoading = true; state.lastError = null; try { - const res = (await state.client.request("chat.history", { + const res = await state.client.request("chat.history", { sessionKey: state.sessionKey, limit: 200, - })); + }); state.chatMessages = Array.isArray(res.messages) ? res.messages : []; state.chatThinkingLevel = res.thinkingLevel ?? null; } catch (err) { @@ -47,7 +49,9 @@ export async function loadChatHistory(state: ChatState) { function dataUrlToBase64(dataUrl: string): { content: string; mimeType: string } | null { const match = /^data:([^;]+);base64,(.+)$/.exec(dataUrl); - if (!match) {return null;} + if (!match) { + return null; + } return { mimeType: match[1], content: match[2] }; } @@ -56,10 +60,14 @@ export async function sendChatMessage( message: string, attachments?: ChatAttachment[], ): Promise { - if (!state.client || !state.connected) {return null;} + if (!state.client || !state.connected) { + return null; + } const msg = message.trim(); const hasAttachments = attachments && attachments.length > 0; - if (!msg && !hasAttachments) {return null;} + if (!msg && !hasAttachments) { + return null; + } const now = Date.now(); @@ -99,7 +107,9 @@ export async function sendChatMessage( ? attachments .map((att) => { const parsed = dataUrlToBase64(att.dataUrl); - if (!parsed) {return null;} + if (!parsed) { + return null; + } return { type: "image", mimeType: parsed.mimeType, @@ -139,7 +149,9 @@ export async function sendChatMessage( } export async function abortChatRun(state: ChatState): Promise { - if (!state.client || !state.connected) {return false;} + if (!state.client || !state.connected) { + return false; + } const runId = state.chatRunId; try { await state.client.request( @@ -154,13 +166,19 @@ export async function abortChatRun(state: ChatState): Promise { } export function handleChatEvent(state: ChatState, payload?: ChatEventPayload) { - if (!payload) {return null;} - if (payload.sessionKey !== state.sessionKey) {return null;} + if (!payload) { + return null; + } + if (payload.sessionKey !== state.sessionKey) { + return null; + } // Final from another run (e.g. sub-agent announce): refresh history to show new message. // See https://github.com/openclaw/openclaw/issues/1909 if (payload.runId && state.chatRunId && payload.runId !== state.chatRunId) { - if (payload.state === "final") {return "final";} + if (payload.state === "final") { + return "final"; + } return null; } diff --git a/ui/src/ui/controllers/config.ts b/ui/src/ui/controllers/config.ts index cb4d6b5223..080c48d071 100644 --- a/ui/src/ui/controllers/config.ts +++ b/ui/src/ui/controllers/config.ts @@ -35,11 +35,13 @@ export type ConfigState = { }; export async function loadConfig(state: ConfigState) { - if (!state.client || !state.connected) {return;} + if (!state.client || !state.connected) { + return; + } state.configLoading = true; state.lastError = null; try { - const res = (await state.client.request("config.get", {})); + const res = await state.client.request("config.get", {}); applyConfigSnapshot(state, res); } catch (err) { state.lastError = String(err); @@ -49,11 +51,15 @@ export async function loadConfig(state: ConfigState) { } export async function loadConfigSchema(state: ConfigState) { - if (!state.client || !state.connected) {return;} - if (state.configSchemaLoading) {return;} + if (!state.client || !state.connected) { + return; + } + if (state.configSchemaLoading) { + return; + } state.configSchemaLoading = true; try { - const res = (await state.client.request("config.schema", {})); + const res = await state.client.request("config.schema", {}); applyConfigSchema(state, res); } catch (err) { state.lastError = String(err); @@ -94,7 +100,9 @@ export function applyConfigSnapshot(state: ConfigState, snapshot: ConfigSnapshot } export async function saveConfig(state: ConfigState) { - if (!state.client || !state.connected) {return;} + if (!state.client || !state.connected) { + return; + } state.configSaving = true; state.lastError = null; try { @@ -118,7 +126,9 @@ export async function saveConfig(state: ConfigState) { } export async function applyConfig(state: ConfigState) { - if (!state.client || !state.connected) {return;} + if (!state.client || !state.connected) { + return; + } state.configApplying = true; state.lastError = null; try { @@ -146,7 +156,9 @@ export async function applyConfig(state: ConfigState) { } export async function runUpdate(state: ConfigState) { - if (!state.client || !state.connected) {return;} + if (!state.client || !state.connected) { + return; + } state.updateRunning = true; state.lastError = null; try { diff --git a/ui/src/ui/controllers/config/form-utils.ts b/ui/src/ui/controllers/config/form-utils.ts index 6a7d95bf2f..296b666e80 100644 --- a/ui/src/ui/controllers/config/form-utils.ts +++ b/ui/src/ui/controllers/config/form-utils.ts @@ -14,19 +14,25 @@ export function setPathValue( path: Array, value: unknown, ) { - if (path.length === 0) {return;} + if (path.length === 0) { + return; + } let current: Record | unknown[] = obj; for (let i = 0; i < path.length - 1; i += 1) { const key = path[i]; const nextKey = path[i + 1]; if (typeof key === "number") { - if (!Array.isArray(current)) {return;} + if (!Array.isArray(current)) { + return; + } if (current[key] == null) { current[key] = typeof nextKey === "number" ? [] : ({} as Record); } current = current[key] as Record | unknown[]; } else { - if (typeof current !== "object" || current == null) {return;} + if (typeof current !== "object" || current == null) { + return; + } const record = current as Record; if (record[key] == null) { record[key] = typeof nextKey === "number" ? [] : ({} as Record); @@ -36,7 +42,9 @@ export function setPathValue( } const lastKey = path[path.length - 1]; if (typeof lastKey === "number") { - if (Array.isArray(current)) {current[lastKey] = value;} + if (Array.isArray(current)) { + current[lastKey] = value; + } return; } if (typeof current === "object" && current != null) { @@ -48,22 +56,32 @@ export function removePathValue( obj: Record | unknown[], path: Array, ) { - if (path.length === 0) {return;} + if (path.length === 0) { + return; + } let current: Record | unknown[] = obj; for (let i = 0; i < path.length - 1; i += 1) { const key = path[i]; if (typeof key === "number") { - if (!Array.isArray(current)) {return;} + if (!Array.isArray(current)) { + return; + } current = current[key] as Record | unknown[]; } else { - if (typeof current !== "object" || current == null) {return;} + if (typeof current !== "object" || current == null) { + return; + } current = (current as Record)[key] as Record | unknown[]; } - if (current == null) {return;} + if (current == null) { + return; + } } const lastKey = path[path.length - 1]; if (typeof lastKey === "number") { - if (Array.isArray(current)) {current.splice(lastKey, 1);} + if (Array.isArray(current)) { + current.splice(lastKey, 1); + } return; } if (typeof current === "object" && current != null) { diff --git a/ui/src/ui/controllers/cron.ts b/ui/src/ui/controllers/cron.ts index d73f638c42..51e80fc642 100644 --- a/ui/src/ui/controllers/cron.ts +++ b/ui/src/ui/controllers/cron.ts @@ -17,9 +17,11 @@ export type CronState = { }; export async function loadCronStatus(state: CronState) { - if (!state.client || !state.connected) {return;} + if (!state.client || !state.connected) { + return; + } try { - const res = (await state.client.request("cron.status", {})); + const res = await state.client.request("cron.status", {}); state.cronStatus = res; } catch (err) { state.cronError = String(err); @@ -27,14 +29,18 @@ export async function loadCronStatus(state: CronState) { } export async function loadCronJobs(state: CronState) { - if (!state.client || !state.connected) {return;} - if (state.cronLoading) {return;} + if (!state.client || !state.connected) { + return; + } + if (state.cronLoading) { + return; + } state.cronLoading = true; state.cronError = null; try { - const res = (await state.client.request("cron.list", { + const res = await state.client.request("cron.list", { includeDisabled: true, - })); + }); state.cronJobs = Array.isArray(res.jobs) ? res.jobs : []; } catch (err) { state.cronError = String(err); @@ -46,29 +52,39 @@ export async function loadCronJobs(state: CronState) { export function buildCronSchedule(form: CronFormState) { if (form.scheduleKind === "at") { const ms = Date.parse(form.scheduleAt); - if (!Number.isFinite(ms)) {throw new Error("Invalid run time.");} + if (!Number.isFinite(ms)) { + throw new Error("Invalid run time."); + } return { kind: "at" as const, atMs: ms }; } if (form.scheduleKind === "every") { const amount = toNumber(form.everyAmount, 0); - if (amount <= 0) {throw new Error("Invalid interval amount.");} + if (amount <= 0) { + throw new Error("Invalid interval amount."); + } const unit = form.everyUnit; const mult = unit === "minutes" ? 60_000 : unit === "hours" ? 3_600_000 : 86_400_000; return { kind: "every" as const, everyMs: amount * mult }; } const expr = form.cronExpr.trim(); - if (!expr) {throw new Error("Cron expression required.");} + if (!expr) { + throw new Error("Cron expression required."); + } return { kind: "cron" as const, expr, tz: form.cronTz.trim() || undefined }; } export function buildCronPayload(form: CronFormState) { if (form.payloadKind === "systemEvent") { const text = form.payloadText.trim(); - if (!text) {throw new Error("System event text required.");} + if (!text) { + throw new Error("System event text required."); + } return { kind: "systemEvent" as const, text }; } const message = form.payloadText.trim(); - if (!message) {throw new Error("Agent message required.");} + if (!message) { + throw new Error("Agent message required."); + } const payload: { kind: "agentTurn"; message: string; @@ -77,16 +93,26 @@ export function buildCronPayload(form: CronFormState) { to?: string; timeoutSeconds?: number; } = { kind: "agentTurn", message }; - if (form.deliver) {payload.deliver = true;} - if (form.channel) {payload.channel = form.channel;} - if (form.to.trim()) {payload.to = form.to.trim();} + if (form.deliver) { + payload.deliver = true; + } + if (form.channel) { + payload.channel = form.channel; + } + if (form.to.trim()) { + payload.to = form.to.trim(); + } const timeoutSeconds = toNumber(form.timeoutSeconds, 0); - if (timeoutSeconds > 0) {payload.timeoutSeconds = timeoutSeconds;} + if (timeoutSeconds > 0) { + payload.timeoutSeconds = timeoutSeconds; + } return payload; } export async function addCronJob(state: CronState) { - if (!state.client || !state.connected || state.cronBusy) {return;} + if (!state.client || !state.connected || state.cronBusy) { + return; + } state.cronBusy = true; state.cronError = null; try { @@ -107,7 +133,9 @@ export async function addCronJob(state: CronState) { ? { postToMainPrefix: state.cronForm.postToMainPrefix.trim() } : undefined, }; - if (!job.name) {throw new Error("Name required.");} + if (!job.name) { + throw new Error("Name required."); + } await state.client.request("cron.add", job); state.cronForm = { ...state.cronForm, @@ -125,7 +153,9 @@ export async function addCronJob(state: CronState) { } export async function toggleCronJob(state: CronState, job: CronJob, enabled: boolean) { - if (!state.client || !state.connected || state.cronBusy) {return;} + if (!state.client || !state.connected || state.cronBusy) { + return; + } state.cronBusy = true; state.cronError = null; try { @@ -140,7 +170,9 @@ export async function toggleCronJob(state: CronState, job: CronJob, enabled: boo } export async function runCronJob(state: CronState, job: CronJob) { - if (!state.client || !state.connected || state.cronBusy) {return;} + if (!state.client || !state.connected || state.cronBusy) { + return; + } state.cronBusy = true; state.cronError = null; try { @@ -154,7 +186,9 @@ export async function runCronJob(state: CronState, job: CronJob) { } export async function removeCronJob(state: CronState, job: CronJob) { - if (!state.client || !state.connected || state.cronBusy) {return;} + if (!state.client || !state.connected || state.cronBusy) { + return; + } state.cronBusy = true; state.cronError = null; try { @@ -173,12 +207,14 @@ export async function removeCronJob(state: CronState, job: CronJob) { } export async function loadCronRuns(state: CronState, jobId: string) { - if (!state.client || !state.connected) {return;} + if (!state.client || !state.connected) { + return; + } try { - const res = (await state.client.request("cron.runs", { + const res = await state.client.request("cron.runs", { id: jobId, limit: 50, - })); + }); state.cronRunsJobId = jobId; state.cronRuns = Array.isArray(res.entries) ? res.entries : []; } catch (err) { diff --git a/ui/src/ui/controllers/debug.ts b/ui/src/ui/controllers/debug.ts index f2da44ca37..aa467fe248 100644 --- a/ui/src/ui/controllers/debug.ts +++ b/ui/src/ui/controllers/debug.ts @@ -16,8 +16,12 @@ export type DebugState = { }; export async function loadDebug(state: DebugState) { - if (!state.client || !state.connected) {return;} - if (state.debugLoading) {return;} + if (!state.client || !state.connected) { + return; + } + if (state.debugLoading) { + return; + } state.debugLoading = true; try { const [status, health, models, heartbeat] = await Promise.all([ @@ -39,7 +43,9 @@ export async function loadDebug(state: DebugState) { } export async function callDebugMethod(state: DebugState) { - if (!state.client || !state.connected) {return;} + if (!state.client || !state.connected) { + return; + } state.debugCallError = null; state.debugCallResult = null; try { diff --git a/ui/src/ui/controllers/devices.ts b/ui/src/ui/controllers/devices.ts index 91f14e6f97..23c07a6b01 100644 --- a/ui/src/ui/controllers/devices.ts +++ b/ui/src/ui/controllers/devices.ts @@ -46,25 +46,35 @@ export type DevicesState = { }; export async function loadDevices(state: DevicesState, opts?: { quiet?: boolean }) { - if (!state.client || !state.connected) {return;} - if (state.devicesLoading) {return;} + if (!state.client || !state.connected) { + return; + } + if (state.devicesLoading) { + return; + } state.devicesLoading = true; - if (!opts?.quiet) {state.devicesError = null;} + if (!opts?.quiet) { + state.devicesError = null; + } try { - const res = (await state.client.request("device.pair.list", {})); + const res = await state.client.request("device.pair.list", {}); state.devicesList = { pending: Array.isArray(res?.pending) ? res.pending : [], paired: Array.isArray(res?.paired) ? res.paired : [], }; } catch (err) { - if (!opts?.quiet) {state.devicesError = String(err);} + if (!opts?.quiet) { + state.devicesError = String(err); + } } finally { state.devicesLoading = false; } } export async function approveDevicePairing(state: DevicesState, requestId: string) { - if (!state.client || !state.connected) {return;} + if (!state.client || !state.connected) { + return; + } try { await state.client.request("device.pair.approve", { requestId }); await loadDevices(state); @@ -74,9 +84,13 @@ export async function approveDevicePairing(state: DevicesState, requestId: strin } export async function rejectDevicePairing(state: DevicesState, requestId: string) { - if (!state.client || !state.connected) {return;} + if (!state.client || !state.connected) { + return; + } const confirmed = window.confirm("Reject this device pairing request?"); - if (!confirmed) {return;} + if (!confirmed) { + return; + } try { await state.client.request("device.pair.reject", { requestId }); await loadDevices(state); @@ -89,9 +103,11 @@ export async function rotateDeviceToken( state: DevicesState, params: { deviceId: string; role: string; scopes?: string[] }, ) { - if (!state.client || !state.connected) {return;} + if (!state.client || !state.connected) { + return; + } try { - const res = (await state.client.request("device.token.rotate", params)); + const res = await state.client.request("device.token.rotate", params); if (res?.token) { const identity = await loadOrCreateDeviceIdentity(); const role = res.role ?? params.role; @@ -115,9 +131,13 @@ export async function revokeDeviceToken( state: DevicesState, params: { deviceId: string; role: string }, ) { - if (!state.client || !state.connected) {return;} + if (!state.client || !state.connected) { + return; + } const confirmed = window.confirm(`Revoke token for ${params.deviceId} (${params.role})?`); - if (!confirmed) {return;} + if (!confirmed) { + return; + } try { await state.client.request("device.token.revoke", params); const identity = await loadOrCreateDeviceIdentity(); diff --git a/ui/src/ui/controllers/exec-approval.ts b/ui/src/ui/controllers/exec-approval.ts index e32c1a6ca0..a0b4acddeb 100644 --- a/ui/src/ui/controllers/exec-approval.ts +++ b/ui/src/ui/controllers/exec-approval.ts @@ -28,15 +28,23 @@ function isRecord(value: unknown): value is Record { } export function parseExecApprovalRequested(payload: unknown): ExecApprovalRequest | null { - if (!isRecord(payload)) {return null;} + if (!isRecord(payload)) { + return null; + } const id = typeof payload.id === "string" ? payload.id.trim() : ""; const request = payload.request; - if (!id || !isRecord(request)) {return null;} + if (!id || !isRecord(request)) { + return null; + } const command = typeof request.command === "string" ? request.command.trim() : ""; - if (!command) {return null;} + if (!command) { + return null; + } const createdAtMs = typeof payload.createdAtMs === "number" ? payload.createdAtMs : 0; const expiresAtMs = typeof payload.expiresAtMs === "number" ? payload.expiresAtMs : 0; - if (!createdAtMs || !expiresAtMs) {return null;} + if (!createdAtMs || !expiresAtMs) { + return null; + } return { id, request: { @@ -55,9 +63,13 @@ export function parseExecApprovalRequested(payload: unknown): ExecApprovalReques } export function parseExecApprovalResolved(payload: unknown): ExecApprovalResolved | null { - if (!isRecord(payload)) {return null;} + if (!isRecord(payload)) { + return null; + } const id = typeof payload.id === "string" ? payload.id.trim() : ""; - if (!id) {return null;} + if (!id) { + return null; + } return { id, decision: typeof payload.decision === "string" ? payload.decision : null, diff --git a/ui/src/ui/controllers/exec-approvals.ts b/ui/src/ui/controllers/exec-approvals.ts index 7d5e21ddff..aa6ba27011 100644 --- a/ui/src/ui/controllers/exec-approvals.ts +++ b/ui/src/ui/controllers/exec-approvals.ts @@ -56,7 +56,9 @@ function resolveExecApprovalsRpc(target?: ExecApprovalsTarget | null): { return { method: "exec.approvals.get", params: {} }; } const nodeId = target.nodeId.trim(); - if (!nodeId) {return null;} + if (!nodeId) { + return null; + } return { method: "exec.approvals.node.get", params: { nodeId } }; } @@ -68,7 +70,9 @@ function resolveExecApprovalsSaveRpc( return { method: "exec.approvals.set", params }; } const nodeId = target.nodeId.trim(); - if (!nodeId) {return null;} + if (!nodeId) { + return null; + } return { method: "exec.approvals.node.set", params: { ...params, nodeId } }; } @@ -76,8 +80,12 @@ export async function loadExecApprovals( state: ExecApprovalsState, target?: ExecApprovalsTarget | null, ) { - if (!state.client || !state.connected) {return;} - if (state.execApprovalsLoading) {return;} + if (!state.client || !state.connected) { + return; + } + if (state.execApprovalsLoading) { + return; + } state.execApprovalsLoading = true; state.lastError = null; try { @@ -86,7 +94,7 @@ export async function loadExecApprovals( state.lastError = "Select a node before loading exec approvals."; return; } - const res = (await state.client.request(rpc.method, rpc.params)); + const res = await state.client.request(rpc.method, rpc.params); applyExecApprovalsSnapshot(state, res); } catch (err) { state.lastError = String(err); @@ -109,7 +117,9 @@ export async function saveExecApprovals( state: ExecApprovalsState, target?: ExecApprovalsTarget | null, ) { - if (!state.client || !state.connected) {return;} + if (!state.client || !state.connected) { + return; + } state.execApprovalsSaving = true; state.lastError = null; try { diff --git a/ui/src/ui/controllers/logs.ts b/ui/src/ui/controllers/logs.ts index 7eee2082a5..c5d41be9a4 100644 --- a/ui/src/ui/controllers/logs.ts +++ b/ui/src/ui/controllers/logs.ts @@ -19,12 +19,18 @@ const LOG_BUFFER_LIMIT = 2000; const LEVELS = new Set(["trace", "debug", "info", "warn", "error", "fatal"]); function parseMaybeJsonString(value: unknown) { - if (typeof value !== "string") {return null;} + if (typeof value !== "string") { + return null; + } const trimmed = value.trim(); - if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) {return null;} + if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) { + return null; + } try { const parsed = JSON.parse(trimmed) as unknown; - if (!parsed || typeof parsed !== "object") {return null;} + if (!parsed || typeof parsed !== "object") { + return null; + } return parsed as Record; } catch { return null; @@ -32,13 +38,17 @@ function parseMaybeJsonString(value: unknown) { } function normalizeLevel(value: unknown): LogLevel | null { - if (typeof value !== "string") {return null;} + if (typeof value !== "string") { + return null; + } const lowered = value.toLowerCase() as LogLevel; return LEVELS.has(lowered) ? lowered : null; } export function parseLogLine(line: string): LogEntry { - if (!line.trim()) {return { raw: line, message: line };} + if (!line.trim()) { + return { raw: line, message: line }; + } try { const obj = JSON.parse(line) as Record; const meta = @@ -50,25 +60,28 @@ export function parseLogLine(line: string): LogEntry { const level = normalizeLevel(meta?.logLevelName ?? meta?.level); const contextCandidate = - typeof obj["0"] === "string" - ? (obj["0"]) - : typeof meta?.name === "string" - ? (meta?.name) - : null; + typeof obj["0"] === "string" ? obj["0"] : typeof meta?.name === "string" ? meta?.name : null; const contextObj = parseMaybeJsonString(contextCandidate); let subsystem: string | null = null; if (contextObj) { - if (typeof contextObj.subsystem === "string") {subsystem = contextObj.subsystem;} - else if (typeof contextObj.module === "string") {subsystem = contextObj.module;} + if (typeof contextObj.subsystem === "string") { + subsystem = contextObj.subsystem; + } else if (typeof contextObj.module === "string") { + subsystem = contextObj.module; + } } if (!subsystem && contextCandidate && contextCandidate.length < 120) { subsystem = contextCandidate; } let message: string | null = null; - if (typeof obj["1"] === "string") {message = obj["1"];} - else if (!contextObj && typeof obj["0"] === "string") {message = obj["0"];} - else if (typeof obj.message === "string") {message = obj.message;} + if (typeof obj["1"] === "string") { + message = obj["1"]; + } else if (!contextObj && typeof obj["0"] === "string") { + message = obj["0"]; + } else if (typeof obj.message === "string") { + message = obj.message; + } return { raw: line, @@ -84,9 +97,15 @@ export function parseLogLine(line: string): LogEntry { } export async function loadLogs(state: LogsState, opts?: { reset?: boolean; quiet?: boolean }) { - if (!state.client || !state.connected) {return;} - if (state.logsLoading && !opts?.quiet) {return;} - if (!opts?.quiet) {state.logsLoading = true;} + if (!state.client || !state.connected) { + return; + } + if (state.logsLoading && !opts?.quiet) { + return; + } + if (!opts?.quiet) { + state.logsLoading = true; + } state.logsError = null; try { const res = await state.client.request("logs.tail", { @@ -103,20 +122,26 @@ export async function loadLogs(state: LogsState, opts?: { reset?: boolean; quiet reset?: boolean; }; const lines = Array.isArray(payload.lines) - ? (payload.lines.filter((line) => typeof line === "string")) + ? payload.lines.filter((line) => typeof line === "string") : []; const entries = lines.map(parseLogLine); const shouldReset = Boolean(opts?.reset || payload.reset || state.logsCursor == null); state.logsEntries = shouldReset ? entries : [...state.logsEntries, ...entries].slice(-LOG_BUFFER_LIMIT); - if (typeof payload.cursor === "number") {state.logsCursor = payload.cursor;} - if (typeof payload.file === "string") {state.logsFile = payload.file;} + if (typeof payload.cursor === "number") { + state.logsCursor = payload.cursor; + } + if (typeof payload.file === "string") { + state.logsFile = payload.file; + } state.logsTruncated = Boolean(payload.truncated); state.logsLastFetchAt = Date.now(); } catch (err) { state.logsError = String(err); } finally { - if (!opts?.quiet) {state.logsLoading = false;} + if (!opts?.quiet) { + state.logsLoading = false; + } } } diff --git a/ui/src/ui/controllers/nodes.ts b/ui/src/ui/controllers/nodes.ts index 9044575388..711a6034c1 100644 --- a/ui/src/ui/controllers/nodes.ts +++ b/ui/src/ui/controllers/nodes.ts @@ -9,15 +9,23 @@ export type NodesState = { }; export async function loadNodes(state: NodesState, opts?: { quiet?: boolean }) { - if (!state.client || !state.connected) {return;} - if (state.nodesLoading) {return;} + if (!state.client || !state.connected) { + return; + } + if (state.nodesLoading) { + return; + } state.nodesLoading = true; - if (!opts?.quiet) {state.lastError = null;} + if (!opts?.quiet) { + state.lastError = null; + } try { - const res = (await state.client.request("node.list", {})); + const res = await state.client.request("node.list", {}); state.nodes = Array.isArray(res.nodes) ? res.nodes : []; } catch (err) { - if (!opts?.quiet) {state.lastError = String(err);} + if (!opts?.quiet) { + state.lastError = String(err); + } } finally { state.nodesLoading = false; } diff --git a/ui/src/ui/controllers/presence.ts b/ui/src/ui/controllers/presence.ts index 8e50afd612..676fff8279 100644 --- a/ui/src/ui/controllers/presence.ts +++ b/ui/src/ui/controllers/presence.ts @@ -11,13 +11,17 @@ export type PresenceState = { }; export async function loadPresence(state: PresenceState) { - if (!state.client || !state.connected) {return;} - if (state.presenceLoading) {return;} + if (!state.client || !state.connected) { + return; + } + if (state.presenceLoading) { + return; + } state.presenceLoading = true; state.presenceError = null; state.presenceStatus = null; try { - const res = (await state.client.request("system-presence", {})); + const res = await state.client.request("system-presence", {}); if (Array.isArray(res)) { state.presenceEntries = res; state.presenceStatus = res.length === 0 ? "No instances yet." : null; diff --git a/ui/src/ui/controllers/sessions.ts b/ui/src/ui/controllers/sessions.ts index bb9a0d8b03..112d990bd4 100644 --- a/ui/src/ui/controllers/sessions.ts +++ b/ui/src/ui/controllers/sessions.ts @@ -23,8 +23,12 @@ export async function loadSessions( includeUnknown?: boolean; }, ) { - if (!state.client || !state.connected) {return;} - if (state.sessionsLoading) {return;} + if (!state.client || !state.connected) { + return; + } + if (state.sessionsLoading) { + return; + } state.sessionsLoading = true; state.sessionsError = null; try { @@ -36,10 +40,16 @@ export async function loadSessions( includeGlobal, includeUnknown, }; - if (activeMinutes > 0) {params.activeMinutes = activeMinutes;} - if (limit > 0) {params.limit = limit;} - const res = (await state.client.request("sessions.list", params)); - if (res) {state.sessionsResult = res;} + if (activeMinutes > 0) { + params.activeMinutes = activeMinutes; + } + if (limit > 0) { + params.limit = limit; + } + const res = await state.client.request("sessions.list", params); + if (res) { + state.sessionsResult = res; + } } catch (err) { state.sessionsError = String(err); } finally { @@ -57,12 +67,22 @@ export async function patchSession( reasoningLevel?: string | null; }, ) { - if (!state.client || !state.connected) {return;} + if (!state.client || !state.connected) { + return; + } const params: Record = { key }; - if ("label" in patch) {params.label = patch.label;} - if ("thinkingLevel" in patch) {params.thinkingLevel = patch.thinkingLevel;} - if ("verboseLevel" in patch) {params.verboseLevel = patch.verboseLevel;} - if ("reasoningLevel" in patch) {params.reasoningLevel = patch.reasoningLevel;} + if ("label" in patch) { + params.label = patch.label; + } + if ("thinkingLevel" in patch) { + params.thinkingLevel = patch.thinkingLevel; + } + if ("verboseLevel" in patch) { + params.verboseLevel = patch.verboseLevel; + } + if ("reasoningLevel" in patch) { + params.reasoningLevel = patch.reasoningLevel; + } try { await state.client.request("sessions.patch", params); await loadSessions(state); @@ -72,12 +92,18 @@ export async function patchSession( } export async function deleteSession(state: SessionsState, key: string) { - if (!state.client || !state.connected) {return;} - if (state.sessionsLoading) {return;} + if (!state.client || !state.connected) { + return; + } + if (state.sessionsLoading) { + return; + } const confirmed = window.confirm( `Delete session "${key}"?\n\nDeletes the session entry and archives its transcript.`, ); - if (!confirmed) {return;} + if (!confirmed) { + return; + } state.sessionsLoading = true; state.sessionsError = null; try { diff --git a/ui/src/ui/controllers/skills.ts b/ui/src/ui/controllers/skills.ts index de844ab40d..568a296472 100644 --- a/ui/src/ui/controllers/skills.ts +++ b/ui/src/ui/controllers/skills.ts @@ -24,15 +24,22 @@ type LoadSkillsOptions = { }; function setSkillMessage(state: SkillsState, key: string, message?: SkillMessage) { - if (!key.trim()) {return;} + if (!key.trim()) { + return; + } const next = { ...state.skillMessages }; - if (message) {next[key] = message;} - else {delete next[key];} + if (message) { + next[key] = message; + } else { + delete next[key]; + } state.skillMessages = next; } function getErrorMessage(err: unknown) { - if (err instanceof Error) {return err.message;} + if (err instanceof Error) { + return err.message; + } return String(err); } @@ -40,13 +47,19 @@ export async function loadSkills(state: SkillsState, options?: LoadSkillsOptions if (options?.clearMessages && Object.keys(state.skillMessages).length > 0) { state.skillMessages = {}; } - if (!state.client || !state.connected) {return;} - if (state.skillsLoading) {return;} + if (!state.client || !state.connected) { + return; + } + if (state.skillsLoading) { + return; + } state.skillsLoading = true; state.skillsError = null; try { - const res = (await state.client.request("skills.status", {})); - if (res) {state.skillsReport = res;} + const res = await state.client.request("skills.status", {}); + if (res) { + state.skillsReport = res; + } } catch (err) { state.skillsError = getErrorMessage(err); } finally { @@ -59,7 +72,9 @@ export function updateSkillEdit(state: SkillsState, skillKey: string, value: str } export async function updateSkillEnabled(state: SkillsState, skillKey: string, enabled: boolean) { - if (!state.client || !state.connected) {return;} + if (!state.client || !state.connected) { + return; + } state.skillsBusyKey = skillKey; state.skillsError = null; try { @@ -82,7 +97,9 @@ export async function updateSkillEnabled(state: SkillsState, skillKey: string, e } export async function saveSkillApiKey(state: SkillsState, skillKey: string) { - if (!state.client || !state.connected) {return;} + if (!state.client || !state.connected) { + return; + } state.skillsBusyKey = skillKey; state.skillsError = null; try { @@ -111,15 +128,17 @@ export async function installSkill( name: string, installId: string, ) { - if (!state.client || !state.connected) {return;} + if (!state.client || !state.connected) { + return; + } state.skillsBusyKey = skillKey; state.skillsError = null; try { - const result = (await state.client.request("skills.install", { + const result = await state.client.request("skills.install", { name, installId, timeoutMs: 120000, - })); + }); await loadSkills(state); setSkillMessage(state, skillKey, { kind: "success", diff --git a/ui/src/ui/device-auth.ts b/ui/src/ui/device-auth.ts index af96ef5723..97ace4962d 100644 --- a/ui/src/ui/device-auth.ts +++ b/ui/src/ui/device-auth.ts @@ -18,11 +18,15 @@ function normalizeRole(role: string): string { } function normalizeScopes(scopes: string[] | undefined): string[] { - if (!Array.isArray(scopes)) {return [];} + if (!Array.isArray(scopes)) { + return []; + } const out = new Set(); for (const scope of scopes) { const trimmed = scope.trim(); - if (trimmed) {out.add(trimmed);} + if (trimmed) { + out.add(trimmed); + } } return [...out].toSorted(); } @@ -30,11 +34,19 @@ function normalizeScopes(scopes: string[] | undefined): string[] { function readStore(): DeviceAuthStore | null { try { const raw = window.localStorage.getItem(STORAGE_KEY); - if (!raw) {return null;} + if (!raw) { + return null; + } const parsed = JSON.parse(raw) as DeviceAuthStore; - if (!parsed || parsed.version !== 1) {return null;} - if (!parsed.deviceId || typeof parsed.deviceId !== "string") {return null;} - if (!parsed.tokens || typeof parsed.tokens !== "object") {return null;} + if (!parsed || parsed.version !== 1) { + return null; + } + if (!parsed.deviceId || typeof parsed.deviceId !== "string") { + return null; + } + if (!parsed.tokens || typeof parsed.tokens !== "object") { + return null; + } return parsed; } catch { return null; @@ -54,10 +66,14 @@ export function loadDeviceAuthToken(params: { role: string; }): DeviceAuthEntry | null { const store = readStore(); - if (!store || store.deviceId !== params.deviceId) {return null;} + if (!store || store.deviceId !== params.deviceId) { + return null; + } const role = normalizeRole(params.role); const entry = store.tokens[role]; - if (!entry || typeof entry.token !== "string") {return null;} + if (!entry || typeof entry.token !== "string") { + return null; + } return entry; } @@ -90,9 +106,13 @@ export function storeDeviceAuthToken(params: { export function clearDeviceAuthToken(params: { deviceId: string; role: string }) { const store = readStore(); - if (!store || store.deviceId !== params.deviceId) {return;} + if (!store || store.deviceId !== params.deviceId) { + return; + } const role = normalizeRole(params.role); - if (!store.tokens[role]) {return;} + if (!store.tokens[role]) { + return; + } const next = { ...store, tokens: { ...store.tokens } }; delete next.tokens[role]; writeStore(next); diff --git a/ui/src/ui/device-identity.ts b/ui/src/ui/device-identity.ts index fd03dafcad..f6febf6d3d 100644 --- a/ui/src/ui/device-identity.ts +++ b/ui/src/ui/device-identity.ts @@ -18,7 +18,9 @@ const STORAGE_KEY = "openclaw-device-identity-v1"; function base64UrlEncode(bytes: Uint8Array): string { let binary = ""; - for (const byte of bytes) {binary += String.fromCharCode(byte);} + for (const byte of bytes) { + binary += String.fromCharCode(byte); + } return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/g, ""); } @@ -27,7 +29,9 @@ function base64UrlDecode(input: string): Uint8Array { const padded = normalized + "=".repeat((4 - (normalized.length % 4)) % 4); const binary = atob(padded); const out = new Uint8Array(binary.length); - for (let i = 0; i < binary.length; i += 1) {out[i] = binary.charCodeAt(i);} + for (let i = 0; i < binary.length; i += 1) { + out[i] = binary.charCodeAt(i); + } return out; } diff --git a/ui/src/ui/focus-mode.browser.test.ts b/ui/src/ui/focus-mode.browser.test.ts index 1e8164d85b..cfa1f0ef9e 100644 --- a/ui/src/ui/focus-mode.browser.test.ts +++ b/ui/src/ui/focus-mode.browser.test.ts @@ -1,6 +1,7 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { OpenClawApp } from "./app"; +// oxlint-disable-next-line typescript/unbound-method const originalConnect = OpenClawApp.prototype.connect; function mountApp(pathname: string) { diff --git a/ui/src/ui/format.ts b/ui/src/ui/format.ts index 29327a1a4e..d1073b8f80 100644 --- a/ui/src/ui/format.ts +++ b/ui/src/ui/format.ts @@ -1,44 +1,70 @@ import { stripReasoningTagsFromText } from "../../../src/shared/text/reasoning-tags.js"; export function formatMs(ms?: number | null): string { - if (!ms && ms !== 0) {return "n/a";} + if (!ms && ms !== 0) { + return "n/a"; + } return new Date(ms).toLocaleString(); } export function formatAgo(ms?: number | null): string { - if (!ms && ms !== 0) {return "n/a";} + if (!ms && ms !== 0) { + return "n/a"; + } const diff = Date.now() - ms; - if (diff < 0) {return "just now";} + if (diff < 0) { + return "just now"; + } const sec = Math.round(diff / 1000); - if (sec < 60) {return `${sec}s ago`;} + if (sec < 60) { + return `${sec}s ago`; + } const min = Math.round(sec / 60); - if (min < 60) {return `${min}m ago`;} + if (min < 60) { + return `${min}m ago`; + } const hr = Math.round(min / 60); - if (hr < 48) {return `${hr}h ago`;} + if (hr < 48) { + return `${hr}h ago`; + } const day = Math.round(hr / 24); return `${day}d ago`; } export function formatDurationMs(ms?: number | null): string { - if (!ms && ms !== 0) {return "n/a";} - if (ms < 1000) {return `${ms}ms`;} + if (!ms && ms !== 0) { + return "n/a"; + } + if (ms < 1000) { + return `${ms}ms`; + } const sec = Math.round(ms / 1000); - if (sec < 60) {return `${sec}s`;} + if (sec < 60) { + return `${sec}s`; + } const min = Math.round(sec / 60); - if (min < 60) {return `${min}m`;} + if (min < 60) { + return `${min}m`; + } const hr = Math.round(min / 60); - if (hr < 48) {return `${hr}h`;} + if (hr < 48) { + return `${hr}h`; + } const day = Math.round(hr / 24); return `${day}d`; } export function formatList(values?: Array): string { - if (!values || values.length === 0) {return "none";} + if (!values || values.length === 0) { + return "none"; + } return values.filter((v): v is string => Boolean(v && v.trim())).join(", "); } export function clampText(value: string, max = 120): string { - if (value.length <= max) {return value;} + if (value.length <= max) { + return value; + } return `${value.slice(0, Math.max(0, max - 1))}…`; } diff --git a/ui/src/ui/gateway.ts b/ui/src/ui/gateway.ts index 4343fc75fa..caaed975b4 100644 --- a/ui/src/ui/gateway.ts +++ b/ui/src/ui/gateway.ts @@ -91,36 +91,44 @@ export class GatewayBrowserClient { } private connect() { - if (this.closed) {return;} + if (this.closed) { + return; + } this.ws = new WebSocket(this.opts.url); - this.ws.onopen = () => this.queueConnect(); - this.ws.onmessage = (ev) => this.handleMessage(String(ev.data ?? "")); - this.ws.onclose = (ev) => { + this.ws.addEventListener("open", () => this.queueConnect()); + this.ws.addEventListener("message", (ev) => this.handleMessage(String(ev.data ?? ""))); + this.ws.addEventListener("close", (ev) => { const reason = String(ev.reason ?? ""); this.ws = null; this.flushPending(new Error(`gateway closed (${ev.code}): ${reason}`)); this.opts.onClose?.({ code: ev.code, reason }); this.scheduleReconnect(); - }; - this.ws.onerror = () => { + }); + this.ws.addEventListener("error", () => { // ignored; close handler will fire - }; + }); } private scheduleReconnect() { - if (this.closed) {return;} + if (this.closed) { + return; + } const delay = this.backoffMs; this.backoffMs = Math.min(this.backoffMs * 1.7, 15_000); window.setTimeout(() => this.connect(), delay); } private flushPending(err: Error) { - for (const [, p] of this.pending) {p.reject(err);} + for (const [, p] of this.pending) { + p.reject(err); + } this.pending.clear(); } private async sendConnect() { - if (this.connectSent) {return;} + if (this.connectSent) { + return; + } this.connectSent = true; if (this.connectTimer !== null) { window.clearTimeout(this.connectTimer); @@ -265,10 +273,15 @@ export class GatewayBrowserClient { if (frame.type === "res") { const res = parsed as GatewayResponseFrame; const pending = this.pending.get(res.id); - if (!pending) {return;} + if (!pending) { + return; + } this.pending.delete(res.id); - if (res.ok) {pending.resolve(res.payload);} - else {pending.reject(new Error(res.error?.message ?? "request failed"));} + if (res.ok) { + pending.resolve(res.payload); + } else { + pending.reject(new Error(res.error?.message ?? "request failed")); + } return; } } @@ -289,7 +302,9 @@ export class GatewayBrowserClient { private queueConnect() { this.connectNonce = null; this.connectSent = false; - if (this.connectTimer !== null) {window.clearTimeout(this.connectTimer);} + if (this.connectTimer !== null) { + window.clearTimeout(this.connectTimer); + } this.connectTimer = window.setTimeout(() => { void this.sendConnect(); }, 750); diff --git a/ui/src/ui/icons.ts b/ui/src/ui/icons.ts index fafc96504b..9b6ac275b3 100644 --- a/ui/src/ui/icons.ts +++ b/ui/src/ui/icons.ts @@ -243,6 +243,8 @@ export function renderEmojiIcon( } export function setEmojiIcon(target: HTMLElement | null, icon: string): void { - if (!target) {return;} + if (!target) { + return; + } target.textContent = icon; } diff --git a/ui/src/ui/markdown.ts b/ui/src/ui/markdown.ts index 7a246d4792..d78b7e3fd6 100644 --- a/ui/src/ui/markdown.ts +++ b/ui/src/ui/markdown.ts @@ -47,7 +47,9 @@ const markdownCache = new Map(); function getCachedMarkdown(key: string): string | null { const cached = markdownCache.get(key); - if (cached === undefined) {return null;} + if (cached === undefined) { + return null; + } markdownCache.delete(key); markdownCache.set(key, cached); return cached; @@ -55,19 +57,29 @@ function getCachedMarkdown(key: string): string | null { function setCachedMarkdown(key: string, value: string) { markdownCache.set(key, value); - if (markdownCache.size <= MARKDOWN_CACHE_LIMIT) {return;} + if (markdownCache.size <= MARKDOWN_CACHE_LIMIT) { + return; + } const oldest = markdownCache.keys().next().value; - if (oldest) {markdownCache.delete(oldest);} + if (oldest) { + markdownCache.delete(oldest); + } } function installHooks() { - if (hooksInstalled) {return;} + if (hooksInstalled) { + return; + } hooksInstalled = true; DOMPurify.addHook("afterSanitizeAttributes", (node) => { - if (!(node instanceof HTMLAnchorElement)) {return;} + if (!(node instanceof HTMLAnchorElement)) { + return; + } const href = node.getAttribute("href"); - if (!href) {return;} + if (!href) { + return; + } node.setAttribute("rel", "noreferrer noopener"); node.setAttribute("target", "_blank"); }); @@ -75,11 +87,15 @@ function installHooks() { export function toSanitizedMarkdownHtml(markdown: string): string { const input = markdown.trim(); - if (!input) {return "";} + if (!input) { + return ""; + } installHooks(); if (input.length <= MARKDOWN_CACHE_MAX_CHARS) { const cached = getCachedMarkdown(input); - if (cached !== null) {return cached;} + if (cached !== null) { + return cached; + } } const truncated = truncateText(input, MARKDOWN_CHAR_LIMIT); const suffix = truncated.truncated diff --git a/ui/src/ui/navigation.browser.test.ts b/ui/src/ui/navigation.browser.test.ts index 3120fb8898..84945e969f 100644 --- a/ui/src/ui/navigation.browser.test.ts +++ b/ui/src/ui/navigation.browser.test.ts @@ -2,6 +2,7 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { OpenClawApp } from "./app"; import "../styles.css"; +// oxlint-disable-next-line typescript/unbound-method const originalConnect = OpenClawApp.prototype.connect; function mountApp(pathname: string) { @@ -117,7 +118,9 @@ describe("control UI routing", () => { const initialContainer = app.querySelector(".chat-thread"); expect(initialContainer).not.toBeNull(); - if (!initialContainer) {return;} + if (!initialContainer) { + return; + } initialContainer.style.maxHeight = "180px"; initialContainer.style.overflow = "auto"; @@ -134,11 +137,15 @@ describe("control UI routing", () => { const container = app.querySelector(".chat-thread"); expect(container).not.toBeNull(); - if (!container) {return;} + if (!container) { + return; + } const maxScroll = container.scrollHeight - container.clientHeight; expect(maxScroll).toBeGreaterThan(0); for (let i = 0; i < 10; i++) { - if (container.scrollTop === maxScroll) {break;} + if (container.scrollTop === maxScroll) { + break; + } await nextFrame(); } expect(container.scrollTop).toBe(maxScroll); diff --git a/ui/src/ui/navigation.ts b/ui/src/ui/navigation.ts index 8c5cddd94d..44333a7811 100644 --- a/ui/src/ui/navigation.ts +++ b/ui/src/ui/navigation.ts @@ -40,18 +40,30 @@ const TAB_PATHS: Record = { const PATH_TO_TAB = new Map(Object.entries(TAB_PATHS).map(([tab, path]) => [path, tab as Tab])); export function normalizeBasePath(basePath: string): string { - if (!basePath) {return "";} + if (!basePath) { + return ""; + } let base = basePath.trim(); - if (!base.startsWith("/")) {base = `/${base}`;} - if (base === "/") {return "";} - if (base.endsWith("/")) {base = base.slice(0, -1);} + if (!base.startsWith("/")) { + base = `/${base}`; + } + if (base === "/") { + return ""; + } + if (base.endsWith("/")) { + base = base.slice(0, -1); + } return base; } export function normalizePath(path: string): string { - if (!path) {return "/";} + if (!path) { + return "/"; + } let normalized = path.trim(); - if (!normalized.startsWith("/")) {normalized = `/${normalized}`;} + if (!normalized.startsWith("/")) { + normalized = `/${normalized}`; + } if (normalized.length > 1 && normalized.endsWith("/")) { normalized = normalized.slice(0, -1); } @@ -75,8 +87,12 @@ export function tabFromPath(pathname: string, basePath = ""): Tab | null { } } let normalized = normalizePath(path).toLowerCase(); - if (normalized.endsWith("/index.html")) {normalized = "/";} - if (normalized === "/") {return "chat";} + if (normalized.endsWith("/index.html")) { + normalized = "/"; + } + if (normalized === "/") { + return "chat"; + } return PATH_TO_TAB.get(normalized) ?? null; } @@ -85,9 +101,13 @@ export function inferBasePathFromPathname(pathname: string): string { if (normalized.endsWith("/index.html")) { normalized = normalizePath(normalized.slice(0, -"/index.html".length)); } - if (normalized === "/") {return "";} + if (normalized === "/") { + return ""; + } const segments = normalized.split("/").filter(Boolean); - if (segments.length === 0) {return "";} + if (segments.length === 0) { + return ""; + } for (let i = 0; i < segments.length; i++) { const candidate = `/${segments.slice(i).join("/")}`.toLowerCase(); if (PATH_TO_TAB.has(candidate)) { diff --git a/ui/src/ui/presenter.ts b/ui/src/ui/presenter.ts index f55f14b4d8..80229269ce 100644 --- a/ui/src/ui/presenter.ts +++ b/ui/src/ui/presenter.ts @@ -15,22 +15,29 @@ export function formatPresenceAge(entry: PresenceEntry): string { } export function formatNextRun(ms?: number | null) { - if (!ms) {return "n/a";} + if (!ms) { + return "n/a"; + } return `${formatMs(ms)} (${formatAgo(ms)})`; } export function formatSessionTokens(row: GatewaySessionRow) { - if (row.totalTokens == null) {return "n/a";} + if (row.totalTokens == null) { + return "n/a"; + } const total = row.totalTokens ?? 0; const ctx = row.contextTokens ?? 0; return ctx ? `${total} / ${ctx}` : String(total); } export function formatEventPayload(payload: unknown): string { - if (payload == null) {return "";} + if (payload == null) { + return ""; + } try { return JSON.stringify(payload, null, 2); } catch { + // oxlint-disable typescript/no-base-to-string return String(payload); } } @@ -45,13 +52,19 @@ export function formatCronState(job: CronJob) { export function formatCronSchedule(job: CronJob) { const s = job.schedule; - if (s.kind === "at") {return `At ${formatMs(s.atMs)}`;} - if (s.kind === "every") {return `Every ${formatDurationMs(s.everyMs)}`;} + if (s.kind === "at") { + return `At ${formatMs(s.atMs)}`; + } + if (s.kind === "every") { + return `Every ${formatDurationMs(s.everyMs)}`; + } return `Cron ${s.expr}${s.tz ? ` (${s.tz})` : ""}`; } export function formatCronPayload(job: CronJob) { const p = job.payload; - if (p.kind === "systemEvent") {return `System: ${p.text}`;} + if (p.kind === "systemEvent") { + return `System: ${p.text}`; + } return `Agent: ${p.message}`; } diff --git a/ui/src/ui/storage.ts b/ui/src/ui/storage.ts index 221e0c58dd..edb5d6a9ee 100644 --- a/ui/src/ui/storage.ts +++ b/ui/src/ui/storage.ts @@ -36,7 +36,9 @@ export function loadSettings(): UiSettings { try { const raw = localStorage.getItem(KEY); - if (!raw) {return defaults;} + if (!raw) { + return defaults; + } const parsed = JSON.parse(raw) as Partial; return { gatewayUrl: diff --git a/ui/src/ui/theme-transition.ts b/ui/src/ui/theme-transition.ts index 5d30208ac7..415c23ac59 100644 --- a/ui/src/ui/theme-transition.ts +++ b/ui/src/ui/theme-transition.ts @@ -18,9 +18,15 @@ type DocumentWithViewTransition = Document & { }; const clamp01 = (value: number) => { - if (Number.isNaN(value)) {return 0.5;} - if (value <= 0) {return 0;} - if (value >= 1) {return 1;} + if (Number.isNaN(value)) { + return 0.5; + } + if (value <= 0) { + return 0; + } + if (value >= 1) { + return 1; + } return value; }; @@ -43,7 +49,9 @@ export const startThemeTransition = ({ context, currentTheme, }: ThemeTransitionOptions) => { - if (currentTheme === nextTheme) {return;} + if (currentTheme === nextTheme) { + return; + } const documentReference = globalThis.document ?? null; if (!documentReference) { diff --git a/ui/src/ui/theme.ts b/ui/src/ui/theme.ts index bb0cfe728f..480f9dbe51 100644 --- a/ui/src/ui/theme.ts +++ b/ui/src/ui/theme.ts @@ -9,6 +9,8 @@ export function getSystemTheme(): ResolvedTheme { } export function resolveTheme(mode: ThemeMode): ResolvedTheme { - if (mode === "system") {return getSystemTheme();} + if (mode === "system") { + return getSystemTheme(); + } return mode; } diff --git a/ui/src/ui/tool-display.ts b/ui/src/ui/tool-display.ts index 64168a2ee6..6f43bd2b22 100644 --- a/ui/src/ui/tool-display.ts +++ b/ui/src/ui/tool-display.ts @@ -39,7 +39,9 @@ function normalizeToolName(name?: string): string { function defaultTitle(name: string): string { const cleaned = name.replace(/_/g, " ").trim(); - if (!cleaned) {return "Tool";} + if (!cleaned) { + return "Tool"; + } return cleaned .split(/\s+/) .map((part) => @@ -52,17 +54,25 @@ function defaultTitle(name: string): string { function normalizeVerb(value?: string): string | undefined { const trimmed = value?.trim(); - if (!trimmed) {return undefined;} + if (!trimmed) { + return undefined; + } return trimmed.replace(/_/g, " "); } function coerceDisplayValue(value: unknown): string | undefined { - if (value === null || value === undefined) {return undefined;} + if (value === null || value === undefined) { + return undefined; + } if (typeof value === "string") { const trimmed = value.trim(); - if (!trimmed) {return undefined;} + if (!trimmed) { + return undefined; + } const firstLine = trimmed.split(/\r?\n/)[0]?.trim() ?? ""; - if (!firstLine) {return undefined;} + if (!firstLine) { + return undefined; + } return firstLine.length > 160 ? `${firstLine.slice(0, 157)}…` : firstLine; } if (typeof value === "number" || typeof value === "boolean") { @@ -72,7 +82,9 @@ function coerceDisplayValue(value: unknown): string | undefined { const values = value .map((item) => coerceDisplayValue(item)) .filter((item): item is string => Boolean(item)); - if (values.length === 0) {return undefined;} + if (values.length === 0) { + return undefined; + } const preview = values.slice(0, 3).join(", "); return values.length > 3 ? `${preview}…` : preview; } @@ -80,11 +92,17 @@ function coerceDisplayValue(value: unknown): string | undefined { } function lookupValueByPath(args: unknown, path: string): unknown { - if (!args || typeof args !== "object") {return undefined;} + if (!args || typeof args !== "object") { + return undefined; + } let current: unknown = args; for (const segment of path.split(".")) { - if (!segment) {return undefined;} - if (!current || typeof current !== "object") {return undefined;} + if (!segment) { + return undefined; + } + if (!current || typeof current !== "object") { + return undefined; + } const record = current as Record; current = record[segment]; } @@ -95,16 +113,22 @@ function resolveDetailFromKeys(args: unknown, keys: string[]): string | undefine for (const key of keys) { const value = lookupValueByPath(args, key); const display = coerceDisplayValue(value); - if (display) {return display;} + if (display) { + return display; + } } return undefined; } function resolveReadDetail(args: unknown): string | undefined { - if (!args || typeof args !== "object") {return undefined;} + if (!args || typeof args !== "object") { + return undefined; + } const record = args as Record; const path = typeof record.path === "string" ? record.path : undefined; - if (!path) {return undefined;} + if (!path) { + return undefined; + } const offset = typeof record.offset === "number" ? record.offset : undefined; const limit = typeof record.limit === "number" ? record.limit : undefined; if (offset !== undefined && limit !== undefined) { @@ -114,7 +138,9 @@ function resolveReadDetail(args: unknown): string | undefined { } function resolveWriteDetail(args: unknown): string | undefined { - if (!args || typeof args !== "object") {return undefined;} + if (!args || typeof args !== "object") { + return undefined; + } const record = args as Record; const path = typeof record.path === "string" ? record.path : undefined; return path; @@ -124,7 +150,9 @@ function resolveActionSpec( spec: ToolDisplaySpec | undefined, action: string | undefined, ): ToolDisplayActionSpec | undefined { - if (!spec || !action) {return undefined;} + if (!spec || !action) { + return undefined; + } return spec.actions?.[action] ?? undefined; } @@ -148,7 +176,9 @@ export function resolveToolDisplay(params: { const verb = normalizeVerb(actionSpec?.label ?? action); let detail: string | undefined; - if (key === "read") {detail = resolveReadDetail(params.args);} + if (key === "read") { + detail = resolveReadDetail(params.args); + } if (!detail && (key === "write" || key === "edit" || key === "attach")) { detail = resolveWriteDetail(params.args); } @@ -178,9 +208,15 @@ export function resolveToolDisplay(params: { export function formatToolDetail(display: ToolDisplay): string | undefined { const parts: string[] = []; - if (display.verb) {parts.push(display.verb);} - if (display.detail) {parts.push(display.detail);} - if (parts.length === 0) {return undefined;} + if (display.verb) { + parts.push(display.verb); + } + if (display.detail) { + parts.push(display.detail); + } + if (parts.length === 0) { + return undefined; + } return parts.join(" · "); } @@ -190,6 +226,8 @@ export function formatToolSummary(display: ToolDisplay): string { } function shortenHomeInString(input: string): string { - if (!input) {return input;} + if (!input) { + return input; + } return input.replace(/\/Users\/[^/]+/g, "~").replace(/\/home\/[^/]+/g, "~"); } diff --git a/ui/src/ui/uuid.test.ts b/ui/src/ui/uuid.test.ts index afe19ad214..8d0aa43543 100644 --- a/ui/src/ui/uuid.test.ts +++ b/ui/src/ui/uuid.test.ts @@ -16,7 +16,9 @@ describe("generateUUID", () => { it("falls back to crypto.getRandomValues", () => { const id = generateUUID({ getRandomValues: (bytes) => { - for (let i = 0; i < bytes.length; i++) {bytes[i] = i;} + for (let i = 0; i < bytes.length; i++) { + bytes[i] = i; + } return bytes; }, }); diff --git a/ui/src/ui/uuid.ts b/ui/src/ui/uuid.ts index fd5f5fdf5d..d813a695a1 100644 --- a/ui/src/ui/uuid.ts +++ b/ui/src/ui/uuid.ts @@ -23,7 +23,9 @@ function uuidFromBytes(bytes: Uint8Array): string { function weakRandomBytes(): Uint8Array { const bytes = new Uint8Array(16); const now = Date.now(); - for (let i = 0; i < bytes.length; i++) {bytes[i] = Math.floor(Math.random() * 256);} + for (let i = 0; i < bytes.length; i++) { + bytes[i] = Math.floor(Math.random() * 256); + } bytes[0] ^= now & 0xff; bytes[1] ^= (now >>> 8) & 0xff; bytes[2] ^= (now >>> 16) & 0xff; @@ -32,13 +34,17 @@ function weakRandomBytes(): Uint8Array { } function warnWeakCryptoOnce() { - if (warnedWeakCrypto) {return;} + if (warnedWeakCrypto) { + return; + } warnedWeakCrypto = true; console.warn("[uuid] crypto API missing; falling back to weak randomness"); } export function generateUUID(cryptoLike: CryptoLike | null = globalThis.crypto): string { - if (cryptoLike && typeof cryptoLike.randomUUID === "function") {return cryptoLike.randomUUID();} + if (cryptoLike && typeof cryptoLike.randomUUID === "function") { + return cryptoLike.randomUUID(); + } if (cryptoLike && typeof cryptoLike.getRandomValues === "function") { const bytes = new Uint8Array(16); diff --git a/ui/src/ui/views/channels.config.ts b/ui/src/ui/views/channels.config.ts index c1566242bb..0ee5abd49c 100644 --- a/ui/src/ui/views/channels.config.ts +++ b/ui/src/ui/views/channels.config.ts @@ -18,7 +18,9 @@ function resolveSchemaNode( ): JsonSchema | null { let current = schema; for (const key of path) { - if (!current) {return null;} + if (!current) { + return null; + } const type = schemaType(current); if (type === "object") { const properties = current.properties ?? {}; @@ -34,7 +36,9 @@ function resolveSchemaNode( return null; } if (type === "array") { - if (typeof key !== "number") {return null;} + if (typeof key !== "number") { + return null; + } const items = Array.isArray(current.items) ? current.items[0] : current.items; current = items ?? null; continue; diff --git a/ui/src/ui/views/channels.nostr-profile-form.ts b/ui/src/ui/views/channels.nostr-profile-form.ts index b9dfc332dd..f9dc855b67 100644 --- a/ui/src/ui/views/channels.nostr-profile-form.ts +++ b/ui/src/ui/views/channels.nostr-profile-form.ts @@ -140,7 +140,9 @@ export function renderNostrProfileForm(params: { const renderPicturePreview = () => { const picture = state.values.picture; - if (!picture) {return nothing;} + if (!picture) { + return nothing; + } return html`
diff --git a/ui/src/ui/views/channels.nostr.ts b/ui/src/ui/views/channels.nostr.ts index 4ad7d82cf2..fb1faf0bf7 100644 --- a/ui/src/ui/views/channels.nostr.ts +++ b/ui/src/ui/views/channels.nostr.ts @@ -13,8 +13,12 @@ import { * Truncate a pubkey for display (shows first and last 8 chars) */ function truncatePubkey(pubkey: string | null | undefined): string { - if (!pubkey) {return "n/a";} - if (pubkey.length <= 20) {return pubkey;} + if (!pubkey) { + return "n/a"; + } + if (pubkey.length <= 20) { + return pubkey; + } return `${pubkey.slice(0, 8)}...${pubkey.slice(-8)}`; } diff --git a/ui/src/ui/views/channels.shared.ts b/ui/src/ui/views/channels.shared.ts index 7f79e18d5d..0ef1baaf88 100644 --- a/ui/src/ui/views/channels.shared.ts +++ b/ui/src/ui/views/channels.shared.ts @@ -3,11 +3,17 @@ import type { ChannelAccountSnapshot } from "../types"; import type { ChannelKey, ChannelsProps } from "./channels.types"; export function formatDuration(ms?: number | null) { - if (!ms && ms !== 0) {return "n/a";} + if (!ms && ms !== 0) { + return "n/a"; + } const sec = Math.round(ms / 1000); - if (sec < 60) {return `${sec}s`;} + if (sec < 60) { + return `${sec}s`; + } const min = Math.round(sec / 60); - if (min < 60) {return `${min}m`;} + if (min < 60) { + return `${min}m`; + } const hr = Math.round(min / 60); return `${hr}h`; } @@ -15,7 +21,9 @@ export function formatDuration(ms?: number | null) { export function channelEnabled(key: ChannelKey, props: ChannelsProps) { const snapshot = props.snapshot; const channels = snapshot?.channels as Record | null; - if (!snapshot || !channels) {return false;} + if (!snapshot || !channels) { + return false; + } const channelStatus = channels[key] as Record | undefined; const configured = typeof channelStatus?.configured === "boolean" && channelStatus.configured; const running = typeof channelStatus?.running === "boolean" && channelStatus.running; @@ -39,6 +47,8 @@ export function renderChannelAccountCount( channelAccounts?: Record | null, ) { const count = getChannelAccountCount(key, channelAccounts); - if (count < 2) {return nothing;} + if (count < 2) { + return nothing; + } return html``; } diff --git a/ui/src/ui/views/channels.ts b/ui/src/ui/views/channels.ts index c00c914174..96bdbc115d 100644 --- a/ui/src/ui/views/channels.ts +++ b/ui/src/ui/views/channels.ts @@ -44,7 +44,9 @@ export function renderChannels(props: ChannelsProps) { order: index, })) .toSorted((a, b) => { - if (a.enabled !== b.enabled) {return a.enabled ? -1 : 1;} + if (a.enabled !== b.enabled) { + return a.enabled ? -1 : 1; + } return a.order - b.order; }); @@ -236,7 +238,9 @@ function renderGenericChannelCard( function resolveChannelMetaMap( snapshot: ChannelsStatusSnapshot | null, ): Record { - if (!snapshot?.channelMeta?.length) {return {};} + if (!snapshot?.channelMeta?.length) { + return {}; + } return Object.fromEntries(snapshot.channelMeta.map((entry) => [entry.id, entry])); } @@ -248,22 +252,34 @@ function resolveChannelLabel(snapshot: ChannelsStatusSnapshot | null, key: strin const RECENT_ACTIVITY_THRESHOLD_MS = 10 * 60 * 1000; // 10 minutes function hasRecentActivity(account: ChannelAccountSnapshot): boolean { - if (!account.lastInboundAt) {return false;} + if (!account.lastInboundAt) { + return false; + } return Date.now() - account.lastInboundAt < RECENT_ACTIVITY_THRESHOLD_MS; } function deriveRunningStatus(account: ChannelAccountSnapshot): "Yes" | "No" | "Active" { - if (account.running) {return "Yes";} + if (account.running) { + return "Yes"; + } // If we have recent inbound activity, the channel is effectively running - if (hasRecentActivity(account)) {return "Active";} + if (hasRecentActivity(account)) { + return "Active"; + } return "No"; } function deriveConnectedStatus(account: ChannelAccountSnapshot): "Yes" | "No" | "Active" | "n/a" { - if (account.connected === true) {return "Yes";} - if (account.connected === false) {return "No";} + if (account.connected === true) { + return "Yes"; + } + if (account.connected === false) { + return "No"; + } // If connected is null/undefined but we have recent activity, show as active - if (hasRecentActivity(account)) {return "Active";} + if (hasRecentActivity(account)) { + return "Active"; + } return "n/a"; } diff --git a/ui/src/ui/views/chat.ts b/ui/src/ui/views/chat.ts index 780e2fb114..b94d2f5cc9 100644 --- a/ui/src/ui/views/chat.ts +++ b/ui/src/ui/views/chat.ts @@ -75,7 +75,9 @@ function adjustTextareaHeight(el: HTMLTextAreaElement) { } function renderCompactionIndicator(status: CompactionIndicatorStatus | null | undefined) { - if (!status) {return nothing;} + if (!status) { + return nothing; + } // Show "compacting..." while active if (status.active) { @@ -107,7 +109,9 @@ function generateAttachmentId(): string { function handlePaste(e: ClipboardEvent, props: ChatProps) { const items = e.clipboardData?.items; - if (!items || !props.onAttachmentsChange) {return;} + if (!items || !props.onAttachmentsChange) { + return; + } const imageItems: DataTransferItem[] = []; for (let i = 0; i < items.length; i++) { @@ -117,16 +121,20 @@ function handlePaste(e: ClipboardEvent, props: ChatProps) { } } - if (imageItems.length === 0) {return;} + if (imageItems.length === 0) { + return; + } e.preventDefault(); for (const item of imageItems) { const file = item.getAsFile(); - if (!file) {continue;} + if (!file) { + continue; + } const reader = new FileReader(); - reader.onload = () => { + reader.addEventListener("load", () => { const dataUrl = reader.result as string; const newAttachment: ChatAttachment = { id: generateAttachmentId(), @@ -135,14 +143,16 @@ function handlePaste(e: ClipboardEvent, props: ChatProps) { }; const current = props.attachments ?? []; props.onAttachmentsChange?.([...current, newAttachment]); - }; + }); reader.readAsDataURL(file); } } function renderAttachmentPreview(props: ChatProps) { const attachments = props.attachments ?? []; - if (attachments.length === 0) {return nothing;} + if (attachments.length === 0) { + return nothing; + } return html`
@@ -286,7 +296,9 @@ export function renderChat(props: ChatProps) { error: props.sidebarError ?? null, onClose: props.onCloseSidebar!, onViewRawText: () => { - if (!props.sidebarContent || !props.onOpenSidebar) {return;} + if (!props.sidebarContent || !props.onOpenSidebar) { + return; + } props.onOpenSidebar(`\`\`\`\n${props.sidebarContent}\n\`\`\``); }, })} @@ -338,12 +350,22 @@ export function renderChat(props: ChatProps) { .value=${props.draft} ?disabled=${!props.connected} @keydown=${(e: KeyboardEvent) => { - if (e.key !== "Enter") {return;} - if (e.isComposing || e.keyCode === 229) {return;} - if (e.shiftKey) {return;} // Allow Shift+Enter for line breaks - if (!props.connected) {return;} + if (e.key !== "Enter") { + return; + } + if (e.isComposing || e.keyCode === 229) { + return; + } + if (e.shiftKey) { + return; + } // Allow Shift+Enter for line breaks + if (!props.connected) { + return; + } e.preventDefault(); - if (canCompose) {props.onSend();} + if (canCompose) { + props.onSend(); + } }} @input=${(e: Event) => { const target = e.target as HTMLTextAreaElement; @@ -397,7 +419,9 @@ function groupMessages(items: ChatItem[]): Array { const timestamp = normalized.timestamp || Date.now(); if (!currentGroup || currentGroup.role !== role) { - if (currentGroup) {result.push(currentGroup);} + if (currentGroup) { + result.push(currentGroup); + } currentGroup = { kind: "group", key: `group:${role}:${item.key}`, @@ -411,7 +435,9 @@ function groupMessages(items: ChatItem[]): Array { } } - if (currentGroup) {result.push(currentGroup);} + if (currentGroup) { + result.push(currentGroup); + } return result; } @@ -475,13 +501,21 @@ function buildChatItems(props: ChatProps): Array { function messageKey(message: unknown, index: number): string { const m = message as Record; const toolCallId = typeof m.toolCallId === "string" ? m.toolCallId : ""; - if (toolCallId) {return `tool:${toolCallId}`;} + if (toolCallId) { + return `tool:${toolCallId}`; + } const id = typeof m.id === "string" ? m.id : ""; - if (id) {return `msg:${id}`;} + if (id) { + return `msg:${id}`; + } const messageId = typeof m.messageId === "string" ? m.messageId : ""; - if (messageId) {return `msg:${messageId}`;} + if (messageId) { + return `msg:${messageId}`; + } const timestamp = typeof m.timestamp === "number" ? m.timestamp : null; const role = typeof m.role === "string" ? m.role : "unknown"; - if (timestamp != null) {return `msg:${role}:${timestamp}:${index}`;} + if (timestamp != null) { + return `msg:${role}:${timestamp}:${index}`; + } return `msg:${role}:${index}`; } diff --git a/ui/src/ui/views/config-form.analyze.ts b/ui/src/ui/views/config-form.analyze.ts index 4c4e4ec320..c8f6b7ab6e 100644 --- a/ui/src/ui/views/config-form.analyze.ts +++ b/ui/src/ui/views/config-form.analyze.ts @@ -41,7 +41,9 @@ function normalizeSchemaNode( if (schema.anyOf || schema.oneOf || schema.allOf) { const union = normalizeUnion(schema, path); - if (union) {return union;} + if (union) { + return union; + } return { schema, unsupportedPaths: [pathLabel] }; } @@ -54,8 +56,12 @@ function normalizeSchemaNode( if (normalized.enum) { const { enumValues, nullable: enumNullable } = normalizeEnum(normalized.enum); normalized.enum = enumValues; - if (enumNullable) {normalized.nullable = true;} - if (enumValues.length === 0) {unsupported.add(pathLabel);} + if (enumNullable) { + normalized.nullable = true; + } + if (enumValues.length === 0) { + unsupported.add(pathLabel); + } } if (type === "object") { @@ -63,8 +69,12 @@ function normalizeSchemaNode( const normalizedProps: Record = {}; for (const [key, value] of Object.entries(properties)) { const res = normalizeSchemaNode(value, [...path, key]); - if (res.schema) {normalizedProps[key] = res.schema;} - for (const entry of res.unsupportedPaths) {unsupported.add(entry);} + if (res.schema) { + normalizedProps[key] = res.schema; + } + for (const entry of res.unsupportedPaths) { + unsupported.add(entry); + } } normalized.properties = normalizedProps; @@ -75,8 +85,10 @@ function normalizeSchemaNode( } else if (schema.additionalProperties && typeof schema.additionalProperties === "object") { if (!isAnySchema(schema.additionalProperties)) { const res = normalizeSchemaNode(schema.additionalProperties, [...path, "*"]); - normalized.additionalProperties = res.schema ?? (schema.additionalProperties); - if (res.unsupportedPaths.length > 0) {unsupported.add(pathLabel);} + normalized.additionalProperties = res.schema ?? schema.additionalProperties; + if (res.unsupportedPaths.length > 0) { + unsupported.add(pathLabel); + } } } } else if (type === "array") { @@ -86,7 +98,9 @@ function normalizeSchemaNode( } else { const res = normalizeSchemaNode(itemsSchema, [...path, "*"]); normalized.items = res.schema ?? itemsSchema; - if (res.unsupportedPaths.length > 0) {unsupported.add(pathLabel);} + if (res.unsupportedPaths.length > 0) { + unsupported.add(pathLabel); + } } } else if ( type !== "string" && @@ -108,20 +122,28 @@ function normalizeUnion( schema: JsonSchema, path: Array, ): ConfigSchemaAnalysis | null { - if (schema.allOf) {return null;} + if (schema.allOf) { + return null; + } const union = schema.anyOf ?? schema.oneOf; - if (!union) {return null;} + if (!union) { + return null; + } const literals: unknown[] = []; const remaining: JsonSchema[] = []; let nullable = false; for (const entry of union) { - if (!entry || typeof entry !== "object") {return null;} + if (!entry || typeof entry !== "object") { + return null; + } if (Array.isArray(entry.enum)) { const { enumValues, nullable: enumNullable } = normalizeEnum(entry.enum); literals.push(...enumValues); - if (enumNullable) {nullable = true;} + if (enumNullable) { + nullable = true; + } continue; } if ("const" in entry) { diff --git a/ui/src/ui/views/config-form.node.ts b/ui/src/ui/views/config-form.node.ts index 282a304585..b2e9e97b5b 100644 --- a/ui/src/ui/views/config-form.node.ts +++ b/ui/src/ui/views/config-form.node.ts @@ -18,7 +18,9 @@ function isAnySchema(schema: JsonSchema): boolean { } function jsonValue(value: unknown): string { - if (value === undefined) {return "";} + if (value === undefined) { + return ""; + } try { return JSON.stringify(value, null, 2) ?? ""; } catch { @@ -131,8 +133,12 @@ export function renderNode(params: { // Check if it's a set of literal values (enum-like) const extractLiteral = (v: JsonSchema): unknown | undefined => { - if (v.const !== undefined) {return v.const;} - if (v.enum && v.enum.length === 1) {return v.enum[0];} + if (v.const !== undefined) { + return v.const; + } + if (v.enum && v.enum.length === 1) { + return v.enum[0]; + } return undefined; }; const literals = nonNull.map(extractLiteral); @@ -147,14 +153,20 @@ export function renderNode(params: { ${help ? html`
${help}
` : nothing}
${literals.map( - (lit, idx) => html` + (lit) => html` `, )} @@ -298,7 +310,12 @@ function renderTextInput(params: { const isSensitive = hint?.sensitive ?? isSensitivePath(path); const placeholder = hint?.placeholder ?? - (isSensitive ? "••••" : schema.default !== undefined ? `Default: ${schema.default}` : ""); + // oxlint-disable typescript/no-base-to-string + (isSensitive + ? "••••" + : schema.default !== undefined + ? `Default: ${String(schema.default)}` + : ""); const displayValue = value ?? ""; return html` @@ -326,7 +343,9 @@ function renderTextInput(params: { onPatch(path, raw); }} @change=${(e: Event) => { - if (inputType === "number") {return;} + if (inputType === "number") { + return; + } const raw = (e.target as HTMLInputElement).value; onPatch(path, raw.trim()); }} @@ -455,7 +474,6 @@ function renderObject(params: { onPatch: (path: Array, value: unknown) => void; }): TemplateResult { const { schema, value, path, hints, unsupported, disabled, onPatch } = params; - const showLabel = params.showLabel ?? true; const hint = hintForPath(path, hints); const label = hint?.label ?? schema.title ?? humanize(String(path.at(-1))); const help = hint?.help ?? schema.description; @@ -472,7 +490,9 @@ function renderObject(params: { const sorted = entries.toSorted((a, b) => { const orderA = hintForPath([...path, a[0]], hints)?.order ?? 0; const orderB = hintForPath([...path, b[0]], hints)?.order ?? 0; - if (orderA !== orderB) {return orderA - orderB;} + if (orderA !== orderB) { + return orderA - orderB; + } return a[0].localeCompare(b[0]); }); @@ -708,9 +728,13 @@ function renderMapField(params: { ?disabled=${disabled} @change=${(e: Event) => { const nextKey = (e.target as HTMLInputElement).value.trim(); - if (!nextKey || nextKey === key) {return;} + if (!nextKey || nextKey === key) { + return; + } const next = { ...value }; - if (nextKey in next) {return;} + if (nextKey in next) { + return; + } next[nextKey] = next[key]; delete next[key]; onPatch(path, next); diff --git a/ui/src/ui/views/config-form.render.ts b/ui/src/ui/views/config-form.render.ts index 55ce94cd77..11698baada 100644 --- a/ui/src/ui/views/config-form.render.ts +++ b/ui/src/ui/views/config-form.render.ts @@ -279,49 +279,73 @@ function getSectionIcon(key: string) { } function matchesSearch(key: string, schema: JsonSchema, query: string): boolean { - if (!query) {return true;} + if (!query) { + return true; + } const q = query.toLowerCase(); const meta = SECTION_META[key]; // Check key name - if (key.toLowerCase().includes(q)) {return true;} + if (key.toLowerCase().includes(q)) { + return true; + } // Check label and description if (meta) { - if (meta.label.toLowerCase().includes(q)) {return true;} - if (meta.description.toLowerCase().includes(q)) {return true;} + if (meta.label.toLowerCase().includes(q)) { + return true; + } + if (meta.description.toLowerCase().includes(q)) { + return true; + } } return schemaMatches(schema, q); } function schemaMatches(schema: JsonSchema, query: string): boolean { - if (schema.title?.toLowerCase().includes(query)) {return true;} - if (schema.description?.toLowerCase().includes(query)) {return true;} - if (schema.enum?.some((value) => String(value).toLowerCase().includes(query))) {return true;} + if (schema.title?.toLowerCase().includes(query)) { + return true; + } + if (schema.description?.toLowerCase().includes(query)) { + return true; + } + if (schema.enum?.some((value) => String(value).toLowerCase().includes(query))) { + return true; + } if (schema.properties) { for (const [propKey, propSchema] of Object.entries(schema.properties)) { - if (propKey.toLowerCase().includes(query)) {return true;} - if (schemaMatches(propSchema, query)) {return true;} + if (propKey.toLowerCase().includes(query)) { + return true; + } + if (schemaMatches(propSchema, query)) { + return true; + } } } if (schema.items) { const items = Array.isArray(schema.items) ? schema.items : [schema.items]; for (const item of items) { - if (item && schemaMatches(item, query)) {return true;} + if (item && schemaMatches(item, query)) { + return true; + } } } if (schema.additionalProperties && typeof schema.additionalProperties === "object") { - if (schemaMatches(schema.additionalProperties, query)) {return true;} + if (schemaMatches(schema.additionalProperties, query)) { + return true; + } } const unions = schema.anyOf ?? schema.oneOf ?? schema.allOf; if (unions) { for (const entry of unions) { - if (entry && schemaMatches(entry, query)) {return true;} + if (entry && schemaMatches(entry, query)) { + return true; + } } } @@ -350,13 +374,19 @@ export function renderConfigForm(props: ConfigFormProps) { const entries = Object.entries(properties).toSorted((a, b) => { const orderA = hintForPath([a[0]], props.uiHints)?.order ?? 50; const orderB = hintForPath([b[0]], props.uiHints)?.order ?? 50; - if (orderA !== orderB) {return orderA - orderB;} + if (orderA !== orderB) { + return orderA - orderB; + } return a[0].localeCompare(b[0]); }); const filteredEntries = entries.filter(([key, node]) => { - if (activeSection && key !== activeSection) {return false;} - if (searchQuery && !matchesSearch(key, node, searchQuery)) {return false;} + if (activeSection && key !== activeSection) { + return false; + } + if (searchQuery && !matchesSearch(key, node, searchQuery)) { + return false; + } return true; }); @@ -398,7 +428,7 @@ export function renderConfigForm(props: ConfigFormProps) { const hint = hintForPath([sectionKey, subsectionKey], props.uiHints); const label = hint?.label ?? node.title ?? humanize(subsectionKey); const description = hint?.help ?? node.description ?? ""; - const sectionValue = (value)[sectionKey]; + const sectionValue = value[sectionKey]; const scopedValue = sectionValue && typeof sectionValue === "object" ? (sectionValue as Record)[subsectionKey] @@ -454,7 +484,7 @@ export function renderConfigForm(props: ConfigFormProps) {
${renderNode({ schema: node, - value: (value)[key], + value: value[key], path: [key], hints: props.uiHints, unsupported, diff --git a/ui/src/ui/views/config-form.shared.ts b/ui/src/ui/views/config-form.shared.ts index ca184ae93e..57de508322 100644 --- a/ui/src/ui/views/config-form.shared.ts +++ b/ui/src/ui/views/config-form.shared.ts @@ -17,7 +17,9 @@ export type JsonSchema = { }; export function schemaType(schema: JsonSchema): string | undefined { - if (!schema) {return undefined;} + if (!schema) { + return undefined; + } if (Array.isArray(schema.type)) { const filtered = schema.type.filter((t) => t !== "null"); return filtered[0] ?? schema.type[0]; @@ -26,8 +28,12 @@ export function schemaType(schema: JsonSchema): string | undefined { } export function defaultValue(schema?: JsonSchema): unknown { - if (!schema) {return "";} - if (schema.default !== undefined) {return schema.default;} + if (!schema) { + return ""; + } + if (schema.default !== undefined) { + return schema.default; + } const type = schemaType(schema); switch (type) { case "object": @@ -53,12 +59,18 @@ export function pathKey(path: Array): string { export function hintForPath(path: Array, hints: ConfigUiHints) { const key = pathKey(path); const direct = hints[key]; - if (direct) {return direct;} + if (direct) { + return direct; + } const segments = key.split("."); for (const [hintKey, hint] of Object.entries(hints)) { - if (!hintKey.includes("*")) {continue;} + if (!hintKey.includes("*")) { + continue; + } const hintSegments = hintKey.split("."); - if (hintSegments.length !== segments.length) {continue;} + if (hintSegments.length !== segments.length) { + continue; + } let match = true; for (let i = 0; i < segments.length; i += 1) { if (hintSegments[i] !== "*" && hintSegments[i] !== segments[i]) { @@ -66,7 +78,9 @@ export function hintForPath(path: Array, hints: ConfigUiHints) break; } } - if (match) {return hint;} + if (match) { + return hint; + } } return undefined; } diff --git a/ui/src/ui/views/config.browser.test.ts b/ui/src/ui/views/config.browser.test.ts index 1c05104dcc..481e163b44 100644 --- a/ui/src/ui/views/config.browser.test.ts +++ b/ui/src/ui/views/config.browser.test.ts @@ -191,7 +191,9 @@ describe("config view", () => { const input = container.querySelector(".config-search__input"); expect(input).not.toBeNull(); - if (!input) {return;} + if (!input) { + return; + } input.value = "gateway"; input.dispatchEvent(new Event("input", { bubbles: true })); expect(onSearchChange).toHaveBeenCalledWith("gateway"); diff --git a/ui/src/ui/views/config.ts b/ui/src/ui/views/config.ts index dd9e2c243e..9b3f3ac3da 100644 --- a/ui/src/ui/views/config.ts +++ b/ui/src/ui/views/config.ts @@ -299,7 +299,9 @@ function resolveSectionMeta( description?: string; } { const meta = SECTION_META[key]; - if (meta) {return meta;} + if (meta) { + return meta; + } return { label: schema?.title ?? humanize(key), description: schema?.description ?? "", @@ -312,7 +314,9 @@ function resolveSubsections(params: { uiHints: ConfigUiHints; }): SubsectionEntry[] { const { key, schema, uiHints } = params; - if (!schema || schemaType(schema) !== "object" || !schema.properties) {return [];} + if (!schema || schemaType(schema) !== "object" || !schema.properties) { + return []; + } const entries = Object.entries(schema.properties).map(([subKey, node]) => { const hint = hintForPath([key, subKey], uiHints); const label = hint?.label ?? node.title ?? humanize(subKey); @@ -328,11 +332,15 @@ function computeDiff( original: Record | null, current: Record | null, ): Array<{ path: string; from: unknown; to: unknown }> { - if (!original || !current) {return [];} + if (!original || !current) { + return []; + } const changes: Array<{ path: string; from: unknown; to: unknown }> = []; function compare(orig: unknown, curr: unknown, path: string) { - if (orig === curr) {return;} + if (orig === curr) { + return; + } if (typeof orig !== typeof curr) { changes.push({ path, from: orig, to: curr }); return; @@ -369,7 +377,9 @@ function truncateValue(value: unknown, maxLen = 40): string { } catch { str = String(value); } - if (str.length <= maxLen) {return str;} + if (str.length <= maxLen) { + return str; + } return str.slice(0, maxLen - 3) + "..."; } @@ -392,7 +402,7 @@ export function renderConfig(props: ConfigProps) { const activeSectionSchema = props.activeSection && analysis.schema && schemaType(analysis.schema) === "object" - ? (analysis.schema.properties?.[props.activeSection]) + ? analysis.schema.properties?.[props.activeSection] : undefined; const activeSectionMeta = props.activeSection ? resolveSectionMeta(props.activeSection, activeSectionSchema) diff --git a/ui/src/ui/views/cron.ts b/ui/src/ui/views/cron.ts index db80f0d251..e144b3f412 100644 --- a/ui/src/ui/views/cron.ts +++ b/ui/src/ui/views/cron.ts @@ -38,16 +38,22 @@ function buildChannelOptions(props: CronProps): string[] { } const seen = new Set(); return options.filter((value) => { - if (seen.has(value)) {return false;} + if (seen.has(value)) { + return false; + } seen.add(value); return true; }); } function resolveChannelLabel(props: CronProps, channel: string): string { - if (channel === "last") {return "last";} + if (channel === "last") { + return "last"; + } const meta = props.channelMeta?.find((entry) => entry.id === channel); - if (meta?.label) {return meta.label;} + if (meta?.label) { + return meta.label; + } return props.channelLabels?.[channel] ?? channel; } @@ -212,8 +218,7 @@ export function renderCron(props: CronProps) { .value=${props.form.channel || "last"} @change=${(e: Event) => props.onFormChange({ - channel: (e.target as HTMLSelectElement) - .value, + channel: (e.target as HTMLSelectElement).value, })} > ${channelOptions.map( diff --git a/ui/src/ui/views/exec-approval.ts b/ui/src/ui/views/exec-approval.ts index 9fa745c66a..2993353852 100644 --- a/ui/src/ui/views/exec-approval.ts +++ b/ui/src/ui/views/exec-approval.ts @@ -4,21 +4,29 @@ import type { AppViewState } from "../app-view-state"; function formatRemaining(ms: number): string { const remaining = Math.max(0, ms); const totalSeconds = Math.floor(remaining / 1000); - if (totalSeconds < 60) {return `${totalSeconds}s`;} + if (totalSeconds < 60) { + return `${totalSeconds}s`; + } const minutes = Math.floor(totalSeconds / 60); - if (minutes < 60) {return `${minutes}m`;} + if (minutes < 60) { + return `${minutes}m`; + } const hours = Math.floor(minutes / 60); return `${hours}h`; } function renderMetaRow(label: string, value?: string | null) { - if (!value) {return nothing;} + if (!value) { + return nothing; + } return html`
${label}${value}
`; } export function renderExecApprovalPrompt(state: AppViewState) { const active = state.execApprovalQueue[0]; - if (!active) {return nothing;} + if (!active) { + return nothing; + } const request = active.request; const remainingMs = active.expiresAtMs - Date.now(); const remaining = remainingMs > 0 ? `expires in ${formatRemaining(remainingMs)}` : "expired"; diff --git a/ui/src/ui/views/gateway-url-confirmation.ts b/ui/src/ui/views/gateway-url-confirmation.ts index 4ad44e88bf..bc588e69d4 100644 --- a/ui/src/ui/views/gateway-url-confirmation.ts +++ b/ui/src/ui/views/gateway-url-confirmation.ts @@ -3,7 +3,9 @@ import type { AppViewState } from "../app-view-state"; export function renderGatewayUrlConfirmation(state: AppViewState) { const { pendingGatewayUrl } = state; - if (!pendingGatewayUrl) {return nothing;} + if (!pendingGatewayUrl) { + return nothing; + } return html`