diff --git a/ui/src/styles/components.css b/ui/src/styles/components.css index 896334fa93e..af4d8126b7d 100644 --- a/ui/src/styles/components.css +++ b/ui/src/styles/components.css @@ -126,6 +126,36 @@ user-select: all; } +.login-gate__command { + display: flex; + align-items: center; + gap: 8px; + margin: 4px 0 2px; + padding: 5px 8px 5px 10px; + background: var(--bg); + border: 1px solid var(--border); + border-radius: var(--radius); + color: var(--fg); + cursor: copy; +} + +.login-gate__command:hover { + border-color: var(--accent); +} + +.login-gate__command:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 2px; +} + +.login-gate__command code { + flex: 1; + margin: 0; + padding: 0; + background: transparent; + border: 0; +} + .login-gate__docs { margin-top: 10px; font-size: 11px; diff --git a/ui/src/ui/chat/copy-as-markdown.ts b/ui/src/ui/chat/copy-as-markdown.ts index 7081fd1959f..f5b31b3284e 100644 --- a/ui/src/ui/chat/copy-as-markdown.ts +++ b/ui/src/ui/chat/copy-as-markdown.ts @@ -92,6 +92,10 @@ function createCopyButton(options: CopyButtonOptions): TemplateResult { `; } -export function renderCopyAsMarkdownButton(markdown: string): TemplateResult { - return createCopyButton({ text: () => markdown, label: COPY_LABEL }); +export function renderCopyButton(text: string, label = COPY_LABEL): TemplateResult { + return createCopyButton({ text: () => text, label }); +} + +export function renderCopyAsMarkdownButton(markdown: string): TemplateResult { + return renderCopyButton(markdown, COPY_LABEL); } diff --git a/ui/src/ui/views/connect-command.ts b/ui/src/ui/views/connect-command.ts new file mode 100644 index 00000000000..a609577072e --- /dev/null +++ b/ui/src/ui/views/connect-command.ts @@ -0,0 +1,38 @@ +import { html } from "lit"; +import { renderCopyButton } from "../chat/copy-as-markdown.ts"; + +async function copyCommand(command: string) { + try { + await navigator.clipboard.writeText(command); + } catch { + // Best effort only; the explicit copy button provides visible feedback. + } +} + +export function renderConnectCommand(command: string) { + return html` +
{ + if ((e.target as HTMLElement | null)?.closest(".chat-copy-btn")) { + return; + } + await copyCommand(command); + }} + @keydown=${async (e: KeyboardEvent) => { + if (e.key !== "Enter" && e.key !== " ") { + return; + } + e.preventDefault(); + await copyCommand(command); + }} + > + ${command} + ${renderCopyButton(command, "Copy command")} +
+ `; +} diff --git a/ui/src/ui/views/login-gate.ts b/ui/src/ui/views/login-gate.ts index 6127053a6a3..9b10fa9c943 100644 --- a/ui/src/ui/views/login-gate.ts +++ b/ui/src/ui/views/login-gate.ts @@ -4,6 +4,7 @@ import type { AppViewState } from "../app-view-state.ts"; import { icons } from "../icons.ts"; import { normalizeBasePath } from "../navigation.ts"; import { agentLogoUrl } from "./agents-utils.ts"; +import { renderConnectCommand } from "./connect-command.ts"; export function renderLoginGate(state: AppViewState) { const basePath = normalizeBasePath(state.basePath ?? ""); @@ -107,8 +108,10 @@ export function renderLoginGate(state: AppViewState) {
${t("overview.connection.title")}
    -
  1. ${t("overview.connection.step1")}openclaw gateway run
  2. -
  3. ${t("overview.connection.step2")}openclaw dashboard --no-open
  4. +
  5. + ${t("overview.connection.step1")}${renderConnectCommand("openclaw gateway run")} +
  6. +
  7. ${t("overview.connection.step2")} ${renderConnectCommand("openclaw dashboard")}
  8. ${t("overview.connection.step3")}
diff --git a/ui/src/ui/views/overview.ts b/ui/src/ui/views/overview.ts index f2c17d481f7..7f103e08bc2 100644 --- a/ui/src/ui/views/overview.ts +++ b/ui/src/ui/views/overview.ts @@ -14,6 +14,7 @@ import type { SessionsUsageResult, SkillStatusReport, } from "../types.ts"; +import { renderConnectCommand } from "./connect-command.ts"; import { renderOverviewAttention } from "./overview-attention.ts"; import { renderOverviewCards } from "./overview-cards.ts"; import { renderOverviewEventLog } from "./overview-event-log.ts"; @@ -316,9 +317,12 @@ export function renderOverview(props: OverviewProps) {