ui: add onboarding mode for control ui
parent
019726f2d1
commit
447db67b18
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,15 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Clawdbot Control</title>
|
||||||
|
<meta name="color-scheme" content="dark light" />
|
||||||
|
<link rel="icon" href="./favicon.ico" sizes="any" />
|
||||||
|
<script type="module" crossorigin src="./assets/index-bYQnHP3a.js"></script>
|
||||||
|
<link rel="stylesheet" crossorigin href="./assets/index-BPDeGGxb.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<clawdbot-app></clawdbot-app>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -37,6 +37,18 @@
|
||||||
grid-template-columns: 0px minmax(0, 1fr);
|
grid-template-columns: 0px minmax(0, 1fr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.shell--onboarding {
|
||||||
|
grid-template-rows: 0 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shell--onboarding .topbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shell--onboarding .content {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.shell--chat-focus .content {
|
.shell--chat-focus .content {
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
gap: 0;
|
gap: 0;
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ type GatewayHost = {
|
||||||
connected: boolean;
|
connected: boolean;
|
||||||
hello: GatewayHelloOk | null;
|
hello: GatewayHelloOk | null;
|
||||||
lastError: string | null;
|
lastError: string | null;
|
||||||
|
onboarding?: boolean;
|
||||||
eventLogBuffer: EventLogEntry[];
|
eventLogBuffer: EventLogEntry[];
|
||||||
eventLog: EventLogEntry[];
|
eventLog: EventLogEntry[];
|
||||||
tab: Tab;
|
tab: Tab;
|
||||||
|
|
@ -153,6 +154,7 @@ export function handleGatewayEvent(host: GatewayHost, evt: GatewayEventFrame) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (evt.event === "agent") {
|
if (evt.event === "agent") {
|
||||||
|
if (host.onboarding) return;
|
||||||
handleAgentEvent(
|
handleAgentEvent(
|
||||||
host as unknown as Parameters<typeof handleAgentEvent>[0],
|
host as unknown as Parameters<typeof handleAgentEvent>[0],
|
||||||
evt.payload as AgentEventPayload | undefined,
|
evt.payload as AgentEventPayload | undefined,
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,10 @@ export function renderTab(state: AppViewState, tab: Tab) {
|
||||||
|
|
||||||
export function renderChatControls(state: AppViewState) {
|
export function renderChatControls(state: AppViewState) {
|
||||||
const sessionOptions = resolveSessionOptions(state.sessionKey, state.sessionsResult);
|
const sessionOptions = resolveSessionOptions(state.sessionKey, state.sessionsResult);
|
||||||
|
const disableThinkingToggle = state.onboarding;
|
||||||
|
const disableFocusToggle = state.onboarding;
|
||||||
|
const showThinking = state.onboarding ? false : state.settings.chatShowThinking;
|
||||||
|
const focusActive = state.onboarding ? true : state.settings.chatFocusMode;
|
||||||
// Refresh icon
|
// Refresh icon
|
||||||
const refreshIcon = html`<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8"></path><path d="M21 3v5h-5"></path></svg>`;
|
const refreshIcon = html`<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8"></path><path d="M21 3v5h-5"></path></svg>`;
|
||||||
const focusIcon = html`<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7V4h3"></path><path d="M20 7V4h-3"></path><path d="M4 17v3h3"></path><path d="M20 17v3h-3"></path><circle cx="12" cy="12" r="3"></circle></svg>`;
|
const focusIcon = html`<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7V4h3"></path><path d="M20 7V4h-3"></path><path d="M4 17v3h3"></path><path d="M20 17v3h-3"></path><circle cx="12" cy="12" r="3"></circle></svg>`;
|
||||||
|
|
@ -90,26 +94,36 @@ export function renderChatControls(state: AppViewState) {
|
||||||
</button>
|
</button>
|
||||||
<span class="chat-controls__separator">|</span>
|
<span class="chat-controls__separator">|</span>
|
||||||
<button
|
<button
|
||||||
class="btn btn--sm btn--icon ${state.settings.chatShowThinking ? "active" : ""}"
|
class="btn btn--sm btn--icon ${showThinking ? "active" : ""}"
|
||||||
@click=${() =>
|
?disabled=${disableThinkingToggle}
|
||||||
|
@click=${() => {
|
||||||
|
if (disableThinkingToggle) return;
|
||||||
state.applySettings({
|
state.applySettings({
|
||||||
...state.settings,
|
...state.settings,
|
||||||
chatShowThinking: !state.settings.chatShowThinking,
|
chatShowThinking: !state.settings.chatShowThinking,
|
||||||
})}
|
});
|
||||||
aria-pressed=${state.settings.chatShowThinking}
|
}}
|
||||||
title="Toggle assistant thinking/working output"
|
aria-pressed=${showThinking}
|
||||||
|
title=${disableThinkingToggle
|
||||||
|
? "Disabled during onboarding"
|
||||||
|
: "Toggle assistant thinking/working output"}
|
||||||
>
|
>
|
||||||
🧠
|
🧠
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="btn btn--sm btn--icon ${state.settings.chatFocusMode ? "active" : ""}"
|
class="btn btn--sm btn--icon ${focusActive ? "active" : ""}"
|
||||||
@click=${() =>
|
?disabled=${disableFocusToggle}
|
||||||
|
@click=${() => {
|
||||||
|
if (disableFocusToggle) return;
|
||||||
state.applySettings({
|
state.applySettings({
|
||||||
...state.settings,
|
...state.settings,
|
||||||
chatFocusMode: !state.settings.chatFocusMode,
|
chatFocusMode: !state.settings.chatFocusMode,
|
||||||
})}
|
});
|
||||||
aria-pressed=${state.settings.chatFocusMode}
|
}}
|
||||||
title="Toggle focus mode (hide sidebar + page header)"
|
aria-pressed=${focusActive}
|
||||||
|
title=${disableFocusToggle
|
||||||
|
? "Disabled during onboarding"
|
||||||
|
: "Toggle focus mode (hide sidebar + page header)"}
|
||||||
>
|
>
|
||||||
${focusIcon}
|
${focusIcon}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -105,12 +105,13 @@ export function renderApp(state: AppViewState) {
|
||||||
const cronNext = state.cronStatus?.nextWakeAtMs ?? null;
|
const cronNext = state.cronStatus?.nextWakeAtMs ?? null;
|
||||||
const chatDisabledReason = state.connected ? null : "Disconnected from gateway.";
|
const chatDisabledReason = state.connected ? null : "Disconnected from gateway.";
|
||||||
const isChat = state.tab === "chat";
|
const isChat = state.tab === "chat";
|
||||||
const chatFocus = isChat && state.settings.chatFocusMode;
|
const chatFocus = isChat && (state.settings.chatFocusMode || state.onboarding);
|
||||||
|
const showThinking = state.onboarding ? false : state.settings.chatShowThinking;
|
||||||
const assistantAvatarUrl = resolveAssistantAvatarUrl(state);
|
const assistantAvatarUrl = resolveAssistantAvatarUrl(state);
|
||||||
const chatAvatarUrl = state.chatAvatarUrl ?? assistantAvatarUrl ?? null;
|
const chatAvatarUrl = state.chatAvatarUrl ?? assistantAvatarUrl ?? null;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="shell ${isChat ? "shell--chat" : ""} ${chatFocus ? "shell--chat-focus" : ""} ${state.settings.navCollapsed ? "shell--nav-collapsed" : ""}">
|
<div class="shell ${isChat ? "shell--chat" : ""} ${chatFocus ? "shell--chat-focus" : ""} ${state.settings.navCollapsed ? "shell--nav-collapsed" : ""} ${state.onboarding ? "shell--onboarding" : ""}">
|
||||||
<header class="topbar">
|
<header class="topbar">
|
||||||
<div class="topbar-left">
|
<div class="topbar-left">
|
||||||
<button
|
<button
|
||||||
|
|
@ -440,7 +441,7 @@ export function renderApp(state: AppViewState) {
|
||||||
void refreshChatAvatar(state);
|
void refreshChatAvatar(state);
|
||||||
},
|
},
|
||||||
thinkingLevel: state.chatThinkingLevel,
|
thinkingLevel: state.chatThinkingLevel,
|
||||||
showThinking: state.settings.chatShowThinking,
|
showThinking,
|
||||||
loading: state.chatLoading,
|
loading: state.chatLoading,
|
||||||
sending: state.chatSending,
|
sending: state.chatSending,
|
||||||
assistantAvatarUrl: chatAvatarUrl,
|
assistantAvatarUrl: chatAvatarUrl,
|
||||||
|
|
@ -455,16 +456,18 @@ export function renderApp(state: AppViewState) {
|
||||||
disabledReason: chatDisabledReason,
|
disabledReason: chatDisabledReason,
|
||||||
error: state.lastError,
|
error: state.lastError,
|
||||||
sessions: state.sessionsResult,
|
sessions: state.sessionsResult,
|
||||||
focusMode: state.settings.chatFocusMode,
|
focusMode: chatFocus,
|
||||||
onRefresh: () => {
|
onRefresh: () => {
|
||||||
state.resetToolStream();
|
state.resetToolStream();
|
||||||
return Promise.all([loadChatHistory(state), refreshChatAvatar(state)]);
|
return Promise.all([loadChatHistory(state), refreshChatAvatar(state)]);
|
||||||
},
|
},
|
||||||
onToggleFocusMode: () =>
|
onToggleFocusMode: () => {
|
||||||
|
if (state.onboarding) return;
|
||||||
state.applySettings({
|
state.applySettings({
|
||||||
...state.settings,
|
...state.settings,
|
||||||
chatFocusMode: !state.settings.chatFocusMode,
|
chatFocusMode: !state.settings.chatFocusMode,
|
||||||
}),
|
});
|
||||||
|
},
|
||||||
onChatScroll: (event) => state.handleChatScroll(event),
|
onChatScroll: (event) => state.handleChatScroll(event),
|
||||||
onDraftChange: (next) => (state.chatMessage = next),
|
onDraftChange: (next) => (state.chatMessage = next),
|
||||||
onSend: () => state.handleSendChat(),
|
onSend: () => state.handleSendChat(),
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ export type AppViewState = {
|
||||||
settings: UiSettings;
|
settings: UiSettings;
|
||||||
password: string;
|
password: string;
|
||||||
tab: Tab;
|
tab: Tab;
|
||||||
|
onboarding: boolean;
|
||||||
basePath: string;
|
basePath: string;
|
||||||
connected: boolean;
|
connected: boolean;
|
||||||
theme: ThemeMode;
|
theme: ThemeMode;
|
||||||
|
|
|
||||||
|
|
@ -87,11 +87,21 @@ declare global {
|
||||||
|
|
||||||
const injectedAssistantIdentity = resolveInjectedAssistantIdentity();
|
const injectedAssistantIdentity = resolveInjectedAssistantIdentity();
|
||||||
|
|
||||||
|
function resolveOnboardingMode(): boolean {
|
||||||
|
if (!window.location.search) return false;
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
const raw = params.get("onboarding");
|
||||||
|
if (!raw) return false;
|
||||||
|
const normalized = raw.trim().toLowerCase();
|
||||||
|
return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on";
|
||||||
|
}
|
||||||
|
|
||||||
@customElement("clawdbot-app")
|
@customElement("clawdbot-app")
|
||||||
export class ClawdbotApp extends LitElement {
|
export class ClawdbotApp extends LitElement {
|
||||||
@state() settings: UiSettings = loadSettings();
|
@state() settings: UiSettings = loadSettings();
|
||||||
@state() password = "";
|
@state() password = "";
|
||||||
@state() tab: Tab = "chat";
|
@state() tab: Tab = "chat";
|
||||||
|
@state() onboarding = resolveOnboardingMode();
|
||||||
@state() connected = false;
|
@state() connected = false;
|
||||||
@state() theme: ThemeMode = this.settings.theme ?? "system";
|
@state() theme: ThemeMode = this.settings.theme ?? "system";
|
||||||
@state() themeResolved: ResolvedTheme = "dark";
|
@state() themeResolved: ResolvedTheme = "dark";
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue