feat(cli): références d'attachments [file n]/[image n] dans le transcript + zone chat redimensionnable — #188 (QA verte)

- Attachments: projection canonique ChatPromptAttachmentDto (référence [file n]/[image n] par compteur de type, ordre d'import) insérée dans le prompt modèle et transportée par ReplyChunk::UserPrompt (attachmentRefs) ; rendu compact côté CustomAgentChatView.
- Resize: la zone chat (LeafView/LayoutGrid) est redimensionnable, taille actuelle conservée par défaut, préférence locale persistée.
- Tests: commands (projection des refs), dto_chat, chat_bridge, CustomAgentChatView, LayoutGrid.chat.
This commit is contained in:
2026-08-13 13:01:03 +02:00
parent 8b8468dacd
commit 4f38e1c506
10 changed files with 513 additions and 42 deletions

View File

@ -78,7 +78,7 @@ describe("CustomAgentChatView", () => {
__resetProjectCliInteractionActivityForTests();
});
it("repaints attachment labels from user prompt scrollback", async () => {
it("repaints compact attachment refs from legacy user prompt scrollback", async () => {
const agent = {
launchAgentChat: vi.fn(),
reattachAgentChat: vi.fn(async (sessionId: string) => ({
@ -119,7 +119,68 @@ describe("CustomAgentChatView", () => {
);
expect(await screen.findByText("describe this")).toBeTruthy();
expect(screen.getByText("Fichier: clipboard.png")).toBeTruthy();
expect(screen.getByText("[image 1] clipboard.png")).toBeTruthy();
});
it("prefers canonical attachment refs from user prompt scrollback", async () => {
const agent = {
launchAgentChat: vi.fn(),
reattachAgentChat: vi.fn(async (sessionId: string) => ({
sessionId,
scrollback: [
{
kind: "userPrompt" as const,
text: "compare these",
attachments: ["screenshot.png", "error.log"],
attachmentRefs: [
{
reference: "[image 1]",
filename: "screenshot.png",
mime: "image/png",
sizeBytes: 12,
readablePath: "/repo/.ideai/attachments/screenshot.png",
},
{
reference: "[file 1]",
filename: "error.log",
mime: "text/plain",
sizeBytes: 34,
readablePath: "/repo/.ideai/attachments/error.log",
},
],
},
],
})),
sendAgentChat: vi.fn(async () => {}),
cancelAgentChat: vi.fn(async () => {}),
closeAgentChat: vi.fn(async () => {}),
};
render(
<DIProvider
gateways={{
agent,
system: { pickFile: vi.fn(async () => null) },
} as unknown as Gateways}
>
<CustomAgentChatView
projectId="project-1"
agentId="agent-1"
agentName="Worker"
profile={profile}
cwd="/repo"
nodeId="node-1"
sessionId="chat-session-1"
conversationId="conversation-1"
onSessionId={vi.fn()}
onConversationId={vi.fn()}
/>
</DIProvider>,
);
expect(await screen.findByText("compare these")).toBeTruthy();
expect(screen.getByText("[image 1] screenshot.png")).toBeTruthy();
expect(screen.getByText("[file 1] error.log")).toBeTruthy();
});
it("detaches on unmount then reattaches with missed scrollback chunks", async () => {
@ -2342,7 +2403,7 @@ describe("CustomAgentChatView", () => {
},
);
expect(screen.getByText("Pièce jointe")).toBeTruthy();
expect(screen.getByText("Fichier: paste-image.png")).toBeTruthy();
expect(screen.getByText("[image 1] paste-image.png")).toBeTruthy();
});
it("accepts pasted generic files exposed only through clipboardData.files", async () => {
@ -2503,7 +2564,7 @@ describe("CustomAgentChatView", () => {
},
);
expect(screen.getByText("prefix inserted")).toBeTruthy();
expect(screen.getByText("Fichier: mixed.txt")).toBeTruthy();
expect(screen.getByText("[file 1] mixed.txt")).toBeTruthy();
});
it("deduplicates clipboard files by metadata across clipboard sources and repeated paste", async () => {