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

@ -106,8 +106,22 @@ fn progress_chunk_with_id(
fn chunk_bytes(chunk: &ReplyChunk) -> usize {
match chunk {
ReplyChunk::UserPrompt { text, attachments } => {
text.len() + attachments.iter().map(String::len).sum::<usize>()
ReplyChunk::UserPrompt {
text,
attachments,
attachment_refs,
} => {
text.len()
+ attachments.iter().map(String::len).sum::<usize>()
+ attachment_refs
.iter()
.map(|attachment| {
attachment.reference.len()
+ attachment.filename.len()
+ attachment.mime.len()
+ attachment.readable_path.len()
})
.sum::<usize>()
}
ReplyChunk::TextDelta { text } => text.len(),
ReplyChunk::ToolActivity { label } => label.len(),

View File

@ -8,9 +8,9 @@
use app_tauri_lib::dto::{
CellKind, ChatAttachmentDto, ChatAttachmentInputDto, ChatDraftScopeRequestDto,
ImportChatAttachmentsRequestDto, ImportChatAttachmentsResponseDto, ReadChatDraftResponseDto,
ReattachChatDto, ReplyChunk, ReplyProgressDto, ReplyProgressKindDto, ReplyProgressSourceDto,
ReplyProgressStageDto, SaveChatDraftRequestDto, TerminalSessionDto,
ChatPromptAttachmentDto, ImportChatAttachmentsRequestDto, ImportChatAttachmentsResponseDto,
ReadChatDraftResponseDto, ReattachChatDto, ReplyChunk, ReplyProgressDto, ReplyProgressKindDto,
ReplyProgressSourceDto, ReplyProgressStageDto, SaveChatDraftRequestDto, TerminalSessionDto,
};
use application::{LaunchAgentOutput, StructuredSessionDescriptor};
use domain::project::ProjectPath;
@ -28,6 +28,7 @@ fn reply_chunk_user_prompt_serialises_exact_camel_case() {
let v = serde_json::to_value(ReplyChunk::UserPrompt {
text: "run tests".into(),
attachments: Vec::new(),
attachment_refs: Vec::new(),
})
.unwrap();
assert_eq!(v, json!({ "kind": "userPrompt", "text": "run tests" }));
@ -38,6 +39,13 @@ fn reply_chunk_user_prompt_serialises_attachment_labels() {
let v = serde_json::to_value(ReplyChunk::UserPrompt {
text: "describe this".into(),
attachments: vec!["clipboard.png".into()],
attachment_refs: vec![ChatPromptAttachmentDto {
reference: "[image 1]".into(),
filename: "clipboard.png".into(),
mime: "image/png".into(),
size_bytes: 9,
readable_path: "/project/.ideai/attachments/agent-chat/s/attach-1-clipboard.png".into(),
}],
})
.unwrap();
assert_eq!(
@ -45,7 +53,14 @@ fn reply_chunk_user_prompt_serialises_attachment_labels() {
json!({
"kind": "userPrompt",
"text": "describe this",
"attachments": ["clipboard.png"]
"attachments": ["clipboard.png"],
"attachmentRefs": [{
"reference": "[image 1]",
"filename": "clipboard.png",
"mime": "image/png",
"sizeBytes": 9,
"readablePath": "/project/.ideai/attachments/agent-chat/s/attach-1-clipboard.png"
}]
})
);
}
@ -128,6 +143,14 @@ fn reply_chunk_round_trips_through_json_for_every_variant() {
ReplyChunk::UserPrompt {
text: "u".into(),
attachments: vec!["clipboard.png".into()],
attachment_refs: vec![ChatPromptAttachmentDto {
reference: "[image 1]".into(),
filename: "clipboard.png".into(),
mime: "image/png".into(),
size_bytes: 9,
readable_path: "/project/.ideai/attachments/agent-chat/s/attach-1-clipboard.png"
.into(),
}],
},
ReplyChunk::TextDelta { text: "x".into() },
ReplyChunk::ToolActivity {
@ -168,7 +191,8 @@ fn reply_chunk_deserialises_from_camel_case_wire_payload() {
back,
ReplyChunk::UserPrompt {
text: "hi".into(),
attachments: Vec::new()
attachments: Vec::new(),
attachment_refs: Vec::new()
}
);
}
@ -343,6 +367,7 @@ fn reattach_chat_dto_serialises_camel_case_with_typed_scrollback() {
ReplyChunk::UserPrompt {
text: "Hi".into(),
attachments: Vec::new(),
attachment_refs: Vec::new(),
},
ReplyChunk::TextDelta { text: "Hi".into() },
ReplyChunk::Final {