mirror of
https://github.com/aiclientproxy/proxycast.git
synced 2026-09-24 23:10:56 +08:00
fix: stabilize v1.5.1 workspace build
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
||||
EMPTY_STATE_BADGE_TONE_CLASSNAMES,
|
||||
EMPTY_STATE_CARD_SURFACE_CLASSNAME,
|
||||
EMPTY_STATE_ICON_TONE_CLASSNAMES,
|
||||
type EmptyStateTone,
|
||||
} from "./emptyStateSurfaceTokens";
|
||||
|
||||
const heroReveal = keyframes`
|
||||
@@ -314,7 +315,7 @@ const CardEyebrow = styled.span`
|
||||
export interface EmptyStateHeroBadge {
|
||||
key: string;
|
||||
label: string;
|
||||
tone?: "slate" | "sky" | "emerald" | "amber";
|
||||
tone?: EmptyStateTone;
|
||||
}
|
||||
|
||||
export interface EmptyStateHeroCard {
|
||||
@@ -326,7 +327,7 @@ export interface EmptyStateHeroCard {
|
||||
icon: ReactNode;
|
||||
imageSrc?: string;
|
||||
imageAlt?: string;
|
||||
tone?: "slate" | "sky" | "emerald" | "amber";
|
||||
tone?: EmptyStateTone;
|
||||
action?: ReactNode;
|
||||
onMediaAction?: () => void;
|
||||
mediaActionLabel?: string;
|
||||
|
||||
@@ -40,7 +40,10 @@ function createPreparedSend(
|
||||
};
|
||||
}
|
||||
|
||||
function createEnv() {
|
||||
type SlashSkillPreflightTestEnv =
|
||||
Parameters<typeof maybeHandleSlashSkillBeforeSend>[0]["env"];
|
||||
|
||||
function createEnv(): SlashSkillPreflightTestEnv {
|
||||
return {
|
||||
ensureSession: async () => "session-1",
|
||||
sessionIdRef: { current: null },
|
||||
@@ -53,7 +56,7 @@ function createEnv() {
|
||||
playTypewriterSound: vi.fn(),
|
||||
playToolcallSound: vi.fn(),
|
||||
onWriteFile: vi.fn(),
|
||||
} as never;
|
||||
};
|
||||
}
|
||||
|
||||
describe("agentStreamSlashSkillPreflight", () => {
|
||||
|
||||
@@ -40,10 +40,14 @@ interface ParsedRuntimeSceneCommand {
|
||||
userInput: string;
|
||||
}
|
||||
|
||||
type ServiceSceneLaunchRequestContext =
|
||||
| Record<string, unknown>
|
||||
| ServiceSkillClawLaunchContext;
|
||||
|
||||
interface ServiceSceneLaunchRequest {
|
||||
skill: ServiceSkillItem;
|
||||
sceneEntry: SkillCatalogSceneEntry;
|
||||
requestContext: Record<string, unknown>;
|
||||
requestContext: ServiceSceneLaunchRequestContext;
|
||||
}
|
||||
|
||||
export class RuntimeSceneLaunchValidationError extends Error {
|
||||
@@ -53,6 +57,20 @@ export class RuntimeSceneLaunchValidationError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
function isServiceSkillClawLaunchContext(
|
||||
value: ServiceSceneLaunchRequestContext,
|
||||
): value is ServiceSkillClawLaunchContext {
|
||||
return (
|
||||
value.kind === "site_adapter" &&
|
||||
typeof value.skillId === "string" &&
|
||||
typeof value.skillTitle === "string" &&
|
||||
typeof value.adapterName === "string" &&
|
||||
value.args !== null &&
|
||||
typeof value.args === "object" &&
|
||||
!Array.isArray(value.args)
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeCommandToken(value?: string | null): string {
|
||||
if (typeof value !== "string") {
|
||||
return "";
|
||||
@@ -252,7 +270,7 @@ function buildServiceSceneLaunchRequestContext(params: {
|
||||
skill_title: skill.title,
|
||||
skill_summary: skill.summary,
|
||||
runner_type: skill.runnerType,
|
||||
execution_kind: sceneEntry.executionKind ?? skill.executionKind,
|
||||
execution_kind: sceneEntry.executionKind ?? skill.defaultExecutorBinding,
|
||||
execution_location: skill.executionLocation,
|
||||
project_id: projectId ?? undefined,
|
||||
content_id: contentId ?? undefined,
|
||||
@@ -327,12 +345,12 @@ async function resolveSiteSceneLaunchReadiness(
|
||||
|
||||
export function buildServiceSceneLaunchRequestMetadata(
|
||||
existingMetadata: Record<string, unknown> | undefined,
|
||||
requestContext: Record<string, unknown>,
|
||||
requestContext: ServiceSceneLaunchRequestContext,
|
||||
): Record<string, unknown> {
|
||||
if (requestContext.kind === "site_adapter") {
|
||||
if (isServiceSkillClawLaunchContext(requestContext)) {
|
||||
const existingHarness = asRecord(existingMetadata?.harness);
|
||||
const siteMetadata = buildServiceSkillClawLaunchRequestMetadata(
|
||||
requestContext as ServiceSkillClawLaunchContext,
|
||||
requestContext,
|
||||
);
|
||||
const siteHarness = asRecord(siteMetadata.harness);
|
||||
|
||||
|
||||
@@ -109,6 +109,16 @@ interface RestoredImageTaskSnapshot extends LoadedImageTaskSnapshot {
|
||||
taskFamily: string;
|
||||
}
|
||||
|
||||
interface ImageTaskPreviewRuntimeContext {
|
||||
sessionId?: string | null;
|
||||
projectId?: string | null;
|
||||
contentId?: string | null;
|
||||
projectRootPath?: string | null;
|
||||
messages?: Message[];
|
||||
currentImageWorkbenchState?: SessionImageWorkbenchState;
|
||||
canvasState: CanvasStateUnion | null;
|
||||
}
|
||||
|
||||
function shouldPreferLoadedImageTaskSnapshot(
|
||||
current: LoadedImageTaskSnapshot | null,
|
||||
candidate: LoadedImageTaskSnapshot | null,
|
||||
@@ -1592,12 +1602,13 @@ export function useWorkspaceImageTaskPreviewRuntime({
|
||||
const restoreSeedMessagesRef = useRef<
|
||||
((seedMessages?: Message[]) => void) | null
|
||||
>(null);
|
||||
const runtimeContextRef = useRef({
|
||||
const runtimeContextRef = useRef<ImageTaskPreviewRuntimeContext>({
|
||||
sessionId,
|
||||
projectId,
|
||||
contentId,
|
||||
projectRootPath,
|
||||
messages,
|
||||
currentImageWorkbenchState,
|
||||
canvasState,
|
||||
});
|
||||
|
||||
@@ -1701,7 +1712,7 @@ export function useWorkspaceImageTaskPreviewRuntime({
|
||||
artifactPath?: string;
|
||||
}) => {
|
||||
const existing = trackedTasks.get(params.taskId);
|
||||
if (existing?.timerId !== null) {
|
||||
if (existing && existing.timerId !== null) {
|
||||
window.clearTimeout(existing.timerId);
|
||||
}
|
||||
trackedTasks.set(params.taskId, {
|
||||
|
||||
@@ -1306,6 +1306,10 @@ async function resolveVoiceSkillLaunchRequestContext(params: {
|
||||
}
|
||||
|
||||
const runtime = resolveOemCloudRuntimeContext();
|
||||
if (!runtime) {
|
||||
toast.error("当前未连接云端运行时,请稍后再试");
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
kind: "cloud_scene",
|
||||
|
||||
@@ -3,7 +3,7 @@ import { act } from "react";
|
||||
import { createRoot, type Root } from "react-dom/client";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { emitVideoWorkbenchTaskAction } from "@/lib/videoWorkbenchEvents";
|
||||
import type { Message } from "../types";
|
||||
import type { Message, MessageVideoTaskPreview } from "../types";
|
||||
import { useWorkspaceVideoTaskActionRuntime } from "./useWorkspaceVideoTaskActionRuntime";
|
||||
|
||||
const { mockCancelTask, mockCreateTask, mockGetTask, toast } = vi.hoisted(
|
||||
@@ -34,41 +34,50 @@ type HookProps = Parameters<typeof useWorkspaceVideoTaskActionRuntime>[0];
|
||||
|
||||
const mountedRoots: Array<{ container: HTMLDivElement; root: Root }> = [];
|
||||
|
||||
function buildFailedVideoPreview(): MessageVideoTaskPreview {
|
||||
return {
|
||||
kind: "video_generate",
|
||||
taskId: "task-video-old",
|
||||
taskType: "video_generate",
|
||||
prompt: "新品发布会短视频",
|
||||
status: "failed",
|
||||
durationSeconds: 15,
|
||||
aspectRatio: "16:9",
|
||||
resolution: "720p",
|
||||
projectId: "project-video-1",
|
||||
contentId: "content-video-1",
|
||||
providerId: "doubao",
|
||||
model: "seedance-1-5-pro-251215",
|
||||
};
|
||||
}
|
||||
|
||||
function buildFailedVideoMessage(): Message {
|
||||
return {
|
||||
id: "msg-video-failed-1",
|
||||
role: "assistant",
|
||||
content: "视频任务失败。",
|
||||
timestamp: new Date(),
|
||||
taskPreview: {
|
||||
kind: "video_generate",
|
||||
taskId: "task-video-old",
|
||||
taskType: "video_generate",
|
||||
prompt: "新品发布会短视频",
|
||||
status: "failed",
|
||||
durationSeconds: 15,
|
||||
aspectRatio: "16:9",
|
||||
resolution: "720p",
|
||||
projectId: "project-video-1",
|
||||
contentId: "content-video-1",
|
||||
providerId: "doubao",
|
||||
model: "seedance-1-5-pro-251215",
|
||||
},
|
||||
taskPreview: buildFailedVideoPreview(),
|
||||
};
|
||||
}
|
||||
|
||||
function buildRunningVideoPreview(): MessageVideoTaskPreview {
|
||||
return {
|
||||
...buildFailedVideoPreview(),
|
||||
taskId: "task-video-running",
|
||||
status: "running",
|
||||
progress: 32,
|
||||
statusMessage: "视频任务正在生成中,工作区会继续同步最新状态。",
|
||||
};
|
||||
}
|
||||
|
||||
function buildRunningVideoMessage(): Message {
|
||||
return {
|
||||
...buildFailedVideoMessage(),
|
||||
id: "msg-video-running-1",
|
||||
role: "assistant",
|
||||
content: "视频任务进行中。",
|
||||
taskPreview: {
|
||||
...buildFailedVideoMessage().taskPreview!,
|
||||
taskId: "task-video-running",
|
||||
status: "running",
|
||||
progress: 32,
|
||||
statusMessage: "视频任务正在生成中,工作区会继续同步最新状态。",
|
||||
},
|
||||
timestamp: new Date(),
|
||||
taskPreview: buildRunningVideoPreview(),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from "react";
|
||||
import { act } from "react";
|
||||
import { createRoot, type Root } from "react-dom/client";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { Message } from "../types";
|
||||
import type { Message, MessageVideoTaskPreview } from "../types";
|
||||
import { useWorkspaceVideoTaskPreviewRuntime } from "./useWorkspaceVideoTaskPreviewRuntime";
|
||||
|
||||
const { mockGetTask } = vi.hoisted(() => ({
|
||||
@@ -19,24 +19,30 @@ type HookProps = Parameters<typeof useWorkspaceVideoTaskPreviewRuntime>[0];
|
||||
|
||||
const mountedRoots: Array<{ container: HTMLDivElement; root: Root }> = [];
|
||||
|
||||
function buildVideoMessage(): Message {
|
||||
function buildRunningVideoPreview(): MessageVideoTaskPreview {
|
||||
return {
|
||||
kind: "video_generate",
|
||||
taskId: "task-video-1",
|
||||
taskType: "video_generate",
|
||||
prompt: "新品发布会短视频",
|
||||
status: "running",
|
||||
durationSeconds: 15,
|
||||
aspectRatio: "16:9",
|
||||
resolution: "720p",
|
||||
projectId: "project-video-1",
|
||||
contentId: "content-video-1",
|
||||
};
|
||||
}
|
||||
|
||||
function buildVideoMessage(
|
||||
taskPreview: MessageVideoTaskPreview = buildRunningVideoPreview(),
|
||||
): Message {
|
||||
return {
|
||||
id: "msg-video-1",
|
||||
role: "assistant",
|
||||
content: "视频任务已提交",
|
||||
timestamp: new Date(),
|
||||
taskPreview: {
|
||||
kind: "video_generate",
|
||||
taskId: "task-video-1",
|
||||
taskType: "video_generate",
|
||||
prompt: "新品发布会短视频",
|
||||
status: "running",
|
||||
durationSeconds: 15,
|
||||
aspectRatio: "16:9",
|
||||
resolution: "720p",
|
||||
projectId: "project-video-1",
|
||||
contentId: "content-video-1",
|
||||
},
|
||||
taskPreview,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -135,14 +141,11 @@ describe("useWorkspaceVideoTaskPreviewRuntime", () => {
|
||||
const setChatMessages: HookProps["setChatMessages"] = vi.fn();
|
||||
const harness = renderHook({
|
||||
messages: [
|
||||
{
|
||||
...buildVideoMessage(),
|
||||
taskPreview: {
|
||||
...buildVideoMessage().taskPreview!,
|
||||
status: "complete",
|
||||
videoUrl: "https://example.com/already-synced.mp4",
|
||||
},
|
||||
},
|
||||
buildVideoMessage({
|
||||
...buildRunningVideoPreview(),
|
||||
status: "complete",
|
||||
videoUrl: "https://example.com/already-synced.mp4",
|
||||
}),
|
||||
],
|
||||
setChatMessages,
|
||||
});
|
||||
@@ -159,14 +162,11 @@ describe("useWorkspaceVideoTaskPreviewRuntime", () => {
|
||||
|
||||
it("历史里的视频任务已完成但尚未带回 videoUrl 时,也应继续回填最终结果", async () => {
|
||||
let messages: Message[] = [
|
||||
{
|
||||
...buildVideoMessage(),
|
||||
taskPreview: {
|
||||
...buildVideoMessage().taskPreview!,
|
||||
status: "complete",
|
||||
statusMessage: "视频已经生成完成,正在同步最终结果。",
|
||||
},
|
||||
},
|
||||
buildVideoMessage({
|
||||
...buildRunningVideoPreview(),
|
||||
status: "complete",
|
||||
statusMessage: "视频已经生成完成,正在同步最终结果。",
|
||||
}),
|
||||
];
|
||||
const setChatMessages: HookProps["setChatMessages"] = (value) => {
|
||||
messages = typeof value === "function" ? value(messages) : value;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { act } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { WorkspaceEnsureResult } from "@/lib/api/project";
|
||||
|
||||
const projectApiMocks = vi.hoisted(() => ({
|
||||
createProject: vi.fn(),
|
||||
@@ -137,23 +138,21 @@ describe("useProjects", () => {
|
||||
|
||||
it("不应等待默认项目目录健康检查完成才暴露项目列表", async () => {
|
||||
const defaultProject = createProject();
|
||||
let resolveEnsure:
|
||||
| ((value: {
|
||||
workspaceId: string;
|
||||
rootPath: string;
|
||||
existed: boolean;
|
||||
created: boolean;
|
||||
repaired: boolean;
|
||||
}) => void)
|
||||
| null = null;
|
||||
const deferredEnsure: {
|
||||
resolve: ((value: WorkspaceEnsureResult) => void) | null;
|
||||
} = {
|
||||
resolve: null,
|
||||
};
|
||||
const ensureReadyPromise = new Promise<WorkspaceEnsureResult>(
|
||||
(resolve) => {
|
||||
deferredEnsure.resolve = resolve;
|
||||
},
|
||||
);
|
||||
|
||||
projectApiMocks.listProjects.mockResolvedValueOnce([defaultProject]);
|
||||
projectApiMocks.getDefaultProject.mockResolvedValueOnce(defaultProject);
|
||||
projectApiMocks.ensureWorkspaceReady.mockImplementationOnce(
|
||||
() =>
|
||||
new Promise((resolve) => {
|
||||
resolveEnsure = resolve;
|
||||
}),
|
||||
() => ensureReadyPromise,
|
||||
);
|
||||
|
||||
const harness = mountHook();
|
||||
@@ -168,7 +167,7 @@ describe("useProjects", () => {
|
||||
expect(harness.getValue().projects).toHaveLength(1);
|
||||
expect(harness.getValue().defaultProject?.id).toBe(defaultProject.id);
|
||||
|
||||
resolveEnsure?.({
|
||||
deferredEnsure.resolve?.({
|
||||
workspaceId: defaultProject.id,
|
||||
rootPath: defaultProject.rootPath,
|
||||
existed: true,
|
||||
|
||||
Reference in New Issue
Block a user