mirror of
https://github.com/moltbot/moltbot.git
synced 2026-03-08 06:54:24 +00:00
fix(ui): load Control UI bootstrap config via JSON endpoint
This commit is contained in:
@@ -17,10 +17,14 @@ import {
|
||||
syncTabWithLocation,
|
||||
syncThemeWithSettings,
|
||||
} from "./app-settings.ts";
|
||||
import { loadControlUiBootstrapConfig } from "./controllers/control-ui-bootstrap.ts";
|
||||
|
||||
type LifecycleHost = {
|
||||
basePath: string;
|
||||
tab: Tab;
|
||||
assistantName: string;
|
||||
assistantAvatar: string | null;
|
||||
assistantAgentId: string | null;
|
||||
chatHasAutoScrolled: boolean;
|
||||
chatManualRefreshInFlight: boolean;
|
||||
chatLoading: boolean;
|
||||
@@ -36,6 +40,7 @@ type LifecycleHost = {
|
||||
|
||||
export function handleConnected(host: LifecycleHost) {
|
||||
host.basePath = inferBasePath();
|
||||
void loadControlUiBootstrapConfig(host);
|
||||
applySettingsFromUrl(host as unknown as Parameters<typeof applySettingsFromUrl>[0]);
|
||||
syncTabWithLocation(host as unknown as Parameters<typeof syncTabWithLocation>[0], true);
|
||||
syncThemeWithSettings(host as unknown as Parameters<typeof syncThemeWithSettings>[0]);
|
||||
|
||||
@@ -76,7 +76,7 @@ import {
|
||||
type ToolStreamEntry,
|
||||
type CompactionStatus,
|
||||
} from "./app-tool-stream.ts";
|
||||
import { resolveInjectedAssistantIdentity } from "./assistant-identity.ts";
|
||||
import { normalizeAssistantIdentity } from "./assistant-identity.ts";
|
||||
import { loadAssistantIdentity as loadAssistantIdentityInternal } from "./controllers/assistant-identity.ts";
|
||||
import { loadSettings, type UiSettings } from "./storage.ts";
|
||||
import { type ChatAttachment, type ChatQueueItem, type CronFormState } from "./ui-types.ts";
|
||||
@@ -87,7 +87,7 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
const injectedAssistantIdentity = resolveInjectedAssistantIdentity();
|
||||
const bootAssistantIdentity = normalizeAssistantIdentity({});
|
||||
|
||||
function resolveOnboardingMode(): boolean {
|
||||
if (!window.location.search) {
|
||||
@@ -118,9 +118,9 @@ export class OpenClawApp extends LitElement {
|
||||
private toolStreamSyncTimer: number | null = null;
|
||||
private sidebarCloseTimer: number | null = null;
|
||||
|
||||
@state() assistantName = injectedAssistantIdentity.name;
|
||||
@state() assistantAvatar = injectedAssistantIdentity.avatar;
|
||||
@state() assistantAgentId = injectedAssistantIdentity.agentId ?? null;
|
||||
@state() assistantName = bootAssistantIdentity.name;
|
||||
@state() assistantAvatar = bootAssistantIdentity.avatar;
|
||||
@state() assistantAgentId = bootAssistantIdentity.agentId ?? null;
|
||||
|
||||
@state() sessionKey = this.settings.sessionKey;
|
||||
@state() chatLoading = false;
|
||||
|
||||
@@ -10,13 +10,6 @@ export type AssistantIdentity = {
|
||||
avatar: string | null;
|
||||
};
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
__OPENCLAW_ASSISTANT_NAME__?: string;
|
||||
__OPENCLAW_ASSISTANT_AVATAR__?: string;
|
||||
}
|
||||
}
|
||||
|
||||
function coerceIdentityValue(value: string | undefined, maxLength: number): string | undefined {
|
||||
if (typeof value !== "string") {
|
||||
return undefined;
|
||||
@@ -40,13 +33,3 @@ export function normalizeAssistantIdentity(
|
||||
typeof input?.agentId === "string" && input.agentId.trim() ? input.agentId.trim() : null;
|
||||
return { agentId, name, avatar };
|
||||
}
|
||||
|
||||
export function resolveInjectedAssistantIdentity(): AssistantIdentity {
|
||||
if (typeof window === "undefined") {
|
||||
return normalizeAssistantIdentity({});
|
||||
}
|
||||
return normalizeAssistantIdentity({
|
||||
name: window.__OPENCLAW_ASSISTANT_NAME__,
|
||||
avatar: window.__OPENCLAW_ASSISTANT_AVATAR__,
|
||||
});
|
||||
}
|
||||
|
||||
60
ui/src/ui/controllers/control-ui-bootstrap.test.ts
Normal file
60
ui/src/ui/controllers/control-ui-bootstrap.test.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
/* @vitest-environment jsdom */
|
||||
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { loadControlUiBootstrapConfig } from "./control-ui-bootstrap.ts";
|
||||
|
||||
describe("loadControlUiBootstrapConfig", () => {
|
||||
it("loads assistant identity from the bootstrap endpoint", async () => {
|
||||
const fetchMock = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
basePath: "/openclaw",
|
||||
assistantName: "Ops",
|
||||
assistantAvatar: "O",
|
||||
assistantAgentId: "main",
|
||||
}),
|
||||
});
|
||||
vi.stubGlobal("fetch", fetchMock as unknown as typeof fetch);
|
||||
|
||||
const state = {
|
||||
basePath: "/openclaw",
|
||||
assistantName: "Assistant",
|
||||
assistantAvatar: null,
|
||||
assistantAgentId: null,
|
||||
};
|
||||
|
||||
await loadControlUiBootstrapConfig(state);
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledWith(
|
||||
"/openclaw/__openclaw/control-ui-config.json",
|
||||
expect.objectContaining({ method: "GET" }),
|
||||
);
|
||||
expect(state.assistantName).toBe("Ops");
|
||||
expect(state.assistantAvatar).toBe("O");
|
||||
expect(state.assistantAgentId).toBe("main");
|
||||
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it("ignores failures", async () => {
|
||||
const fetchMock = vi.fn().mockResolvedValue({ ok: false });
|
||||
vi.stubGlobal("fetch", fetchMock as unknown as typeof fetch);
|
||||
|
||||
const state = {
|
||||
basePath: "",
|
||||
assistantName: "Assistant",
|
||||
assistantAvatar: null,
|
||||
assistantAgentId: null,
|
||||
};
|
||||
|
||||
await loadControlUiBootstrapConfig(state);
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledWith(
|
||||
"/__openclaw/control-ui-config.json",
|
||||
expect.objectContaining({ method: "GET" }),
|
||||
);
|
||||
expect(state.assistantName).toBe("Assistant");
|
||||
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
});
|
||||
52
ui/src/ui/controllers/control-ui-bootstrap.ts
Normal file
52
ui/src/ui/controllers/control-ui-bootstrap.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { normalizeAssistantIdentity } from "../assistant-identity.ts";
|
||||
import { normalizeBasePath } from "../navigation.ts";
|
||||
|
||||
type ControlUiBootstrapConfig = {
|
||||
basePath?: string;
|
||||
assistantName?: string;
|
||||
assistantAvatar?: string;
|
||||
assistantAgentId?: string;
|
||||
};
|
||||
|
||||
export type ControlUiBootstrapState = {
|
||||
basePath: string;
|
||||
assistantName: string;
|
||||
assistantAvatar: string | null;
|
||||
assistantAgentId: string | null;
|
||||
};
|
||||
|
||||
export async function loadControlUiBootstrapConfig(state: ControlUiBootstrapState) {
|
||||
if (typeof window === "undefined") {
|
||||
return;
|
||||
}
|
||||
if (typeof fetch !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
const basePath = normalizeBasePath(state.basePath ?? "");
|
||||
const url = basePath
|
||||
? `${basePath}/__openclaw/control-ui-config.json`
|
||||
: "/__openclaw/control-ui-config.json";
|
||||
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: { Accept: "application/json" },
|
||||
credentials: "same-origin",
|
||||
});
|
||||
if (!res.ok) {
|
||||
return;
|
||||
}
|
||||
const parsed = (await res.json()) as ControlUiBootstrapConfig;
|
||||
const normalized = normalizeAssistantIdentity({
|
||||
agentId: parsed.assistantAgentId ?? null,
|
||||
name: parsed.assistantName,
|
||||
avatar: parsed.assistantAvatar ?? null,
|
||||
});
|
||||
state.assistantName = normalized.name;
|
||||
state.assistantAvatar = normalized.avatar;
|
||||
state.assistantAgentId = normalized.agentId ?? null;
|
||||
} catch {
|
||||
// Ignore bootstrap failures; UI will update identity after connecting.
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user