Add Agent execution guidelines & trim AGENTS.md
Introduce a new detailed Agent handbook (docs/【协作规范】Agent工作入口与执行准则-2026-06-22.md) and refactor AGENTS.md to be an entry-only navigation page. Update documentation cross-references (docs/README.md, project-memory README, document-map, team-conventions, development-workflow, decision-log and project-overview) to point to the new file and clarify reading/order rules, submission/validation constraints, and SpacetimeDB/back-end wording. Also adjust image canvas generation placement rules in the frontend doc and apply related changes to image-editor hooks/tests (src/components/image-editor/*). Purpose: separate high-level entry rules from complex execution details to reduce startup cost for complex tasks and centralize execution practices in a single guideline document.
This commit is contained in:
@@ -120,6 +120,35 @@ function createIconResult(name: string, imageSrc: string) {
|
||||
};
|
||||
}
|
||||
|
||||
function parseLayerRect(text: string, layerId: string) {
|
||||
const entry = text
|
||||
.split('|')
|
||||
.find((currentEntry) => currentEntry.startsWith(`${layerId}:`));
|
||||
if (!entry) {
|
||||
throw new Error(`missing layer rect for ${layerId}`);
|
||||
}
|
||||
const [, x, y, width, height] = entry.split(':');
|
||||
return {
|
||||
x: Number(x),
|
||||
y: Number(y),
|
||||
width: Number(width),
|
||||
height: Number(height),
|
||||
};
|
||||
}
|
||||
|
||||
function rectOverlapsExpanded(
|
||||
rect: { x: number; y: number; width: number; height: number },
|
||||
blocker: { x: number; y: number; width: number; height: number },
|
||||
gap: number,
|
||||
) {
|
||||
return (
|
||||
rect.x < blocker.x + blocker.width + gap &&
|
||||
rect.x + rect.width > blocker.x - gap &&
|
||||
rect.y < blocker.y + blocker.height + gap &&
|
||||
rect.y + rect.height > blocker.y - gap
|
||||
);
|
||||
}
|
||||
|
||||
function SubmissionWorkflowHarness({
|
||||
initialDialog = null,
|
||||
initialQuickEditPanel = null,
|
||||
@@ -179,6 +208,7 @@ function SubmissionWorkflowHarness({
|
||||
quickEditPanel,
|
||||
quickEditSourceLayer,
|
||||
quickEditSelectionState: initialQuickEditSelectionState,
|
||||
canvasGenerationDialogs: dialogs.canvasGenerationDialogs,
|
||||
setQuickEditPanel,
|
||||
characterAnimationPanel,
|
||||
characterAnimationDialog: activeCanvasDialog,
|
||||
@@ -216,6 +246,14 @@ function SubmissionWorkflowHarness({
|
||||
)
|
||||
.join('|')}
|
||||
</span>
|
||||
<span data-testid="layer-rects">
|
||||
{layers
|
||||
.map(
|
||||
(layer) =>
|
||||
`${layer.id}:${layer.x}:${layer.y}:${layer.width}:${layer.height}`,
|
||||
)
|
||||
.join('|')}
|
||||
</span>
|
||||
<span data-testid="dialog">
|
||||
{activeDialog
|
||||
? `${activeDialog.mode}:${activeDialog.status}:${activeDialog.composerOpen !== false ? 'open' : 'closed'}:${activeDialog.generatedLayerId ?? '-'}:${activeDialog.placeholder ? 'placeholder' : '-'}:${activeDialog.errorMessage ?? '-'}`
|
||||
@@ -410,6 +448,45 @@ describe('useImageCanvasGenerationSubmissionWorkflow', () => {
|
||||
expect(screen.getByTestId('fit-count').textContent).toBe('1');
|
||||
});
|
||||
|
||||
it('places quick edit result with the new image placement logic away from existing assets', async () => {
|
||||
const blockingLayer = createLayer({
|
||||
id: 'layer-blocker',
|
||||
resourceId: 'resource-blocker',
|
||||
title: '右侧已有素材',
|
||||
x: 472,
|
||||
y: 140,
|
||||
width: 1024,
|
||||
height: 768,
|
||||
originalWidth: 1024,
|
||||
originalHeight: 768,
|
||||
zIndex: 3,
|
||||
});
|
||||
generateEditorImageMock.mockResolvedValueOnce(
|
||||
createGenerated({ prompt: '快速修图', width: 1024, height: 768 }),
|
||||
);
|
||||
render(
|
||||
<SubmissionWorkflowHarness
|
||||
initialLayers={[createLayer(), blockingLayer]}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '打开快速编辑' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '填写快速编辑' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '提交快速编辑' }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('layers').textContent).toContain(
|
||||
'layer-quick-edit-1:源图 快速编辑:resource-source:-',
|
||||
);
|
||||
});
|
||||
const layerRects = screen.getByTestId('layer-rects').textContent ?? '';
|
||||
const resultRect = parseLayerRect(layerRects, 'layer-quick-edit-1');
|
||||
|
||||
expect(
|
||||
rectOverlapsExpanded(resultRect, blockingLayer, 32),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('submits quick edits with the numbered red selection image as the final source reference', async () => {
|
||||
generateEditorImageMock.mockResolvedValueOnce(
|
||||
createGenerated({ prompt: '对1号红色圈选框里的内容做以下修改:改成蓝色' }),
|
||||
|
||||
@@ -44,6 +44,7 @@ import {
|
||||
createQuickEditResultLayer,
|
||||
createVideoResultLayer,
|
||||
} from './ImageCanvasGenerationLayerModel';
|
||||
import { chooseGenerationPlacement } from './ImageCanvasGenerationPlacementModel';
|
||||
import {
|
||||
buildCharacterAnimationGenerationInputs,
|
||||
buildEditGenerationInputs,
|
||||
@@ -98,6 +99,7 @@ type GenerationSubmissionWorkflowOptions = {
|
||||
quickEditPanel: QuickEditPanelState | null;
|
||||
quickEditSourceLayer: CanvasLayer | null;
|
||||
quickEditSelectionState: UiAssetExtractionState | null;
|
||||
canvasGenerationDialogs: CanvasGenerationDialogState[];
|
||||
setQuickEditPanel: Dispatch<SetStateAction<QuickEditPanelState | null>>;
|
||||
characterAnimationPanel: CharacterAnimationPanelState | null;
|
||||
characterAnimationDialog: CanvasGenerationDialogState | null;
|
||||
@@ -165,6 +167,7 @@ export function useImageCanvasGenerationSubmissionWorkflow({
|
||||
quickEditPanel,
|
||||
quickEditSourceLayer,
|
||||
quickEditSelectionState,
|
||||
canvasGenerationDialogs,
|
||||
setQuickEditPanel,
|
||||
characterAnimationPanel,
|
||||
characterAnimationDialog,
|
||||
@@ -741,21 +744,33 @@ export function useImageCanvasGenerationSubmissionWorkflow({
|
||||
);
|
||||
let quickEditDialogId: string | undefined;
|
||||
if (panelMode === 'quick-edit') {
|
||||
quickEditDialogId = openCanvasGenerationDialog(
|
||||
createQuickEditGenerationDialogDraft({
|
||||
sourceLayer: quickEditSourceLayer,
|
||||
prompt: normalizedPrompt,
|
||||
status: 'generating',
|
||||
references: quickEditReferences,
|
||||
model: quickEditPanel.model,
|
||||
aspectRatio: quickEditAspectRatio,
|
||||
imageSize: quickEditImageSize,
|
||||
frame: {
|
||||
width: quickEditOutputSize.width,
|
||||
height: quickEditOutputSize.height,
|
||||
},
|
||||
}),
|
||||
);
|
||||
const quickEditDraft = createQuickEditGenerationDialogDraft({
|
||||
sourceLayer: quickEditSourceLayer,
|
||||
prompt: normalizedPrompt,
|
||||
status: 'generating',
|
||||
references: quickEditReferences,
|
||||
model: quickEditPanel.model,
|
||||
aspectRatio: quickEditAspectRatio,
|
||||
imageSize: quickEditImageSize,
|
||||
frame: {
|
||||
width: quickEditOutputSize.width,
|
||||
height: quickEditOutputSize.height,
|
||||
},
|
||||
});
|
||||
const quickEditPlacement = quickEditDraft.placeholder
|
||||
? chooseGenerationPlacement({
|
||||
canvasSize,
|
||||
viewport,
|
||||
frame: quickEditDraft.placeholder,
|
||||
layers,
|
||||
generationDialogs: canvasGenerationDialogs,
|
||||
})
|
||||
: quickEditDraft.placeholder;
|
||||
quickEditDialogId = openCanvasGenerationDialog({
|
||||
...quickEditDraft,
|
||||
// 中文注释:快速编辑生成结果复用新建图片的避让落点,避免覆盖已有素材。
|
||||
placeholder: quickEditPlacement,
|
||||
});
|
||||
setQuickEditPanel(null);
|
||||
selectSingleLayer(null);
|
||||
} else {
|
||||
@@ -956,7 +971,10 @@ export function useImageCanvasGenerationSubmissionWorkflow({
|
||||
addCharacterAnimationResultLayer,
|
||||
addQuickEditResultLayer,
|
||||
addVideoResultLayer,
|
||||
canvasGenerationDialogs,
|
||||
canvasSize,
|
||||
getGeneratingDialogPlaceholder,
|
||||
layers,
|
||||
openCanvasGenerationDialog,
|
||||
projectId,
|
||||
assetFolderId,
|
||||
@@ -966,6 +984,7 @@ export function useImageCanvasGenerationSubmissionWorkflow({
|
||||
selectSingleLayer,
|
||||
setQuickEditPanel,
|
||||
updateCanvasGenerationDialogById,
|
||||
viewport,
|
||||
]);
|
||||
|
||||
const submitImageGeneration = useCallback(
|
||||
|
||||
@@ -1210,6 +1210,7 @@ export function useImageCanvasGenerationWorkflow({
|
||||
quickEditPanel,
|
||||
quickEditSourceLayer,
|
||||
quickEditSelectionState,
|
||||
canvasGenerationDialogs,
|
||||
setQuickEditPanel,
|
||||
characterAnimationPanel: effectiveCharacterAnimationPanel,
|
||||
characterAnimationDialog,
|
||||
|
||||
Reference in New Issue
Block a user