feat: animate Skill Workshop chat landing

This commit is contained in:
Shakker
2026-05-31 19:30:01 +01:00
committed by Shakker
parent 561e993282
commit 11631bf044
4 changed files with 70 additions and 0 deletions

View File

@@ -1315,6 +1315,57 @@
margin-top: 0;
}
@media (prefers-reduced-motion: no-preference) {
.content--chat-workshop-handoff .content-header {
animation: workshop-chat-header-enter 340ms cubic-bezier(0.16, 1, 0.3, 1) both;
}
.content--chat-workshop-handoff .chat-thread {
animation: workshop-chat-thread-enter 420ms cubic-bezier(0.16, 1, 0.3, 1) both;
}
.content--chat-workshop-handoff .agent-chat__input {
animation: workshop-chat-composer-enter 520ms cubic-bezier(0.16, 1, 0.3, 1) 90ms both;
}
}
@keyframes workshop-chat-header-enter {
from {
opacity: 0;
transform: translateY(-6px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes workshop-chat-thread-enter {
from {
opacity: 0;
transform: translateY(12px) scale(0.992);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
@keyframes workshop-chat-composer-enter {
0% {
opacity: 0;
transform: translateY(18px) scale(0.988);
}
65% {
opacity: 1;
transform: translateY(-2px) scale(1);
}
100% {
opacity: 1;
transform: translateY(0) scale(1);
}
}
.content--workboard {
display: flex;
flex-direction: column;

View File

@@ -502,6 +502,7 @@ const SKILL_WORKSHOP_CURRENT_CHAT_REVISIONS_KEY =
"openclaw:control-ui:skill-workshop-current-chat-revisions:v1";
const SKILL_WORKSHOP_REVISION_SESSIONS_KEY =
"openclaw:control-ui:skill-workshop-revision-sessions:v1";
const SKILL_WORKSHOP_CHAT_HANDOFF_MS = 900;
const MAX_SKILL_WORKSHOP_REVIEWED_KEYS = 500;
const MAX_SKILL_WORKSHOP_REVISION_SESSIONS = 200;
const DEFAULT_SKILL_WORKSHOP_QUEUE_WIDTH = 360;
@@ -917,6 +918,7 @@ async function sendSkillWorkshopRevisionRequest(
if (!sessionKey) {
throw new Error(state.sessionsError ?? "Could not prepare a Skill Workshop session.");
}
startSkillWorkshopChatHandoff(state);
if (state.tab !== "chat") {
state.setTab("chat" as Tab);
}
@@ -928,6 +930,17 @@ async function sendSkillWorkshopRevisionRequest(
await state.handleSendChat(message);
}
function startSkillWorkshopChatHandoff(state: AppViewState): void {
if (state.skillWorkshopChatHandoffTimer) {
globalThis.clearTimeout(state.skillWorkshopChatHandoffTimer);
}
state.skillWorkshopChatHandoffActive = true;
state.skillWorkshopChatHandoffTimer = globalThis.setTimeout(() => {
state.skillWorkshopChatHandoffActive = false;
state.skillWorkshopChatHandoffTimer = null;
}, SKILL_WORKSHOP_CHAT_HANDOFF_MS);
}
function loadDismissedUpdateBanner(): DismissedUpdateBanner | null {
try {
const raw = getSafeLocalStorage()?.getItem(UPDATE_BANNER_DISMISS_KEY);
@@ -2371,6 +2384,8 @@ export function renderApp(state: AppViewState) {
<main
class="content ${isChat ? "content--chat" : ""} ${state.tab === "logs"
? "content--logs"
: ""} ${isChat && state.skillWorkshopChatHandoffActive
? "content--chat-workshop-handoff"
: ""} ${state.tab === "workboard" ? "content--workboard" : ""} ${state.tab ===
"skillWorkshop"
? `content--skill-workshop ${state.skillWorkshopMode === "today" ? "content--skill-workshop-today" : ""}`

View File

@@ -446,6 +446,8 @@ export type AppViewState = {
skillWorkshopRevisionKey: string | null;
skillWorkshopRevisionDraft: string;
skillWorkshopActionNoticeTimer?: ReturnType<typeof globalThis.setTimeout> | number | null;
skillWorkshopChatHandoffActive?: boolean;
skillWorkshopChatHandoffTimer?: ReturnType<typeof globalThis.setTimeout> | number | null;
healthLoading: boolean;
healthResult: HealthSummary | null;
healthError: string | null;

View File

@@ -657,6 +657,8 @@ export class OpenClawApp extends LitElement {
@state() skillWorkshopRevisionKey: string | null = null;
@state() skillWorkshopRevisionDraft = "";
skillWorkshopActionNoticeTimer: ReturnType<typeof globalThis.setTimeout> | number | null = null;
@state() skillWorkshopChatHandoffActive = false;
skillWorkshopChatHandoffTimer: ReturnType<typeof globalThis.setTimeout> | number | null = null;
@state() healthLoading = false;
@state() healthResult: HealthSummary | null = null;