Merge branch 'main' into feat/mattermost-channel

This commit is contained in:
Dominic Damoah
2026-01-22 18:17:40 -05:00
committed by GitHub
149 changed files with 5513 additions and 672 deletions

View File

@@ -37,6 +37,18 @@
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 {
padding-top: 0;
gap: 0;

View File

@@ -34,6 +34,7 @@ type GatewayHost = {
connected: boolean;
hello: GatewayHelloOk | null;
lastError: string | null;
onboarding?: boolean;
eventLogBuffer: EventLogEntry[];
eventLog: EventLogEntry[];
tab: Tab;
@@ -153,6 +154,7 @@ export function handleGatewayEvent(host: GatewayHost, evt: GatewayEventFrame) {
}
if (evt.event === "agent") {
if (host.onboarding) return;
handleAgentEvent(
host as unknown as Parameters<typeof handleAgentEvent>[0],
evt.payload as AgentEventPayload | undefined,

View File

@@ -39,6 +39,10 @@ export function renderTab(state: AppViewState, tab: Tab) {
export function renderChatControls(state: AppViewState) {
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
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>`;
@@ -90,26 +94,36 @@ export function renderChatControls(state: AppViewState) {
</button>
<span class="chat-controls__separator">|</span>
<button
class="btn btn--sm btn--icon ${state.settings.chatShowThinking ? "active" : ""}"
@click=${() =>
class="btn btn--sm btn--icon ${showThinking ? "active" : ""}"
?disabled=${disableThinkingToggle}
@click=${() => {
if (disableThinkingToggle) return;
state.applySettings({
...state.settings,
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
class="btn btn--sm btn--icon ${state.settings.chatFocusMode ? "active" : ""}"
@click=${() =>
class="btn btn--sm btn--icon ${focusActive ? "active" : ""}"
?disabled=${disableFocusToggle}
@click=${() => {
if (disableFocusToggle) return;
state.applySettings({
...state.settings,
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}
</button>

View File

@@ -105,12 +105,13 @@ export function renderApp(state: AppViewState) {
const cronNext = state.cronStatus?.nextWakeAtMs ?? null;
const chatDisabledReason = state.connected ? null : "Disconnected from gateway.";
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 chatAvatarUrl = state.chatAvatarUrl ?? assistantAvatarUrl ?? null;
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">
<div class="topbar-left">
<button
@@ -440,7 +441,7 @@ export function renderApp(state: AppViewState) {
void refreshChatAvatar(state);
},
thinkingLevel: state.chatThinkingLevel,
showThinking: state.settings.chatShowThinking,
showThinking,
loading: state.chatLoading,
sending: state.chatSending,
assistantAvatarUrl: chatAvatarUrl,
@@ -455,16 +456,18 @@ export function renderApp(state: AppViewState) {
disabledReason: chatDisabledReason,
error: state.lastError,
sessions: state.sessionsResult,
focusMode: state.settings.chatFocusMode,
focusMode: chatFocus,
onRefresh: () => {
state.resetToolStream();
return Promise.all([loadChatHistory(state), refreshChatAvatar(state)]);
},
onToggleFocusMode: () =>
onToggleFocusMode: () => {
if (state.onboarding) return;
state.applySettings({
...state.settings,
chatFocusMode: !state.settings.chatFocusMode,
}),
});
},
onChatScroll: (event) => state.handleChatScroll(event),
onDraftChange: (next) => (state.chatMessage = next),
onSend: () => state.handleSendChat(),

View File

@@ -34,6 +34,7 @@ export type AppViewState = {
settings: UiSettings;
password: string;
tab: Tab;
onboarding: boolean;
basePath: string;
connected: boolean;
theme: ThemeMode;

View File

@@ -87,11 +87,21 @@ declare global {
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")
export class ClawdbotApp extends LitElement {
@state() settings: UiSettings = loadSettings();
@state() password = "";
@state() tab: Tab = "chat";
@state() onboarding = resolveOnboardingMode();
@state() connected = false;
@state() theme: ThemeMode = this.settings.theme ?? "system";
@state() themeResolved: ResolvedTheme = "dark";

View File

@@ -158,7 +158,8 @@ function renderAvatar(
function isAvatarUrl(value: string): boolean {
return (
/^https?:\/\//i.test(value) ||
/^data:image\//i.test(value)
/^data:image\//i.test(value) ||
/^\//.test(value) // Relative paths from avatar endpoint
);
}