fix(cli): bouton Send↔Stop #168 + collage clipboard fichier générique #155

This commit is contained in:
2026-08-06 20:00:36 +02:00
parent 292a81ee60
commit 8e97da57c9
5 changed files with 140 additions and 48 deletions

View File

@ -246,11 +246,13 @@ function bytesToBase64(bytes: Uint8Array): string {
return btoa(binary);
}
async function clipboardImageToAttachment(file: File): Promise<AttachmentDraft> {
async function clipboardFileToAttachment(file: File): Promise<AttachmentDraft> {
const mime = file.type || "application/octet-stream";
const filename =
file.name ||
`clipboard-image-${Date.now()}.${fileExtension(mime)}`;
(mime.startsWith("image/")
? `clipboard-image-${Date.now()}.${fileExtension(mime)}`
: `clipboard-file-${Date.now()}`);
const contentBase64 = bytesToBase64(new Uint8Array(await file.arrayBuffer()));
return {
id: `clipboard-${Date.now()}-${Math.random().toString(36).slice(2)}`,
@ -267,7 +269,7 @@ async function clipboardImageToAttachment(file: File): Promise<AttachmentDraft>
};
}
function clipboardImageFiles(event: ClipboardEvent<HTMLTextAreaElement>): File[] {
function clipboardFiles(event: ClipboardEvent<HTMLTextAreaElement>): File[] {
const clipboardItems = event.clipboardData.items
? Array.from(event.clipboardData.items)
: [];
@ -275,12 +277,11 @@ function clipboardImageFiles(event: ClipboardEvent<HTMLTextAreaElement>): File[]
? Array.from(event.clipboardData.files)
: [];
const fromItems = clipboardItems
.filter((item) => item.kind === "file" && item.type.startsWith("image/"))
.filter((item) => item.kind === "file")
.map((item) => item.getAsFile())
.filter((file): file is File => Boolean(file));
const fromFiles = clipboardFiles.filter((file) => file.type.startsWith("image/"));
const seen = new Set<string>();
return [...fromItems, ...fromFiles].filter((file) => {
return [...fromItems, ...clipboardFiles].filter((file) => {
const key = `${file.name}\u0000${file.type}\u0000${file.size}\u0000${file.lastModified}`;
if (seen.has(key)) return false;
seen.add(key);
@ -849,10 +850,10 @@ export function CustomAgentChatView({
}
}
async function pasteClipboardImages(
async function pasteClipboardFiles(
event: ClipboardEvent<HTMLTextAreaElement>,
) {
const files = clipboardImageFiles(event);
const files = clipboardFiles(event);
if (files.length === 0) return;
event.preventDefault();
@ -861,7 +862,7 @@ export function CustomAgentChatView({
const selectionEnd = event.currentTarget.selectionEnd;
try {
const nextAttachments = await Promise.all(
files.map(clipboardImageToAttachment),
files.map(clipboardFileToAttachment),
);
setAttachments((prev) => [...prev, ...nextAttachments]);
if (pastedText) {
@ -976,18 +977,6 @@ export function CustomAgentChatView({
CLI custom · {profile.name}
</div>
</div>
<div className="flex shrink-0 items-center justify-end gap-2">
{(opening || busy) && (
<Button
size="sm"
variant="danger"
className="relative z-30 shrink-0 whitespace-nowrap shadow-sm"
onClick={() => void cancel()}
>
Cancel
</Button>
)}
</div>
</div>
{!supported && (
@ -1206,7 +1195,7 @@ export function CustomAgentChatView({
disabled={!supported || opening || busy}
placeholder="Message à l'agent…"
onChange={(e) => setDraft(e.target.value)}
onPaste={(e) => void pasteClipboardImages(e)}
onPaste={(e) => void pasteClipboardFiles(e)}
onKeyDown={(e) => {
if (slashMenuOpen && slashCommands.length > 0) {
if (e.key === "ArrowDown") {
@ -1250,12 +1239,12 @@ export function CustomAgentChatView({
</Button>
<Button
size="sm"
variant={busy ? "danger" : undefined}
className="shrink-0 whitespace-nowrap"
disabled={!canSend}
loading={busy}
onClick={() => void send()}
disabled={busy ? !supported || !currentSession : !canSend}
onClick={() => (busy ? void cancel() : void send())}
>
Envoyer
{busy ? "Stop" : "Envoyer"}
</Button>
</div>
</div>