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:
2026-06-22 21:44:07 +08:00
parent 1e3a68a459
commit dfd567fe2c
14 changed files with 307 additions and 111 deletions
@@ -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,