mirror of
https://github.com/aiclientproxy/proxycast.git
synced 2026-09-24 23:10:56 +08:00
fix: unblock v0.93.0 release build
This commit is contained in:
@@ -2,6 +2,8 @@ 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 { ConfiguredProvider } from "@/hooks/useConfiguredProviders";
|
||||
import type { EnhancedModelMetadata } from "@/lib/types/modelRegistry";
|
||||
import { AgentChatHomeShell } from "./AgentChatHomeShell";
|
||||
import { SettingsTabs } from "@/types/settings";
|
||||
|
||||
@@ -67,8 +69,12 @@ const {
|
||||
mockSetProviderType: vi.fn(),
|
||||
mockSetModel: vi.fn(),
|
||||
mockSetExecutionStrategy: vi.fn(),
|
||||
mockLoadConfiguredProviders: vi.fn(async () => []),
|
||||
mockLoadProviderModels: vi.fn(async () => []),
|
||||
mockLoadConfiguredProviders: vi.fn(
|
||||
async (): Promise<ConfiguredProvider[]> => [],
|
||||
),
|
||||
mockLoadProviderModels: vi.fn(
|
||||
async (): Promise<EnhancedModelMetadata[]> => [],
|
||||
),
|
||||
mockFilterModelsByTheme: vi.fn(
|
||||
(_theme: string | undefined, models: unknown[]) => ({
|
||||
models,
|
||||
|
||||
@@ -5398,8 +5398,8 @@ export function AgentChatWorkspace({
|
||||
dismissThemeWorkbenchEntryPrompt,
|
||||
} = useThemeWorkbenchEntryPrompt({
|
||||
activeTheme,
|
||||
contentId,
|
||||
sessionId,
|
||||
contentId: contentId ?? undefined,
|
||||
sessionId: sessionId ?? undefined,
|
||||
isThemeWorkbench,
|
||||
shouldUseCompactThemeWorkbench,
|
||||
messagesCount: messages.length,
|
||||
|
||||
@@ -40,19 +40,23 @@ function renderHook(props?: Partial<HookProps>) {
|
||||
selectedTeam: createSelectedTeam(),
|
||||
subagentEnabled: true,
|
||||
hasRealTeamGraph: false,
|
||||
generateRuntimeTeam: vi.fn(async () => ({
|
||||
id: "ephemeral-1",
|
||||
source: "ephemeral",
|
||||
label: "临时 Team",
|
||||
description: "自动生成",
|
||||
roles: [
|
||||
{
|
||||
id: "member-1",
|
||||
label: "执行者",
|
||||
summary: "执行当前任务",
|
||||
},
|
||||
],
|
||||
})),
|
||||
generateRuntimeTeam: vi.fn(
|
||||
async (
|
||||
_options: Parameters<NonNullable<HookProps["generateRuntimeTeam"]>>[0],
|
||||
): Promise<TeamDefinition> => ({
|
||||
id: "ephemeral-1",
|
||||
source: "ephemeral",
|
||||
label: "临时 Team",
|
||||
description: "自动生成",
|
||||
roles: [
|
||||
{
|
||||
id: "member-1",
|
||||
label: "执行者",
|
||||
summary: "执行当前任务",
|
||||
},
|
||||
],
|
||||
}),
|
||||
),
|
||||
createRequestId: () => "request-1",
|
||||
now: () => 1_710_000_000_000,
|
||||
};
|
||||
|
||||
@@ -140,7 +140,7 @@ describe("useSelectedTeamPreference", () => {
|
||||
agentTeam: {
|
||||
selectedTeam: {
|
||||
id: engineeringTeam.id,
|
||||
source: engineeringTeam.source,
|
||||
source: "builtin",
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -228,7 +228,7 @@ describe("useSelectedTeamPreference", () => {
|
||||
agentTeam: {
|
||||
selectedTeam: {
|
||||
id: engineeringTeam.id,
|
||||
source: engineeringTeam.source,
|
||||
source: "builtin",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { act } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { WorkflowState as ContentWorkflowState } from "@/lib/api/content-workflow";
|
||||
import type { ThemeWorkbenchRunState as BackendThemeWorkbenchRunState } from "@/lib/api/executionRun";
|
||||
import { useThemeWorkbenchEntryPrompt } from "./useThemeWorkbenchEntryPrompt";
|
||||
|
||||
interface HookHarness {
|
||||
@@ -29,8 +31,13 @@ function mountHook(initialProps?: Partial<HookProps>): HookHarness {
|
||||
const root = createRoot(container);
|
||||
const onHydrateInitialPrompt = vi.fn();
|
||||
|
||||
const loadWorkflow = vi.fn(async () => null);
|
||||
const loadRunState = vi.fn(async () => null);
|
||||
const loadWorkflow = vi.fn(
|
||||
async (_contentId: string): Promise<ContentWorkflowState | null> => null,
|
||||
);
|
||||
const loadRunState = vi.fn(
|
||||
async (_sessionId: string): Promise<BackendThemeWorkbenchRunState | null> =>
|
||||
null,
|
||||
);
|
||||
|
||||
let hookValue: ReturnType<typeof useThemeWorkbenchEntryPrompt> | null = null;
|
||||
let currentProps: HookProps = {
|
||||
@@ -135,32 +142,51 @@ describe("useThemeWorkbenchEntryPrompt", () => {
|
||||
document.body.appendChild(container);
|
||||
const root = createRoot(container);
|
||||
const onHydrateInitialPrompt = vi.fn();
|
||||
const loadWorkflow = vi.fn(async () => ({
|
||||
id: "wf-1",
|
||||
content_id: "content-1",
|
||||
theme: "social-media",
|
||||
mode: "guided",
|
||||
current_step_index: 1,
|
||||
created_at: Date.now(),
|
||||
updated_at: Date.now(),
|
||||
steps: [
|
||||
{
|
||||
id: "step-1",
|
||||
title: "撰写主稿",
|
||||
status: "completed",
|
||||
},
|
||||
{
|
||||
id: "step-2",
|
||||
title: "润色结尾",
|
||||
status: "pending",
|
||||
},
|
||||
],
|
||||
}));
|
||||
const loadRunState = vi.fn(async () => null);
|
||||
let hookValue: ReturnType<typeof useThemeWorkbenchEntryPrompt> | null = null;
|
||||
const loadWorkflow = vi.fn(
|
||||
async (_contentId: string): Promise<ContentWorkflowState | null> => ({
|
||||
id: "wf-1",
|
||||
content_id: "content-1",
|
||||
theme: "social-media",
|
||||
mode: "guided",
|
||||
current_step_index: 1,
|
||||
created_at: Date.now(),
|
||||
updated_at: Date.now(),
|
||||
steps: [
|
||||
{
|
||||
id: "step-1",
|
||||
type: "write",
|
||||
title: "撰写主稿",
|
||||
behavior: {
|
||||
skippable: false,
|
||||
redoable: true,
|
||||
auto_advance: false,
|
||||
},
|
||||
status: "completed",
|
||||
},
|
||||
{
|
||||
id: "step-2",
|
||||
type: "polish",
|
||||
title: "润色结尾",
|
||||
behavior: {
|
||||
skippable: false,
|
||||
redoable: true,
|
||||
auto_advance: false,
|
||||
},
|
||||
status: "pending",
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
const loadRunState = vi.fn(
|
||||
async (_sessionId: string): Promise<BackendThemeWorkbenchRunState | null> =>
|
||||
null,
|
||||
);
|
||||
const hookValueRef: {
|
||||
current: ReturnType<typeof useThemeWorkbenchEntryPrompt> | null;
|
||||
} = { current: null };
|
||||
|
||||
function TestComponent() {
|
||||
hookValue = useThemeWorkbenchEntryPrompt({
|
||||
hookValueRef.current = useThemeWorkbenchEntryPrompt({
|
||||
activeTheme: "social-media",
|
||||
contentId: "content-1",
|
||||
sessionId: "session-1",
|
||||
@@ -185,11 +211,11 @@ describe("useThemeWorkbenchEntryPrompt", () => {
|
||||
try {
|
||||
await flushEffects();
|
||||
expect(loadWorkflow).toHaveBeenCalledWith("content-1");
|
||||
expect(hookValue?.themeWorkbenchEntryPrompt).toMatchObject({
|
||||
expect(hookValueRef.current?.themeWorkbenchEntryPrompt).toMatchObject({
|
||||
kind: "resume",
|
||||
title: "发现上次未完成任务",
|
||||
});
|
||||
expect(hookValue?.themeWorkbenchEntryCheckPending).toBe(false);
|
||||
expect(hookValueRef.current?.themeWorkbenchEntryCheckPending).toBe(false);
|
||||
} finally {
|
||||
act(() => {
|
||||
root.unmount();
|
||||
|
||||
@@ -54,7 +54,6 @@ function resolveThemeWorkbenchGateLabel(
|
||||
return "写作推进";
|
||||
case "publish_confirm":
|
||||
return "发布确认";
|
||||
case "idle":
|
||||
case null:
|
||||
case undefined:
|
||||
default:
|
||||
|
||||
@@ -640,6 +640,21 @@ interface MountedHarness {
|
||||
rerender: (props?: Partial<ComponentProps<typeof AgentChatPage>>) => void;
|
||||
}
|
||||
|
||||
type MockInputbarSendProps = {
|
||||
onToolStatesChange?: (
|
||||
next:
|
||||
| Record<string, unknown>
|
||||
| ((prev: Record<string, unknown>) => Record<string, unknown>),
|
||||
) => void;
|
||||
onSend?: (
|
||||
images?: unknown[],
|
||||
webSearch?: boolean,
|
||||
thinking?: boolean,
|
||||
textOverride?: string,
|
||||
executionStrategy?: "react" | "code_orchestrated" | "auto",
|
||||
) => void | Promise<void> | Promise<boolean | void> | boolean;
|
||||
};
|
||||
|
||||
const mountedRoots: MountedHarness[] = [];
|
||||
const observedWorkspaceIds: string[] = [];
|
||||
let sharedSwitchTopicMock: ReturnType<typeof vi.fn>;
|
||||
@@ -1582,13 +1597,7 @@ describe("AgentChatPage 通用工作台", () => {
|
||||
await flushEffects(10);
|
||||
|
||||
let latestInputbarProps = mockInputbar.mock.calls.at(-1)?.[0] as
|
||||
| {
|
||||
onToolStatesChange?: (
|
||||
next:
|
||||
| Record<string, unknown>
|
||||
| ((prev: Record<string, unknown>) => Record<string, unknown>),
|
||||
) => void;
|
||||
}
|
||||
| MockInputbarSendProps
|
||||
| undefined;
|
||||
|
||||
act(() => {
|
||||
@@ -1600,15 +1609,7 @@ describe("AgentChatPage 通用工作台", () => {
|
||||
await flushEffects(8);
|
||||
|
||||
latestInputbarProps = mockInputbar.mock.calls.at(-1)?.[0] as
|
||||
| {
|
||||
onSend?: (
|
||||
images?: unknown[],
|
||||
webSearch?: boolean,
|
||||
thinking?: boolean,
|
||||
textOverride?: string,
|
||||
executionStrategy?: "react" | "code_orchestrated" | "auto",
|
||||
) => Promise<void>;
|
||||
}
|
||||
| MockInputbarSendProps
|
||||
| undefined;
|
||||
|
||||
await act(async () => {
|
||||
@@ -1720,13 +1721,7 @@ describe("AgentChatPage 通用工作台", () => {
|
||||
await flushEffects(10);
|
||||
|
||||
let latestInputbarProps = mockInputbar.mock.calls.at(-1)?.[0] as
|
||||
| {
|
||||
onToolStatesChange?: (
|
||||
next:
|
||||
| Record<string, unknown>
|
||||
| ((prev: Record<string, unknown>) => Record<string, unknown>),
|
||||
) => void;
|
||||
}
|
||||
| MockInputbarSendProps
|
||||
| undefined;
|
||||
|
||||
act(() => {
|
||||
@@ -1738,15 +1733,7 @@ describe("AgentChatPage 通用工作台", () => {
|
||||
await flushEffects(8);
|
||||
|
||||
latestInputbarProps = mockInputbar.mock.calls.at(-1)?.[0] as
|
||||
| {
|
||||
onSend?: (
|
||||
images?: unknown[],
|
||||
webSearch?: boolean,
|
||||
thinking?: boolean,
|
||||
textOverride?: string,
|
||||
executionStrategy?: "react" | "code_orchestrated" | "auto",
|
||||
) => Promise<void>;
|
||||
}
|
||||
| MockInputbarSendProps
|
||||
| undefined;
|
||||
|
||||
await act(async () => {
|
||||
@@ -3156,9 +3143,12 @@ describe("AgentChatPage 自动引导", () => {
|
||||
|
||||
const mounted = mountedRoots.at(-1);
|
||||
expect(mounted).toBeTruthy();
|
||||
if (!mounted) {
|
||||
throw new Error("未找到挂载页面");
|
||||
}
|
||||
|
||||
act(() => {
|
||||
mounted?.root.render(
|
||||
mounted.root.render(
|
||||
<AgentChatPage
|
||||
projectId="project-theme-delayed-intent"
|
||||
contentId="content-theme-delayed-intent"
|
||||
|
||||
@@ -118,7 +118,7 @@ export function sanitizeContentPartsForDisplay(
|
||||
return parts;
|
||||
}
|
||||
|
||||
const sanitizedParts = parts.flatMap((part) => {
|
||||
const sanitizedParts = parts.flatMap<ContentPart>((part) => {
|
||||
if (part.type !== "text") {
|
||||
return [part];
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
createTeamDefinitionId,
|
||||
normalizeTeamDefinition,
|
||||
type TeamDefinition,
|
||||
type TeamRoleDefinition,
|
||||
} from "./teamDefinitions";
|
||||
|
||||
interface GenerateTeamWithModelOptions {
|
||||
@@ -136,13 +137,23 @@ function parseGeneratedTeam(
|
||||
const json = extractJsonObject(raw);
|
||||
const parsed = JSON.parse(json) as GeneratedTeamPayload;
|
||||
const payload = parsed.team && typeof parsed.team === "object" ? parsed.team : parsed;
|
||||
const normalizedRoles: TeamRoleDefinition[] | undefined = payload.roles?.map(
|
||||
(role) => ({
|
||||
id: role.id?.trim() || "",
|
||||
label: role.label?.trim() || "",
|
||||
summary: role.summary?.trim() || "",
|
||||
profileId: role.profileId?.trim() || undefined,
|
||||
roleKey: role.roleKey?.trim() || undefined,
|
||||
skillIds: role.skillIds?.map((skillId) => skillId.trim()).filter(Boolean) || [],
|
||||
}),
|
||||
);
|
||||
const normalized = normalizeTeamDefinition({
|
||||
id: createTeamDefinitionId("ephemeral-team"),
|
||||
source: "ephemeral",
|
||||
label: payload.label,
|
||||
description: payload.description,
|
||||
theme: activeTheme?.trim() || undefined,
|
||||
roles: payload.roles,
|
||||
roles: normalizedRoles,
|
||||
});
|
||||
|
||||
if (!normalized) {
|
||||
@@ -188,7 +199,7 @@ export async function generateEphemeralTeamWithModel(
|
||||
resolvedExecutionStrategy,
|
||||
);
|
||||
const eventName = `agent_team_draft:${sessionId}:${Date.now()}`;
|
||||
let unlisten: (() => void) | null = null;
|
||||
const unlistenRef: { current: (() => void) | null } = { current: null };
|
||||
|
||||
try {
|
||||
const completion = new Promise<TeamDefinition>((resolve, reject) => {
|
||||
@@ -207,7 +218,7 @@ export async function generateEphemeralTeamWithModel(
|
||||
|
||||
void (async () => {
|
||||
try {
|
||||
unlisten = await safeListen(eventName, async (event) => {
|
||||
unlistenRef.current = await safeListen(eventName, async (event) => {
|
||||
const parsed = parseStreamEvent(event.payload);
|
||||
if (!parsed) {
|
||||
return;
|
||||
@@ -292,7 +303,7 @@ export async function generateEphemeralTeamWithModel(
|
||||
return await completion;
|
||||
} finally {
|
||||
try {
|
||||
unlisten?.();
|
||||
unlistenRef.current?.();
|
||||
} catch {
|
||||
// ignore cleanup failure
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user