Skip to content

Commit a057d76

Browse files
committed
fix: stabilize v1.5.1 workspace build
1 parent 7431c19 commit a057d76

8 files changed

+123
-78
lines changed

src/components/agent/chat/components/EmptyStateHero.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
EMPTY_STATE_BADGE_TONE_CLASSNAMES,
77
EMPTY_STATE_CARD_SURFACE_CLASSNAME,
88
EMPTY_STATE_ICON_TONE_CLASSNAMES,
9+
type EmptyStateTone,
910
} from "./emptyStateSurfaceTokens";
1011

1112
const heroReveal = keyframes`
@@ -314,7 +315,7 @@ const CardEyebrow = styled.span`
314315
export interface EmptyStateHeroBadge {
315316
key: string;
316317
label: string;
317-
tone?: "slate" | "sky" | "emerald" | "amber";
318+
tone?: EmptyStateTone;
318319
}
319320

320321
export interface EmptyStateHeroCard {
@@ -326,7 +327,7 @@ export interface EmptyStateHeroCard {
326327
icon: ReactNode;
327328
imageSrc?: string;
328329
imageAlt?: string;
329-
tone?: "slate" | "sky" | "emerald" | "amber";
330+
tone?: EmptyStateTone;
330331
action?: ReactNode;
331332
onMediaAction?: () => void;
332333
mediaActionLabel?: string;

src/components/agent/chat/hooks/agentStreamSlashSkillPreflight.test.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@ function createPreparedSend(
4040
};
4141
}
4242

43-
function createEnv() {
43+
type SlashSkillPreflightTestEnv =
44+
Parameters<typeof maybeHandleSlashSkillBeforeSend>[0]["env"];
45+
46+
function createEnv(): SlashSkillPreflightTestEnv {
4447
return {
4548
ensureSession: async () => "session-1",
4649
sessionIdRef: { current: null },
@@ -53,7 +56,7 @@ function createEnv() {
5356
playTypewriterSound: vi.fn(),
5457
playToolcallSound: vi.fn(),
5558
onWriteFile: vi.fn(),
56-
} as never;
59+
};
5760
}
5861

5962
describe("agentStreamSlashSkillPreflight", () => {

src/components/agent/chat/workspace/serviceSkillSceneLaunch.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,14 @@ interface ParsedRuntimeSceneCommand {
4040
userInput: string;
4141
}
4242

43+
type ServiceSceneLaunchRequestContext =
44+
| Record<string, unknown>
45+
| ServiceSkillClawLaunchContext;
46+
4347
interface ServiceSceneLaunchRequest {
4448
skill: ServiceSkillItem;
4549
sceneEntry: SkillCatalogSceneEntry;
46-
requestContext: Record<string, unknown>;
50+
requestContext: ServiceSceneLaunchRequestContext;
4751
}
4852

4953
export class RuntimeSceneLaunchValidationError extends Error {
@@ -53,6 +57,20 @@ export class RuntimeSceneLaunchValidationError extends Error {
5357
}
5458
}
5559

60+
function isServiceSkillClawLaunchContext(
61+
value: ServiceSceneLaunchRequestContext,
62+
): value is ServiceSkillClawLaunchContext {
63+
return (
64+
value.kind === "site_adapter" &&
65+
typeof value.skillId === "string" &&
66+
typeof value.skillTitle === "string" &&
67+
typeof value.adapterName === "string" &&
68+
value.args !== null &&
69+
typeof value.args === "object" &&
70+
!Array.isArray(value.args)
71+
);
72+
}
73+
5674
function normalizeCommandToken(value?: string | null): string {
5775
if (typeof value !== "string") {
5876
return "";
@@ -252,7 +270,7 @@ function buildServiceSceneLaunchRequestContext(params: {
252270
skill_title: skill.title,
253271
skill_summary: skill.summary,
254272
runner_type: skill.runnerType,
255-
execution_kind: sceneEntry.executionKind ?? skill.executionKind,
273+
execution_kind: sceneEntry.executionKind ?? skill.defaultExecutorBinding,
256274
execution_location: skill.executionLocation,
257275
project_id: projectId ?? undefined,
258276
content_id: contentId ?? undefined,
@@ -327,12 +345,12 @@ async function resolveSiteSceneLaunchReadiness(
327345

328346
export function buildServiceSceneLaunchRequestMetadata(
329347
existingMetadata: Record<string, unknown> | undefined,
330-
requestContext: Record<string, unknown>,
348+
requestContext: ServiceSceneLaunchRequestContext,
331349
): Record<string, unknown> {
332-
if (requestContext.kind === "site_adapter") {
350+
if (isServiceSkillClawLaunchContext(requestContext)) {
333351
const existingHarness = asRecord(existingMetadata?.harness);
334352
const siteMetadata = buildServiceSkillClawLaunchRequestMetadata(
335-
requestContext as ServiceSkillClawLaunchContext,
353+
requestContext,
336354
);
337355
const siteHarness = asRecord(siteMetadata.harness);
338356

src/components/agent/chat/workspace/useWorkspaceImageTaskPreviewRuntime.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,16 @@ interface RestoredImageTaskSnapshot extends LoadedImageTaskSnapshot {
109109
taskFamily: string;
110110
}
111111

112+
interface ImageTaskPreviewRuntimeContext {
113+
sessionId?: string | null;
114+
projectId?: string | null;
115+
contentId?: string | null;
116+
projectRootPath?: string | null;
117+
messages?: Message[];
118+
currentImageWorkbenchState?: SessionImageWorkbenchState;
119+
canvasState: CanvasStateUnion | null;
120+
}
121+
112122
function shouldPreferLoadedImageTaskSnapshot(
113123
current: LoadedImageTaskSnapshot | null,
114124
candidate: LoadedImageTaskSnapshot | null,
@@ -1592,12 +1602,13 @@ export function useWorkspaceImageTaskPreviewRuntime({
15921602
const restoreSeedMessagesRef = useRef<
15931603
((seedMessages?: Message[]) => void) | null
15941604
>(null);
1595-
const runtimeContextRef = useRef({
1605+
const runtimeContextRef = useRef<ImageTaskPreviewRuntimeContext>({
15961606
sessionId,
15971607
projectId,
15981608
contentId,
15991609
projectRootPath,
16001610
messages,
1611+
currentImageWorkbenchState,
16011612
canvasState,
16021613
});
16031614

@@ -1701,7 +1712,7 @@ export function useWorkspaceImageTaskPreviewRuntime({
17011712
artifactPath?: string;
17021713
}) => {
17031714
const existing = trackedTasks.get(params.taskId);
1704-
if (existing?.timerId !== null) {
1715+
if (existing && existing.timerId !== null) {
17051716
window.clearTimeout(existing.timerId);
17061717
}
17071718
trackedTasks.set(params.taskId, {

src/components/agent/chat/workspace/useWorkspaceSendActions.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1306,6 +1306,10 @@ async function resolveVoiceSkillLaunchRequestContext(params: {
13061306
}
13071307

13081308
const runtime = resolveOemCloudRuntimeContext();
1309+
if (!runtime) {
1310+
toast.error("当前未连接云端运行时,请稍后再试");
1311+
return null;
1312+
}
13091313

13101314
return {
13111315
kind: "cloud_scene",

src/components/agent/chat/workspace/useWorkspaceVideoTaskActionRuntime.test.tsx

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { act } from "react";
33
import { createRoot, type Root } from "react-dom/client";
44
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
55
import { emitVideoWorkbenchTaskAction } from "@/lib/videoWorkbenchEvents";
6-
import type { Message } from "../types";
6+
import type { Message, MessageVideoTaskPreview } from "../types";
77
import { useWorkspaceVideoTaskActionRuntime } from "./useWorkspaceVideoTaskActionRuntime";
88

99
const { mockCancelTask, mockCreateTask, mockGetTask, toast } = vi.hoisted(
@@ -34,41 +34,50 @@ type HookProps = Parameters<typeof useWorkspaceVideoTaskActionRuntime>[0];
3434

3535
const mountedRoots: Array<{ container: HTMLDivElement; root: Root }> = [];
3636

37+
function buildFailedVideoPreview(): MessageVideoTaskPreview {
38+
return {
39+
kind: "video_generate",
40+
taskId: "task-video-old",
41+
taskType: "video_generate",
42+
prompt: "新品发布会短视频",
43+
status: "failed",
44+
durationSeconds: 15,
45+
aspectRatio: "16:9",
46+
resolution: "720p",
47+
projectId: "project-video-1",
48+
contentId: "content-video-1",
49+
providerId: "doubao",
50+
model: "seedance-1-5-pro-251215",
51+
};
52+
}
53+
3754
function buildFailedVideoMessage(): Message {
3855
return {
3956
id: "msg-video-failed-1",
4057
role: "assistant",
4158
content: "视频任务失败。",
4259
timestamp: new Date(),
43-
taskPreview: {
44-
kind: "video_generate",
45-
taskId: "task-video-old",
46-
taskType: "video_generate",
47-
prompt: "新品发布会短视频",
48-
status: "failed",
49-
durationSeconds: 15,
50-
aspectRatio: "16:9",
51-
resolution: "720p",
52-
projectId: "project-video-1",
53-
contentId: "content-video-1",
54-
providerId: "doubao",
55-
model: "seedance-1-5-pro-251215",
56-
},
60+
taskPreview: buildFailedVideoPreview(),
61+
};
62+
}
63+
64+
function buildRunningVideoPreview(): MessageVideoTaskPreview {
65+
return {
66+
...buildFailedVideoPreview(),
67+
taskId: "task-video-running",
68+
status: "running",
69+
progress: 32,
70+
statusMessage: "视频任务正在生成中,工作区会继续同步最新状态。",
5771
};
5872
}
5973

6074
function buildRunningVideoMessage(): Message {
6175
return {
62-
...buildFailedVideoMessage(),
6376
id: "msg-video-running-1",
77+
role: "assistant",
6478
content: "视频任务进行中。",
65-
taskPreview: {
66-
...buildFailedVideoMessage().taskPreview!,
67-
taskId: "task-video-running",
68-
status: "running",
69-
progress: 32,
70-
statusMessage: "视频任务正在生成中,工作区会继续同步最新状态。",
71-
},
79+
timestamp: new Date(),
80+
taskPreview: buildRunningVideoPreview(),
7281
};
7382
}
7483

src/components/agent/chat/workspace/useWorkspaceVideoTaskPreviewRuntime.test.tsx

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React from "react";
22
import { act } from "react";
33
import { createRoot, type Root } from "react-dom/client";
44
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
5-
import type { Message } from "../types";
5+
import type { Message, MessageVideoTaskPreview } from "../types";
66
import { useWorkspaceVideoTaskPreviewRuntime } from "./useWorkspaceVideoTaskPreviewRuntime";
77

88
const { mockGetTask } = vi.hoisted(() => ({
@@ -19,24 +19,30 @@ type HookProps = Parameters<typeof useWorkspaceVideoTaskPreviewRuntime>[0];
1919

2020
const mountedRoots: Array<{ container: HTMLDivElement; root: Root }> = [];
2121

22-
function buildVideoMessage(): Message {
22+
function buildRunningVideoPreview(): MessageVideoTaskPreview {
23+
return {
24+
kind: "video_generate",
25+
taskId: "task-video-1",
26+
taskType: "video_generate",
27+
prompt: "新品发布会短视频",
28+
status: "running",
29+
durationSeconds: 15,
30+
aspectRatio: "16:9",
31+
resolution: "720p",
32+
projectId: "project-video-1",
33+
contentId: "content-video-1",
34+
};
35+
}
36+
37+
function buildVideoMessage(
38+
taskPreview: MessageVideoTaskPreview = buildRunningVideoPreview(),
39+
): Message {
2340
return {
2441
id: "msg-video-1",
2542
role: "assistant",
2643
content: "视频任务已提交",
2744
timestamp: new Date(),
28-
taskPreview: {
29-
kind: "video_generate",
30-
taskId: "task-video-1",
31-
taskType: "video_generate",
32-
prompt: "新品发布会短视频",
33-
status: "running",
34-
durationSeconds: 15,
35-
aspectRatio: "16:9",
36-
resolution: "720p",
37-
projectId: "project-video-1",
38-
contentId: "content-video-1",
39-
},
45+
taskPreview,
4046
};
4147
}
4248

@@ -135,14 +141,11 @@ describe("useWorkspaceVideoTaskPreviewRuntime", () => {
135141
const setChatMessages: HookProps["setChatMessages"] = vi.fn();
136142
const harness = renderHook({
137143
messages: [
138-
{
139-
...buildVideoMessage(),
140-
taskPreview: {
141-
...buildVideoMessage().taskPreview!,
142-
status: "complete",
143-
videoUrl: "https://example.com/already-synced.mp4",
144-
},
145-
},
144+
buildVideoMessage({
145+
...buildRunningVideoPreview(),
146+
status: "complete",
147+
videoUrl: "https://example.com/already-synced.mp4",
148+
}),
146149
],
147150
setChatMessages,
148151
});
@@ -159,14 +162,11 @@ describe("useWorkspaceVideoTaskPreviewRuntime", () => {
159162

160163
it("历史里的视频任务已完成但尚未带回 videoUrl 时,也应继续回填最终结果", async () => {
161164
let messages: Message[] = [
162-
{
163-
...buildVideoMessage(),
164-
taskPreview: {
165-
...buildVideoMessage().taskPreview!,
166-
status: "complete",
167-
statusMessage: "视频已经生成完成,正在同步最终结果。",
168-
},
169-
},
165+
buildVideoMessage({
166+
...buildRunningVideoPreview(),
167+
status: "complete",
168+
statusMessage: "视频已经生成完成,正在同步最终结果。",
169+
}),
170170
];
171171
const setChatMessages: HookProps["setChatMessages"] = (value) => {
172172
messages = typeof value === "function" ? value(messages) : value;

src/hooks/useProjects.test.tsx

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { act } from "react";
22
import { createRoot } from "react-dom/client";
33
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
4+
import type { WorkspaceEnsureResult } from "@/lib/api/project";
45

56
const projectApiMocks = vi.hoisted(() => ({
67
createProject: vi.fn(),
@@ -137,23 +138,21 @@ describe("useProjects", () => {
137138

138139
it("不应等待默认项目目录健康检查完成才暴露项目列表", async () => {
139140
const defaultProject = createProject();
140-
let resolveEnsure:
141-
| ((value: {
142-
workspaceId: string;
143-
rootPath: string;
144-
existed: boolean;
145-
created: boolean;
146-
repaired: boolean;
147-
}) => void)
148-
| null = null;
141+
const deferredEnsure: {
142+
resolve: ((value: WorkspaceEnsureResult) => void) | null;
143+
} = {
144+
resolve: null,
145+
};
146+
const ensureReadyPromise = new Promise<WorkspaceEnsureResult>(
147+
(resolve) => {
148+
deferredEnsure.resolve = resolve;
149+
},
150+
);
149151

150152
projectApiMocks.listProjects.mockResolvedValueOnce([defaultProject]);
151153
projectApiMocks.getDefaultProject.mockResolvedValueOnce(defaultProject);
152154
projectApiMocks.ensureWorkspaceReady.mockImplementationOnce(
153-
() =>
154-
new Promise((resolve) => {
155-
resolveEnsure = resolve;
156-
}),
155+
() => ensureReadyPromise,
157156
);
158157

159158
const harness = mountHook();
@@ -168,7 +167,7 @@ describe("useProjects", () => {
168167
expect(harness.getValue().projects).toHaveLength(1);
169168
expect(harness.getValue().defaultProject?.id).toBe(defaultProject.id);
170169

171-
resolveEnsure?.({
170+
deferredEnsure.resolve?.({
172171
workspaceId: defaultProject.id,
173172
rootPath: defaultProject.rootPath,
174173
existed: true,

0 commit comments

Comments
 (0)