fix(create): 参考内容 - 支持添加、独立删除与筛选管理
- 支持参考内容按稳定 ID 删除、筛选、清空、拖拽与触屏交互 - 保留 Vite 生产环境模型 Logo 加载并兼容 Bun 测试 - 同步最新 main,修正相关格式和陈旧测试断言 Co-authored-by: echoD886 <dingxiaojie66@gmail.com>
This commit is contained in:
@@ -47,10 +47,11 @@ describe("canvas resource mention editor", () => {
|
||||
const project = source("../src/pages/canvas/project.tsx");
|
||||
|
||||
expect(editor).toContain("useResolvedCanvasResourceReferences");
|
||||
expect(panel).toContain("<LoaderCircle className=\"size-3 animate-spin");
|
||||
expect(panel).toContain("<LoaderCircle className=");
|
||||
expect(panel).toContain("animate-spin motion-reduce:animate-none");
|
||||
expect(panel).not.toContain("isRunning ? theme.accent.danger");
|
||||
expect(configComposer).toContain("wrapper.dataset.referenceToken");
|
||||
expect(configComposer).not.toContain('result += `@[node:');
|
||||
expect(configComposer).not.toContain("result += `@[node:");
|
||||
expect(project).not.toContain("removeCanvasResourceMention");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,6 +2,8 @@ import { describe, expect, test } from "bun:test";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
|
||||
import { removeCreationAttachment } from "../src/pages/create/creation-assets";
|
||||
|
||||
function compactSource(source: string) {
|
||||
return source.replace(/\s+/g, " ").trim();
|
||||
}
|
||||
@@ -61,19 +63,26 @@ describe("creation library button", () => {
|
||||
expect(source).toContain('import { Reorder } from "motion/react"');
|
||||
expect(source).toContain("<Reorder.Group");
|
||||
expect(source).toContain('axis="x"');
|
||||
expect(source).toContain("values={props.attachments}");
|
||||
expect(source).toContain("onReorder={props.onReorderAttachments}");
|
||||
expect(source).toContain('className="creation-reference-card-remove" onPointerDown={(event) => event.stopPropagation()}');
|
||||
expect(source).toContain("values={visibleAttachments}");
|
||||
expect(source).toContain("onReorder={reorderVisibleAttachments}");
|
||||
expect(source).toContain('className="creation-reference-card-remove" onPointerDownCapture={(event) => event.stopPropagation()}');
|
||||
expect(source).toContain("<Reorder.Item");
|
||||
expect(source).toContain('layout="position"');
|
||||
expect(source).toContain("isExpanded");
|
||||
expect(source).toContain("handleTrackMouseEnter");
|
||||
expect(source).toContain("handleTrackMouseLeave");
|
||||
expect(source).toContain("handleTrackFocus");
|
||||
expect(source).toContain("handleTrackBlur");
|
||||
expect(source).toContain("setReferencePanelExpanded");
|
||||
expect(source).toContain("aria-label={`查看全部 ${props.attachments.length} 个参考内容`}");
|
||||
expect(source).toContain('aria-label="收起素材面板"');
|
||||
expect(source).toContain("清空全部素材");
|
||||
expect(source).toContain('role="group"');
|
||||
expect(source).toContain("aria-pressed={referenceFilter === filter.id}");
|
||||
expect(source).toContain('{ id: "file", label: "文件", count: referenceCounts.file }');
|
||||
expect(source).toContain("canDragReferences");
|
||||
expect(source).toContain("creation-reference-track-wrapper");
|
||||
expect(source).toContain("creation-reference-stack-card");
|
||||
expect(source).toContain("creation-reference-add-button");
|
||||
expect(source).toContain("addReferenceLabel");
|
||||
expect(source).toContain("aria-busy={interactionBusy}");
|
||||
expect(source).toContain("disabled={interactionBusy || !canAddMoreReferences}");
|
||||
expect(source).toContain("creation-reference-track-button");
|
||||
expect(source).toContain("imageReferenceAtPoint");
|
||||
expect(source).toContain("setDropTargetReferenceId");
|
||||
@@ -86,6 +95,30 @@ describe("creation library button", () => {
|
||||
expect(styles).toContain(".creation-reference-stack-card");
|
||||
expect(styles).toContain("--stack-rotate: -7deg");
|
||||
expect(styles).toContain(".creation-reference-track.is-expanded");
|
||||
expect(styles).toContain(".creation-reference-stack-card:is(:hover, :focus-within) .creation-reference-card-content");
|
||||
expect(styles).toContain("@media (hover: none)");
|
||||
expect(styles).toContain(".creation-reference-card-remove { opacity: 1; }");
|
||||
expect(styles).not.toContain(".creation-reference-track:not(.is-expanded) .creation-reference-stack-card:nth-child(n+5) { display: block; }");
|
||||
});
|
||||
|
||||
test("首个、中间和末尾参考内容都按稳定 id 独立删除并保留顺序", () => {
|
||||
const attachments = [
|
||||
{ id: "first", name: "首个" },
|
||||
{ id: "middle", name: "中间" },
|
||||
{ id: "last", name: "末尾" },
|
||||
];
|
||||
|
||||
expect(removeCreationAttachment(attachments, "first").map((item) => item.id)).toEqual(["middle", "last"]);
|
||||
expect(removeCreationAttachment(attachments, "middle").map((item) => item.id)).toEqual(["first", "last"]);
|
||||
expect(removeCreationAttachment(attachments, "last").map((item) => item.id)).toEqual(["first", "middle"]);
|
||||
});
|
||||
|
||||
test("删除按钮在指针按下阶段隔离拖拽,附件与素材库入口职责独立", () => {
|
||||
const source = compactSource(readFileSync(resolve(import.meta.dir, "../src/pages/create/index.tsx"), "utf8"));
|
||||
|
||||
expect(source).toContain("onPointerDownCapture={(event) => event.stopPropagation()}");
|
||||
expect(source).toContain("onRemove(item.id)");
|
||||
expect(source).toContain("onClick={() => props.fileInputRef.current?.click()}");
|
||||
expect(source).toContain("onClick={props.onOpenLibrary}");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,6 +10,8 @@ describe("模型 Logo 选择器", () => {
|
||||
|
||||
expect(source).toContain('import.meta.glob("../../node_modules/@lobehub/icons/es/*/components/Mono.js"');
|
||||
expect(source).not.toContain("import.meta.glob?.(");
|
||||
expect(source).toContain('"Bun" in globalThis');
|
||||
expect(source).not.toContain("typeof import.meta.glob");
|
||||
expect(options.length).toBeGreaterThan(300);
|
||||
expect(options.some((item) => item.id === "OpenAI")).toBe(true);
|
||||
expect(options.some((item) => item.id === "AgnesAI")).toBe(true);
|
||||
|
||||
@@ -15,22 +15,24 @@ function systemConfig(input: { capability?: "image" | "video"; logicalModelId?:
|
||||
apiKey: "system",
|
||||
apiFormat: "openai",
|
||||
models: [model],
|
||||
modelCosts: [{
|
||||
model,
|
||||
capability,
|
||||
pricePolicy: "channel",
|
||||
billingMode: input.tiers[0]?.billingMode || "fixed_request",
|
||||
unitPriceMicrocredits: input.tiers[0]?.unitPriceMicrocredits || 0,
|
||||
logicalModelId: input.logicalModelId,
|
||||
logicalPriceTiers: input.tiers.map((tier) => ({
|
||||
...tier,
|
||||
resolution: tier.selector.vquality || "*",
|
||||
videoSeconds: Number(tier.selector.videoSeconds || 0),
|
||||
inputTokenPriceMicrocredits: 0,
|
||||
outputTokenPriceMicrocredits: 0,
|
||||
cachedTokenPriceMicrocredits: 0,
|
||||
})),
|
||||
}],
|
||||
modelCosts: [
|
||||
{
|
||||
model,
|
||||
capability,
|
||||
pricePolicy: "channel",
|
||||
billingMode: input.tiers[0]?.billingMode || "fixed_request",
|
||||
unitPriceMicrocredits: input.tiers[0]?.unitPriceMicrocredits || 0,
|
||||
logicalModelId: input.logicalModelId,
|
||||
logicalPriceTiers: input.tiers.map((tier) => ({
|
||||
...tier,
|
||||
resolution: tier.selector.vquality || "*",
|
||||
videoSeconds: Number(tier.selector.videoSeconds || 0),
|
||||
inputTokenPriceMicrocredits: 0,
|
||||
outputTokenPriceMicrocredits: 0,
|
||||
cachedTokenPriceMicrocredits: 0,
|
||||
})),
|
||||
},
|
||||
],
|
||||
});
|
||||
return normalizeConfigSnapshot({
|
||||
config: {
|
||||
@@ -66,15 +68,17 @@ describe("model request pricing", () => {
|
||||
});
|
||||
const channel = resolveModelChannel(config, config.model);
|
||||
|
||||
expect(requestCreditCost({
|
||||
channelMode: "remote",
|
||||
modelCosts: channel.modelCosts,
|
||||
model: "agnes-video-2.5",
|
||||
capability: "video",
|
||||
config,
|
||||
requirements: textVideoRequirements,
|
||||
seconds: "5",
|
||||
})).toBe(0.125);
|
||||
expect(
|
||||
requestCreditCost({
|
||||
channelMode: "remote",
|
||||
modelCosts: channel.modelCosts,
|
||||
model: "agnes-video-2.5",
|
||||
capability: "video",
|
||||
config,
|
||||
requirements: textVideoRequirements,
|
||||
seconds: "5",
|
||||
}),
|
||||
).toBe(0.125);
|
||||
});
|
||||
|
||||
test("uses reference count and operation to avoid a text-video price tier", () => {
|
||||
@@ -101,15 +105,17 @@ describe("model request pricing", () => {
|
||||
});
|
||||
const channel = resolveModelChannel(config, config.model);
|
||||
|
||||
expect(requestCreditCost({
|
||||
channelMode: "remote",
|
||||
modelCosts: channel.modelCosts,
|
||||
model: "image-model",
|
||||
capability: "image",
|
||||
config,
|
||||
requirements: { capability: "image" },
|
||||
count: 3,
|
||||
})).toBe(0.03);
|
||||
expect(
|
||||
requestCreditCost({
|
||||
channelMode: "remote",
|
||||
modelCosts: channel.modelCosts,
|
||||
model: "image-model",
|
||||
capability: "image",
|
||||
config,
|
||||
requirements: { capability: "image" },
|
||||
count: 3,
|
||||
}),
|
||||
).toBe(0.03);
|
||||
});
|
||||
|
||||
test("builds a logical-model quote using the normalized current request", () => {
|
||||
|
||||
Reference in New Issue
Block a user