refactor: extract canvas detail slot helpers
This commit is contained in:
parent
6a19c43094
commit
9821832364
@ -163,6 +163,11 @@ import {
|
||||
replaceSavedDetailArchive,
|
||||
sanitizeDetailArchiveList,
|
||||
} from "@/components/canvas/chat-panel/canvas-detail-archive";
|
||||
import {
|
||||
applyDetailSlotAttachment,
|
||||
fillDetailSlotAttachments,
|
||||
getDetailSlotTargetIndexes,
|
||||
} from "@/components/canvas/chat-panel/canvas-detail-slots";
|
||||
import {
|
||||
clampChatRequestText,
|
||||
deriveSessionTitleFromMessages,
|
||||
@ -4954,11 +4959,7 @@ export function CanvasChatPanel({
|
||||
|
||||
const updateDetailSlotAttachment = useCallback(
|
||||
(slotIndex: number, attachment: PickedObjectAttachment | null) => {
|
||||
setDetailSlots((current) => {
|
||||
const next = [...current];
|
||||
next[slotIndex] = attachment;
|
||||
return next;
|
||||
});
|
||||
setDetailSlots((current) => applyDetailSlotAttachment(current, slotIndex, attachment));
|
||||
},
|
||||
[]
|
||||
);
|
||||
@ -4968,13 +4969,7 @@ export function CanvasChatPanel({
|
||||
if (slotIndex < 0 || slotIndex >= DETAIL_SLOT_CONFIGS.length) return 0;
|
||||
if (selectedImageNodes.length === 0) return 0;
|
||||
|
||||
const slotRole = DETAIL_SLOT_CONFIGS[slotIndex]?.role;
|
||||
const sameRoleIndexes = DETAIL_SLOT_CONFIGS.map((slot, index) =>
|
||||
slot.role === slotRole ? index : -1
|
||||
).filter((index) => index >= 0);
|
||||
const startOffset = sameRoleIndexes.indexOf(slotIndex);
|
||||
const targetIndexes =
|
||||
startOffset >= 0 ? sameRoleIndexes.slice(startOffset) : sameRoleIndexes;
|
||||
const targetIndexes = getDetailSlotTargetIndexes(slotIndex);
|
||||
if (targetIndexes.length === 0) return 0;
|
||||
|
||||
const candidateAttachments = selectedImageNodes
|
||||
@ -4990,13 +4985,9 @@ export function CanvasChatPanel({
|
||||
|
||||
let filledCount = 0;
|
||||
setDetailSlots((current) => {
|
||||
const next = [...current];
|
||||
for (let i = 0; i < targetIndexes.length && i < candidateAttachments.length; i += 1) {
|
||||
const index = targetIndexes[i]!;
|
||||
next[index] = candidateAttachments[i]!;
|
||||
filledCount += 1;
|
||||
}
|
||||
return next;
|
||||
const result = fillDetailSlotAttachments(current, targetIndexes, candidateAttachments);
|
||||
filledCount = result.filledCount;
|
||||
return result.slots;
|
||||
});
|
||||
return filledCount;
|
||||
},
|
||||
|
||||
48
components/canvas/chat-panel/canvas-detail-slots.ts
Normal file
48
components/canvas/chat-panel/canvas-detail-slots.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import {
|
||||
type DetailSlotConfig,
|
||||
DETAIL_SLOT_CONFIGS,
|
||||
} from "@/components/canvas/chat-panel/canvas-detail-config";
|
||||
import type { PickedObjectAttachment } from "@/components/canvas/chat-panel/canvas-chat-session";
|
||||
|
||||
export function getDetailSlotTargetIndexes(
|
||||
slotIndex: number,
|
||||
slotConfigs: DetailSlotConfig[] = DETAIL_SLOT_CONFIGS
|
||||
): number[] {
|
||||
if (slotIndex < 0 || slotIndex >= slotConfigs.length) return [];
|
||||
const slotRole = slotConfigs[slotIndex]?.role;
|
||||
const sameRoleIndexes = slotConfigs
|
||||
.map((slot, index) => (slot.role === slotRole ? index : -1))
|
||||
.filter((index) => index >= 0);
|
||||
const startOffset = sameRoleIndexes.indexOf(slotIndex);
|
||||
return startOffset >= 0 ? sameRoleIndexes.slice(startOffset) : sameRoleIndexes;
|
||||
}
|
||||
|
||||
export function applyDetailSlotAttachment(
|
||||
current: Array<PickedObjectAttachment | null>,
|
||||
slotIndex: number,
|
||||
attachment: PickedObjectAttachment | null
|
||||
): Array<PickedObjectAttachment | null> {
|
||||
const next = [...current];
|
||||
if (slotIndex < 0) return next;
|
||||
next[slotIndex] = attachment;
|
||||
return next;
|
||||
}
|
||||
|
||||
export function fillDetailSlotAttachments(
|
||||
current: Array<PickedObjectAttachment | null>,
|
||||
targetIndexes: number[],
|
||||
attachments: PickedObjectAttachment[]
|
||||
): {
|
||||
slots: Array<PickedObjectAttachment | null>;
|
||||
filledCount: number;
|
||||
} {
|
||||
const next = [...current];
|
||||
let filledCount = 0;
|
||||
for (let i = 0; i < targetIndexes.length && i < attachments.length; i += 1) {
|
||||
const index = targetIndexes[i]!;
|
||||
if (index < 0) continue;
|
||||
next[index] = attachments[i]!;
|
||||
filledCount += 1;
|
||||
}
|
||||
return { slots: next, filledCount };
|
||||
}
|
||||
@ -33,6 +33,12 @@ import {
|
||||
replaceSavedDetailArchive,
|
||||
sanitizeDetailArchiveList,
|
||||
} from "@/components/canvas/chat-panel/canvas-detail-archive";
|
||||
import {
|
||||
applyDetailSlotAttachment,
|
||||
fillDetailSlotAttachments,
|
||||
getDetailSlotTargetIndexes,
|
||||
} from "@/components/canvas/chat-panel/canvas-detail-slots";
|
||||
import type { PickedObjectAttachment } from "@/components/canvas/chat-panel/canvas-chat-session";
|
||||
import type { ChatMessage } from "@/lib/types";
|
||||
|
||||
test.describe("canvas chat pure helpers", () => {
|
||||
@ -404,4 +410,38 @@ test.describe("canvas chat pure helpers", () => {
|
||||
}));
|
||||
expect(prependDetailArchive(manyArchives, saved)).toHaveLength(30);
|
||||
});
|
||||
|
||||
test("fills detail slots by current role group without crossing groups", () => {
|
||||
expect(getDetailSlotTargetIndexes(0)).toEqual([0, 1, 2, 3, 4]);
|
||||
expect(getDetailSlotTargetIndexes(3)).toEqual([3, 4]);
|
||||
expect(getDetailSlotTargetIndexes(5)).toEqual([5]);
|
||||
expect(getDetailSlotTargetIndexes(7)).toEqual([7, 8]);
|
||||
expect(getDetailSlotTargetIndexes(-1)).toEqual([]);
|
||||
expect(getDetailSlotTargetIndexes(99)).toEqual([]);
|
||||
|
||||
const current = Array.from({ length: DETAIL_SLOT_CONFIGS.length }, () => null);
|
||||
const productA: PickedObjectAttachment = {
|
||||
name: "product-a",
|
||||
url: "https://cdn.example.com/product-a.png",
|
||||
contentType: "image/png",
|
||||
};
|
||||
const productB: PickedObjectAttachment = {
|
||||
name: "product-b",
|
||||
url: "https://cdn.example.com/product-b.png",
|
||||
contentType: "image/png",
|
||||
};
|
||||
|
||||
const updatedSingle = applyDetailSlotAttachment(current, 5, productA);
|
||||
expect(updatedSingle[5]).toBe(productA);
|
||||
expect(current[5]).toBeNull();
|
||||
|
||||
const filled = fillDetailSlotAttachments(current, getDetailSlotTargetIndexes(3), [
|
||||
productA,
|
||||
productB,
|
||||
]);
|
||||
expect(filled.filledCount).toBe(2);
|
||||
expect(filled.slots[3]).toBe(productA);
|
||||
expect(filled.slots[4]).toBe(productB);
|
||||
expect(filled.slots[5]).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user