diff --git a/ui/src/e2e/new-session-page.e2e.test.ts b/ui/src/e2e/new-session-page.e2e.test.ts index 44bb3afe3764..560d8c1d2bd5 100644 --- a/ui/src/e2e/new-session-page.e2e.test.ts +++ b/ui/src/e2e/new-session-page.e2e.test.ts @@ -798,6 +798,22 @@ describeControlUiE2e("Control UI new-session page mocked Gateway E2E", () => { await expect .poll(() => page.locator(".new-session-page__error").textContent()) .toContain("cloud worker placement could not be verified"); + const alert = page.locator(".new-session-page__alert"); + await expect.poll(() => alert.getAttribute("role")).toBe("alert"); + await expect.poll(() => alert.locator("svg").count()).toBe(1); + const [alertBox, composerBox] = await Promise.all([ + alert.boundingBox(), + page.locator(".new-session-page__composer").boundingBox(), + ]); + expect(alertBox).not.toBeNull(); + expect(composerBox).not.toBeNull(); + expect( + Math.abs( + (alertBox?.x ?? 0) + + (alertBox?.width ?? 0) / 2 - + ((composerBox?.x ?? 0) + (composerBox?.width ?? 0) / 2), + ), + ).toBeLessThanOrEqual(1); await expect.poll(() => startButton.isDisabled()).toBe(false); await page.getByRole("button", { name: "Start session" }).click(); await expect diff --git a/ui/src/pages/new-session/new-session-page.ts b/ui/src/pages/new-session/new-session-page.ts index 80adb6967a48..294aa2699619 100644 --- a/ui/src/pages/new-session/new-session-page.ts +++ b/ui/src/pages/new-session/new-session-page.ts @@ -6,6 +6,7 @@ import { applicationContext, type ApplicationContext } from "../../app/context.t import { beginNativeWindowDragFromTopInset } from "../../app/native-window-drag.ts"; import { hasOperatorAdminAccess } from "../../app/operator-access.ts"; import { loadSettings } from "../../app/settings.ts"; +import { icons } from "../../components/icons.ts"; import "../../components/tooltip.ts"; import "../../components/web-awesome-popover.ts"; import "../../components/web-awesome-select.ts"; @@ -42,6 +43,15 @@ import { renderAgentSelect, renderFolderSelect, renderWhereSelect } from "./targ const CATALOG_RETRY_DELAYS_MS = [0, 1_000, 3_000] as const; +function renderDraftError(message: string) { + return html` + + `; +} + class NewSessionPage extends OpenClawLightDomElement { @property({ attribute: false }) data: NewSessionRouteData | undefined; @@ -1215,12 +1225,10 @@ class NewSessionPage extends OpenClawLightDomElement { return html`
${this.renderTargetBar()} - ${worktreeNameInvalid - ? html`
${t("newSession.worktreeNameInvalid")}
` - : nothing} - ${this.error ? html`
${this.error}
` : nothing} + ${worktreeNameInvalid ? renderDraftError(t("newSession.worktreeNameInvalid")) : nothing} + ${this.error ? renderDraftError(this.error) : nothing} ${this.submissionOutcomeUnknown - ? html`
${t("newSession.createOutcomeUnknown")}
` + ? renderDraftError(t("newSession.createOutcomeUnknown")) : nothing} ${renderNewSessionDraftComposer({ agentDefaultModel: this.selectedAgent()?.model?.primary, diff --git a/ui/src/styles/new-session.css b/ui/src/styles/new-session.css index b61f26f5d827..e05fefce65e1 100644 --- a/ui/src/styles/new-session.css +++ b/ui/src/styles/new-session.css @@ -361,11 +361,54 @@ wa-popover.new-session-page__select--folder::part(body) { border-top: 1px solid var(--border); } -.new-session-page__error { +.new-session-page__browser > .new-session-page__error { + padding: 8px 10px 0; font-size: 12px; color: var(--danger); } +.new-session-page__alert { + display: flex; + align-items: flex-start; + gap: 10px; + width: fit-content; + max-width: min(48rem, calc(100% - 36px)); + margin: 4px auto 2px; + padding: 10px 12px; + box-sizing: border-box; + text-align: left; + box-shadow: 0 8px 24px color-mix(in srgb, var(--danger) 8%, transparent); +} + +.new-session-page__alert-icon { + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + width: 26px; + height: 26px; + border-radius: 50%; + background: color-mix(in srgb, var(--danger) 14%, transparent); + color: var(--danger); +} + +.new-session-page__alert-icon svg { + width: 15px; + height: 15px; + fill: none; + stroke: currentColor; + stroke-width: 1.8; + stroke-linecap: round; + stroke-linejoin: round; +} + +.new-session-page__alert-message { + min-width: 0; + padding-top: 3px; + line-height: 1.45; + overflow-wrap: anywhere; +} + .new-session-page__browser-use { padding: 6px 14px; border: 1px solid transparent;