fix(console): tighten hero and persist pairing state
This commit is contained in:
parent
55d96d2ecb
commit
68bf1e2c1e
@ -169,26 +169,20 @@ export function HeroSection() {
|
||||
const presentation = resolveHomepageVideoPresentation(entry);
|
||||
|
||||
const heroCopy = isChinese
|
||||
? {
|
||||
? {
|
||||
eyebrow: "AI Native Workspace",
|
||||
title: "直接说出你的需求,剩下的交给 AI",
|
||||
subtitle: "从想法到上线,AI 自动完成构建、部署与优化。",
|
||||
demoLabel: "产品演示",
|
||||
demoHint:
|
||||
"这里展示当前域名对应的产品演示链接。主站默认走 YouTube,中国站可切到 Bilibili,也可以继续按域名覆盖。",
|
||||
}
|
||||
: {
|
||||
eyebrow: "AI Native Workspace",
|
||||
title: "Describe what you need. Let AI handle the rest.",
|
||||
subtitle:
|
||||
"From idea to launch, AI can assemble, deploy, and optimize the work.",
|
||||
demoLabel: "Product demo",
|
||||
demoHint:
|
||||
"This section resolves the product demo for the current host. The default can use YouTube while regional hosts override it.",
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="relative overflow-hidden rounded-[2.75rem] border border-slate-900/10 bg-[linear-gradient(180deg,#ffffff,#faf7f2)] p-6 shadow-[0_24px_56px_rgba(15,23,42,0.05)] sm:p-8 lg:p-10">
|
||||
<section className="relative overflow-hidden rounded-[1.75rem] border border-slate-900/10 bg-[linear-gradient(180deg,#ffffff,#faf7f2)] p-3 shadow-[0_18px_40px_rgba(15,23,42,0.05)] sm:p-4 lg:p-5">
|
||||
<div aria-hidden className="pointer-events-none absolute inset-0">
|
||||
<div className="absolute left-[8%] top-[8%] h-[16rem] w-[16rem] rounded-full bg-[radial-gradient(circle,rgba(37,78,219,0.1),transparent_64%)] blur-3xl" />
|
||||
<div className="absolute left-[30%] top-[12%] h-[14rem] w-[14rem] rounded-full bg-[radial-gradient(circle,rgba(245,211,170,0.42),transparent_66%)] blur-3xl" />
|
||||
@ -196,35 +190,25 @@ export function HeroSection() {
|
||||
<div className="absolute inset-x-0 top-0 h-[18rem] bg-[linear-gradient(180deg,rgba(255,255,255,0.78),rgba(255,255,255,0)_72%)]" />
|
||||
</div>
|
||||
|
||||
<div className="relative grid gap-8 lg:grid-cols-[0.96fr_1.04fr] lg:gap-12">
|
||||
<div className="flex flex-col gap-6 pt-2">
|
||||
<div className="overflow-hidden rounded-[2rem] border border-slate-900/10 bg-[linear-gradient(180deg,rgba(255,255,255,0.96),rgba(243,246,251,0.96))] shadow-[0_24px_60px_rgba(15,23,42,0.08)]">
|
||||
<div className="border-b border-slate-900/10 px-5 py-4 sm:px-6">
|
||||
<div className="relative grid gap-4 lg:grid-cols-[0.98fr_1.02fr] lg:gap-5">
|
||||
<div className="flex flex-col gap-3 pt-1">
|
||||
<div className="overflow-hidden rounded-[1.35rem] border border-slate-900/10 bg-[linear-gradient(180deg,rgba(255,255,255,0.96),rgba(243,246,251,0.96))] shadow-[0_18px_44px_rgba(15,23,42,0.07)]">
|
||||
<div className="border-b border-slate-900/10 px-4 py-3 sm:px-4.5">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<p className={HOME_SECTION_LABEL_CLASS}>{heroCopy.demoLabel}</p>
|
||||
<p className="mt-2 max-w-md text-sm leading-6 text-text-muted">
|
||||
{heroCopy.demoHint}
|
||||
</p>
|
||||
</div>
|
||||
<span className="hidden rounded-full border border-slate-900/10 bg-white/90 px-3 py-1 text-xs font-semibold text-slate-600 sm:inline-flex">
|
||||
{entry.domain?.trim()
|
||||
? `${isChinese ? "当前域名" : "Host"}: ${entry.domain}`
|
||||
: isChinese
|
||||
? "默认主站配置"
|
||||
: "Default site config"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4 p-4 sm:p-5">
|
||||
<div className="space-y-3 p-3 sm:p-3.5">
|
||||
<DemoVideoSurface presentation={presentation} isChinese={isChinese} />
|
||||
<div className="flex flex-wrap gap-2 text-xs text-slate-500">
|
||||
<a
|
||||
href={entry.videoUrl}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="inline-flex items-center rounded-full border border-slate-900/10 bg-white px-3 py-1.5 font-semibold text-slate-600 transition hover:border-slate-300 hover:text-slate-900"
|
||||
className="inline-flex items-center rounded-full border border-slate-900/10 bg-white px-2.5 py-1 font-semibold text-slate-600 transition hover:border-slate-300 hover:text-slate-900"
|
||||
>
|
||||
{isChinese ? "打开原始链接" : "Open source link"}
|
||||
</a>
|
||||
@ -232,47 +216,32 @@ export function HeroSection() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-5">
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-2">
|
||||
<p className={HOME_SECTION_LABEL_CLASS}>{heroCopy.eyebrow}</p>
|
||||
<h1
|
||||
className={cn(
|
||||
"max-w-[10ch] leading-[0.94] text-heading",
|
||||
isChinese
|
||||
? "text-[3.05rem] font-semibold tracking-[-0.055em] sm:text-[3.6rem] lg:text-[4.2rem]"
|
||||
: "editorial-display text-[3rem] tracking-[-0.05em] sm:text-[3.5rem] lg:text-[4.3rem]",
|
||||
)}
|
||||
>
|
||||
{heroCopy.title}
|
||||
</h1>
|
||||
<p className="max-w-xl text-[1rem] leading-8 text-text-muted sm:text-[1.08rem]">
|
||||
<p className="max-w-xl text-[0.95rem] leading-7 text-text-muted sm:text-[1rem]">
|
||||
{heroCopy.subtitle}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="lg:pl-4">
|
||||
<div className="overflow-hidden rounded-[2rem] border border-slate-900/10 bg-[linear-gradient(180deg,rgba(255,255,255,0.94),rgba(246,248,251,0.98))] shadow-[0_24px_60px_rgba(15,23,42,0.08)]">
|
||||
<div className="border-b border-slate-900/10 px-5 py-4 sm:px-6">
|
||||
<div className="lg:pl-1">
|
||||
<div className="overflow-hidden rounded-[1.35rem] border border-slate-900/10 bg-[linear-gradient(180deg,rgba(255,255,255,0.94),rgba(246,248,251,0.98))] shadow-[0_18px_44px_rgba(15,23,42,0.07)]">
|
||||
<div className="border-b border-slate-900/10 px-4 py-3 sm:px-4.5">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<p className={HOME_SECTION_LABEL_CLASS}>
|
||||
{isChinese ? "X 助手" : "X Assistant"}
|
||||
</p>
|
||||
<p className="mt-2 max-w-md text-sm leading-6 text-text-muted">
|
||||
{isChinese
|
||||
? "首页只保留一个主路径:先提问,再由助手拆解任务、调用能力并推进执行。"
|
||||
: "The homepage keeps one primary path: ask first, then let the assistant plan and execute."}
|
||||
</p>
|
||||
</div>
|
||||
<span className="hidden rounded-full border border-slate-900/10 bg-white/90 px-3 py-1 text-xs font-semibold text-slate-600 sm:inline-flex">
|
||||
<span className="hidden rounded-full border border-slate-900/10 bg-white/90 px-2.5 py-0.5 text-xs font-semibold text-slate-600 sm:inline-flex">
|
||||
{isChinese ? "对话即入口" : "Prompt-first"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-4 sm:p-5">
|
||||
<div className="p-3 sm:p-3.5">
|
||||
<OpenClawAssistantPane
|
||||
defaults={assistantDefaultsSWR.data ?? EMPTY_ASSISTANT_DEFAULTS}
|
||||
autoSubmitInitialQuestion={false}
|
||||
|
||||
@ -43,6 +43,7 @@ import {
|
||||
type OpenClawStreamEvent,
|
||||
type ThinkingLevel,
|
||||
} from "@/lib/openclaw/types";
|
||||
import { useUserStore } from "@/lib/userStore";
|
||||
import { useOpenClawConsoleStore } from "@/state/openclawConsoleStore";
|
||||
|
||||
type OpenClawAssistantPaneProps = {
|
||||
@ -75,6 +76,24 @@ type ConnectGatewayOptions = {
|
||||
force?: boolean;
|
||||
};
|
||||
|
||||
type PersistedPairingRequiredState = {
|
||||
signature: string;
|
||||
errorMessage: string;
|
||||
scope: string;
|
||||
savedAtMs: number;
|
||||
ttlMs: number;
|
||||
};
|
||||
|
||||
type PersistedPairingRequiredLookup = {
|
||||
state: PersistedPairingRequiredState | null;
|
||||
expired: boolean;
|
||||
};
|
||||
|
||||
const PAIRING_REQUIRED_SESSION_STORAGE_KEY =
|
||||
"openclaw:pairing-required-state";
|
||||
const PAIRING_REQUIRED_STATE_TTL_MS = 1000 * 60 * 60 * 12;
|
||||
const PAIRING_REQUIRED_GUEST_TTL_MS = 1000 * 60 * 60;
|
||||
|
||||
export type OpenClawAssistantViewState = {
|
||||
connectionState: ConnectionState;
|
||||
healthBadge: string;
|
||||
@ -154,6 +173,99 @@ function buildPairingRequiredSignature(
|
||||
return `${deviceId}::${requestId}::${reason}`;
|
||||
}
|
||||
|
||||
function buildPairingPersistenceScope(params: {
|
||||
openclawUrl: string;
|
||||
openclawOrigin: string;
|
||||
userId: string;
|
||||
}): string {
|
||||
return `${params.userId.trim()}::${params.openclawUrl.trim()}::${params.openclawOrigin.trim()}`;
|
||||
}
|
||||
|
||||
function inspectPersistedPairingRequiredState(
|
||||
scope: string,
|
||||
): PersistedPairingRequiredLookup {
|
||||
if (typeof window === "undefined") {
|
||||
return { state: null, expired: false };
|
||||
}
|
||||
|
||||
try {
|
||||
const raw = window.sessionStorage.getItem(
|
||||
PAIRING_REQUIRED_SESSION_STORAGE_KEY,
|
||||
);
|
||||
if (!raw) {
|
||||
return { state: null, expired: false };
|
||||
}
|
||||
|
||||
const parsed = JSON.parse(raw) as PersistedPairingRequiredState;
|
||||
if (
|
||||
!parsed ||
|
||||
typeof parsed.signature !== "string" ||
|
||||
typeof parsed.errorMessage !== "string" ||
|
||||
typeof parsed.scope !== "string" ||
|
||||
typeof parsed.savedAtMs !== "number"
|
||||
) {
|
||||
window.sessionStorage.removeItem(PAIRING_REQUIRED_SESSION_STORAGE_KEY);
|
||||
return { state: null, expired: false };
|
||||
}
|
||||
|
||||
const ttlMs =
|
||||
typeof parsed.ttlMs === "number" && Number.isFinite(parsed.ttlMs)
|
||||
? parsed.ttlMs
|
||||
: PAIRING_REQUIRED_STATE_TTL_MS;
|
||||
|
||||
const isExpired = Date.now() - parsed.savedAtMs > ttlMs;
|
||||
|
||||
if (parsed.scope !== scope) {
|
||||
window.sessionStorage.removeItem(PAIRING_REQUIRED_SESSION_STORAGE_KEY);
|
||||
return { state: null, expired: false };
|
||||
}
|
||||
|
||||
if (isExpired) {
|
||||
window.sessionStorage.removeItem(PAIRING_REQUIRED_SESSION_STORAGE_KEY);
|
||||
return { state: null, expired: true };
|
||||
}
|
||||
|
||||
return {
|
||||
state: {
|
||||
...parsed,
|
||||
ttlMs,
|
||||
},
|
||||
expired: false,
|
||||
};
|
||||
} catch {
|
||||
return { state: null, expired: false };
|
||||
}
|
||||
}
|
||||
|
||||
function persistPairingRequiredState(
|
||||
state: PersistedPairingRequiredState,
|
||||
): void {
|
||||
if (typeof window === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
window.sessionStorage.setItem(
|
||||
PAIRING_REQUIRED_SESSION_STORAGE_KEY,
|
||||
JSON.stringify(state),
|
||||
);
|
||||
} catch {
|
||||
// Ignore unavailable storage.
|
||||
}
|
||||
}
|
||||
|
||||
function clearPersistedPairingRequiredState(): void {
|
||||
if (typeof window === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
window.sessionStorage.removeItem(PAIRING_REQUIRED_SESSION_STORAGE_KEY);
|
||||
} catch {
|
||||
// Ignore unavailable storage.
|
||||
}
|
||||
}
|
||||
|
||||
function renderMarkdown(value: string): string {
|
||||
return DOMPurify.sanitize(marked.parse(value) as string);
|
||||
}
|
||||
@ -321,6 +433,7 @@ export function OpenClawAssistantPane({
|
||||
const [errorMessage, setErrorMessage] = useState("");
|
||||
const [isSending, setIsSending] = useState(false);
|
||||
const [isCapturing, setIsCapturing] = useState(false);
|
||||
const [guestSessionExpired, setGuestSessionExpired] = useState(false);
|
||||
|
||||
const defaultsLoaded = useOpenClawConsoleStore(
|
||||
(state) => state.defaultsLoaded,
|
||||
@ -360,9 +473,20 @@ export function OpenClawAssistantPane({
|
||||
const setSelectedSessionKey = useOpenClawConsoleStore(
|
||||
(state) => state.setSelectedSessionKey,
|
||||
);
|
||||
const sessionUser = useUserStore((state) => state.user);
|
||||
|
||||
const compact = variant === "sidebar";
|
||||
const minimalPage = variant === "page";
|
||||
const pairingPersistenceUserId =
|
||||
sessionUser?.uuid?.trim() || sessionUser?.id?.trim() || "anonymous";
|
||||
const pairingPersistenceTtlMs = sessionUser?.isGuest
|
||||
? PAIRING_REQUIRED_GUEST_TTL_MS
|
||||
: PAIRING_REQUIRED_STATE_TTL_MS;
|
||||
const pairingPersistenceScope = buildPairingPersistenceScope({
|
||||
openclawUrl,
|
||||
openclawOrigin,
|
||||
userId: pairingPersistenceUserId,
|
||||
});
|
||||
const locale = isChinese ? "zh-CN" : "en-US";
|
||||
const compactConnected = compact && connectionState === "ready";
|
||||
const showMinimalAgentSelect =
|
||||
@ -445,6 +569,13 @@ export function OpenClawAssistantPane({
|
||||
"当前没有可用的 OpenClaw 地址。先到融合设置填写 gateway / vault / APISIX,再回来启动 XWorkmate。",
|
||||
"No OpenClaw endpoint is available yet. Configure gateway, vault, and APISIX first, then return to XWorkmate.",
|
||||
),
|
||||
guestSessionExpired: pickCopy(
|
||||
isChinese,
|
||||
"演示模式已超过 1 小时。请注册或登录后继续使用助手。",
|
||||
"Demo mode has exceeded 1 hour. Register or sign in to continue using the assistant.",
|
||||
),
|
||||
login: pickCopy(isChinese, "登录", "Sign in"),
|
||||
register: pickCopy(isChinese, "注册", "Register"),
|
||||
openIntegrations: pickCopy(
|
||||
isChinese,
|
||||
"打开接口集成",
|
||||
@ -572,24 +703,32 @@ export function OpenClawAssistantPane({
|
||||
const presentAssistantError = useCallback(
|
||||
(payload: AssistantApiErrorPayload, fallback: string) => {
|
||||
const signature = buildPairingRequiredSignature(payload);
|
||||
const formattedMessage = formatAssistantApiError({
|
||||
payload,
|
||||
isChinese,
|
||||
fallback,
|
||||
});
|
||||
|
||||
if (signature) {
|
||||
if (lastPairingRequiredSignatureRef.current === signature) {
|
||||
return;
|
||||
}
|
||||
lastPairingRequiredSignatureRef.current = signature;
|
||||
setGuestSessionExpired(false);
|
||||
persistPairingRequiredState({
|
||||
signature,
|
||||
errorMessage: formattedMessage,
|
||||
scope: pairingPersistenceScope,
|
||||
savedAtMs: Date.now(),
|
||||
ttlMs: pairingPersistenceTtlMs,
|
||||
});
|
||||
} else {
|
||||
lastPairingRequiredSignatureRef.current = null;
|
||||
}
|
||||
|
||||
setErrorMessage(
|
||||
formatAssistantApiError({
|
||||
payload,
|
||||
isChinese,
|
||||
fallback,
|
||||
}),
|
||||
);
|
||||
setErrorMessage(formattedMessage);
|
||||
},
|
||||
[isChinese],
|
||||
[isChinese, pairingPersistenceScope, pairingPersistenceTtlMs],
|
||||
);
|
||||
|
||||
const connectGateway = useCallback(
|
||||
@ -606,6 +745,7 @@ export function OpenClawAssistantPane({
|
||||
|
||||
if (!options?.force && lastConnectPairingSignatureRef.current) {
|
||||
setConnectionState("error");
|
||||
setErrorMessage((current) => current);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -652,6 +792,7 @@ export function OpenClawAssistantPane({
|
||||
|
||||
const data = payload as OpenClawBootstrapResponse;
|
||||
lastConnectPairingSignatureRef.current = null;
|
||||
clearPersistedPairingRequiredState();
|
||||
|
||||
setConnectionState("ready");
|
||||
setAgents(data.agents);
|
||||
@ -902,7 +1043,29 @@ export function OpenClawAssistantPane({
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!defaultsLoaded || bootstrappedRef.current) {
|
||||
const persisted = inspectPersistedPairingRequiredState(
|
||||
pairingPersistenceScope,
|
||||
);
|
||||
if (persisted.state) {
|
||||
lastPairingRequiredSignatureRef.current = persisted.state.signature;
|
||||
lastConnectPairingSignatureRef.current = persisted.state.signature;
|
||||
setGuestSessionExpired(false);
|
||||
setConnectionState("error");
|
||||
setErrorMessage(persisted.state.errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
if (sessionUser?.isGuest && persisted.expired) {
|
||||
lastPairingRequiredSignatureRef.current = null;
|
||||
lastConnectPairingSignatureRef.current = "guest-session-expired";
|
||||
setGuestSessionExpired(true);
|
||||
setConnectionState("error");
|
||||
setErrorMessage(copy.guestSessionExpired);
|
||||
}
|
||||
}, [copy.guestSessionExpired, pairingPersistenceScope, sessionUser?.isGuest]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!defaultsLoaded || bootstrappedRef.current || guestSessionExpired) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -912,14 +1075,17 @@ export function OpenClawAssistantPane({
|
||||
|
||||
bootstrappedRef.current = true;
|
||||
void connectGateway();
|
||||
}, [connectGateway, defaultsLoaded, openclawUrl]);
|
||||
}, [connectGateway, defaultsLoaded, guestSessionExpired, openclawUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
lastConnectPairingSignatureRef.current = null;
|
||||
lastPairingRequiredSignatureRef.current = null;
|
||||
setGuestSessionExpired(false);
|
||||
}, [
|
||||
openclawOrigin,
|
||||
openclawToken,
|
||||
openclawUrl,
|
||||
pairingPersistenceUserId,
|
||||
vaultNamespace,
|
||||
vaultSecretKey,
|
||||
vaultSecretPath,
|
||||
@ -1124,7 +1290,12 @@ export function OpenClawAssistantPane({
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="flex min-h-0 flex-1 flex-col">
|
||||
<div
|
||||
className={cn(
|
||||
"flex min-h-0 flex-1 flex-col",
|
||||
compact && "grid grid-rows-[minmax(0,1fr)_auto]",
|
||||
)}
|
||||
>
|
||||
{!compact && !minimalPage ? (
|
||||
<div className="border-b border-[color:var(--color-surface-border)] px-3 py-2.5">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
@ -1153,7 +1324,12 @@ export function OpenClawAssistantPane({
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="flex-1 overflow-y-auto px-3 py-3">
|
||||
<div
|
||||
className={cn(
|
||||
"flex-1 overflow-y-auto px-3 py-3",
|
||||
compact && "min-h-0 overscroll-contain",
|
||||
)}
|
||||
>
|
||||
{!showConversation ? (
|
||||
<div className="flex h-full flex-col items-center justify-center gap-4 rounded-[var(--radius-xl)] border border-dashed border-[color:var(--color-surface-border)] bg-[var(--color-surface-muted)]/40 px-5 text-center">
|
||||
<div className="flex h-12 w-12 items-center justify-center rounded-[var(--radius-lg)] bg-[var(--color-primary-muted)] text-[var(--color-primary)]">
|
||||
@ -1284,7 +1460,12 @@ export function OpenClawAssistantPane({
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="border-t border-[color:var(--color-surface-border)] px-3 py-3">
|
||||
<div
|
||||
className={cn(
|
||||
"border-t border-[color:var(--color-surface-border)] px-3 py-3",
|
||||
compact && "shrink-0",
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
{modeOptions.map((option) => (
|
||||
<button
|
||||
@ -1350,15 +1531,40 @@ export function OpenClawAssistantPane({
|
||||
) : null}
|
||||
|
||||
{errorMessage ? (
|
||||
<div className="mt-2.5 whitespace-pre-wrap rounded-[var(--radius-lg)] border border-[color:var(--color-danger-border)] bg-[var(--color-danger-muted)]/40 px-3 py-2 text-sm text-[var(--color-danger-foreground)]">
|
||||
{errorMessage}
|
||||
<div className="mt-2.5 space-y-2">
|
||||
<div className="whitespace-pre-wrap rounded-[var(--radius-lg)] border border-[color:var(--color-danger-border)] bg-[var(--color-danger-muted)]/40 px-3 py-2 text-sm text-[var(--color-danger-foreground)]">
|
||||
{errorMessage}
|
||||
</div>
|
||||
{guestSessionExpired ? (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => router.push("/login")}
|
||||
className="inline-flex items-center gap-2 rounded-full bg-[var(--color-primary)] px-3 py-1.5 text-xs font-semibold text-[var(--color-primary-foreground)] transition hover:opacity-90"
|
||||
>
|
||||
{copy.login}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => router.push("/register")}
|
||||
className="inline-flex items-center gap-2 rounded-full border border-[color:var(--color-surface-border)] bg-[var(--color-surface)] px-3 py-1.5 text-xs font-semibold text-[var(--color-text)] transition hover:border-[color:var(--color-primary-border)] hover:bg-[var(--color-surface-muted)]"
|
||||
>
|
||||
{copy.register}
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="mt-2.5 flex min-h-[248px] flex-1 flex-col rounded-[var(--radius-xl)] border border-[color:var(--color-surface-border)] bg-[var(--color-surface)] p-2.5 shadow-[var(--shadow-sm)]">
|
||||
<div
|
||||
className={cn(
|
||||
"mt-2.5 flex flex-1 flex-col rounded-[var(--radius-xl)] border border-[color:var(--color-surface-border)] bg-[var(--color-surface)] p-2.5 shadow-[var(--shadow-sm)]",
|
||||
compact ? "min-h-[184px]" : "min-h-[248px]",
|
||||
)}
|
||||
>
|
||||
<textarea
|
||||
ref={textareaRef}
|
||||
rows={compact ? 3 : 4}
|
||||
rows={compact ? 2 : 4}
|
||||
value={composerValue}
|
||||
placeholder={copy.placeholder}
|
||||
onChange={(event) => setComposerValue(event.target.value)}
|
||||
@ -1370,7 +1576,10 @@ export function OpenClawAssistantPane({
|
||||
void addFiles(clipboardFiles);
|
||||
}
|
||||
}}
|
||||
className="min-h-[148px] w-full flex-1 resize-none bg-transparent text-sm leading-6 text-[var(--color-text)] outline-none placeholder:text-[var(--color-text-subtle)]/70"
|
||||
className={cn(
|
||||
"w-full flex-1 resize-none bg-transparent text-sm leading-6 text-[var(--color-text)] outline-none placeholder:text-[var(--color-text-subtle)]/70",
|
||||
compact ? "min-h-[92px]" : "min-h-[148px]",
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="mt-2.5 flex flex-wrap items-center gap-2">
|
||||
|
||||
Loading…
Reference in New Issue
Block a user