mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 05:16:23 -06:00
Control UI: unify composer attachment chip layout (#130312)
* fix(ui): unify composer attachment chips * fix(ui): resolve composer attachment glyphs
This commit is contained in:
committed by
GitHub
parent
7a550091d1
commit
34ccf63e13
@@ -5872,11 +5872,11 @@ describe("chat attachment picker", () => {
|
||||
expect(preview.querySelector(".chat-attachment-file__name")?.textContent).toBe("brief.pdf");
|
||||
});
|
||||
|
||||
it("accepts video file attachments with the generic file preview", async () => {
|
||||
it("infers video preview glyphs from filenames when MIME is absent", async () => {
|
||||
const onAttachmentsChange = vi.fn();
|
||||
const container = renderChatView({ onAttachmentsChange });
|
||||
const input = container.querySelector<HTMLInputElement>(".agent-chat__file-input");
|
||||
const file = new File(["video"], "clip.mp4", { type: "video/mp4" });
|
||||
const file = new File(["video"], "clip.mp4");
|
||||
|
||||
expect(input).toBeInstanceOf(HTMLInputElement);
|
||||
expect(input?.accept).toContain("video/*");
|
||||
@@ -5886,7 +5886,7 @@ describe("chat attachment picker", () => {
|
||||
const attachments = requireFirstAttachmentsChange(onAttachmentsChange);
|
||||
expect(attachments).toHaveLength(1);
|
||||
expect(attachments[0]?.fileName).toBe("clip.mp4");
|
||||
expect(attachments[0]?.mimeType).toBe("video/mp4");
|
||||
expect(attachments[0]?.mimeType).toBe("application/octet-stream");
|
||||
expect(attachments[0]?.sizeBytes).toBe(file.size);
|
||||
});
|
||||
|
||||
@@ -5894,6 +5894,10 @@ describe("chat attachment picker", () => {
|
||||
const preview = renderChatView({ attachments: nextAttachments });
|
||||
expect(preview.querySelectorAll(".chat-attachment-thumb--file")).toHaveLength(1);
|
||||
expect(preview.querySelector(".chat-attachment-file__name")?.textContent).toBe("clip.mp4");
|
||||
expect(preview.querySelector(".chat-attachment-file__icon")?.getAttribute("data-family")).toBe(
|
||||
"video",
|
||||
);
|
||||
expect(preview.querySelector(".chat-attachment-file__type")?.textContent).toBe("MP4");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
releaseChatAttachmentPayload,
|
||||
} from "../attachment-payload-store.ts";
|
||||
import { admitAttachmentFiles } from "./chat-attachment-admission.ts";
|
||||
import { renderAttachmentFileIcon } from "./chat-attachment-file-icon.ts";
|
||||
import { resolveAttachmentFileIcon } from "./chat-attachment-file-icon.ts";
|
||||
import { syncChatAttachmentRailScroll } from "./chat-attachment-viewport.ts";
|
||||
|
||||
const CHAT_ATTACHMENT_ACCEPT =
|
||||
@@ -195,6 +195,32 @@ function pastedTextPreview(attachment: ChatAttachment): string {
|
||||
);
|
||||
}
|
||||
|
||||
function renderCompactAttachmentFile(attachment: ChatAttachment) {
|
||||
const resolved = resolveAttachmentFileIcon(
|
||||
attachment.fileName ?? "attachment",
|
||||
attachment.mimeType,
|
||||
);
|
||||
const glyph =
|
||||
resolved.family === "video"
|
||||
? icons.play
|
||||
: resolved.family === "audio"
|
||||
? icons.music
|
||||
: icons.fileText;
|
||||
return html`
|
||||
<openclaw-tooltip .content=${attachment.fileName ?? t("chat.attachments.attachedFile")}>
|
||||
<div class="chat-attachment-file">
|
||||
<span class="chat-attachment-file__icon" data-family=${resolved.family}>${glyph}</span>
|
||||
<span class="chat-attachment-file__body">
|
||||
<span class="chat-attachment-file__name"
|
||||
>${attachment.fileName ?? t("chat.attachments.attachedFile")}</span
|
||||
>
|
||||
<span class="chat-attachment-file__type">${resolved.extensionLabel}</span>
|
||||
</span>
|
||||
</div>
|
||||
</openclaw-tooltip>
|
||||
`;
|
||||
}
|
||||
|
||||
function appendPastedTextToDraft(draft: string, text: string): string {
|
||||
if (!draft.trim()) {
|
||||
return text;
|
||||
@@ -685,13 +711,7 @@ export function renderAttachmentPreview(props: ChatAttachmentControlsProps) {
|
||||
: isLargePastedTextAttachment(att)
|
||||
? html`
|
||||
<div class="chat-attachment-file chat-attachment-file--pasted-text">
|
||||
<span class="chat-attachment-file__icon"
|
||||
>${renderAttachmentFileIcon({
|
||||
filename: att.fileName ?? "pasted-text.txt",
|
||||
mimeType: att.mimeType,
|
||||
mode: "preview-with-favicon",
|
||||
})}</span
|
||||
>
|
||||
<span class="chat-attachment-file__icon">${icons.fileText}</span>
|
||||
<span class="chat-attachment-file__body">
|
||||
<span class="chat-attachment-file__name"
|
||||
>${pastedTextPreview(att)}</span
|
||||
@@ -709,24 +729,7 @@ export function renderAttachmentPreview(props: ChatAttachmentControlsProps) {
|
||||
</span>
|
||||
</div>
|
||||
`
|
||||
: html`
|
||||
<openclaw-tooltip
|
||||
.content=${att.fileName ?? t("chat.attachments.attachedFile")}
|
||||
>
|
||||
<div class="chat-attachment-file">
|
||||
<span class="chat-attachment-file__icon"
|
||||
>${renderAttachmentFileIcon({
|
||||
filename: att.fileName ?? "attachment",
|
||||
mimeType: att.mimeType,
|
||||
mode: "large-placeholder",
|
||||
})}</span
|
||||
>
|
||||
<span class="chat-attachment-file__name"
|
||||
>${att.fileName ?? t("chat.attachments.attachedFile")}</span
|
||||
>
|
||||
</div>
|
||||
</openclaw-tooltip>
|
||||
`}
|
||||
: renderCompactAttachmentFile(att)}
|
||||
<openclaw-tooltip .content=${t("chat.composer.removeAttachment")}>
|
||||
<button
|
||||
class="chat-attachment-remove"
|
||||
|
||||
@@ -5592,7 +5592,7 @@ button.chat-pr__diff {
|
||||
padding-block-start: 10px;
|
||||
padding-block-end: 5px;
|
||||
padding-inline: 10px;
|
||||
margin-bottom: 8px;
|
||||
margin-bottom: 2px;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
overscroll-behavior-inline: contain;
|
||||
@@ -5632,7 +5632,7 @@ button.chat-pr__diff {
|
||||
corner-shape: superellipse(1.5);
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--border);
|
||||
background: var(--panel);
|
||||
background: var(--chat-composer-surface, var(--popover));
|
||||
transition:
|
||||
border-color var(--duration-fast) ease,
|
||||
background var(--duration-fast) ease;
|
||||
@@ -5644,13 +5644,13 @@ button.chat-pr__diff {
|
||||
}
|
||||
|
||||
.chat-attachment-thumb--file {
|
||||
width: 220px;
|
||||
height: 82px;
|
||||
width: 162px;
|
||||
height: 56px;
|
||||
}
|
||||
|
||||
.chat-attachment-thumb--pasted-text {
|
||||
width: min(220px, calc(100vw - 64px));
|
||||
background: transparent;
|
||||
width: min(162px, calc(100vw - 64px));
|
||||
background: var(--chat-composer-surface, var(--popover));
|
||||
}
|
||||
|
||||
.chat-attachment-thumb--browser-annotation {
|
||||
@@ -5661,7 +5661,7 @@ button.chat-pr__diff {
|
||||
max-width: min(300px, calc(100vw - 64px));
|
||||
min-width: 0;
|
||||
height: 56px;
|
||||
background: transparent;
|
||||
background: var(--chat-composer-surface, var(--popover));
|
||||
}
|
||||
|
||||
.chat-browser-annotation-card__preview {
|
||||
@@ -5723,7 +5723,7 @@ button.chat-pr__diff {
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.chat-attachment-image-button {
|
||||
.chat-attachment-image-button.chat-attachment-image-button {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@@ -5731,11 +5731,12 @@ button.chat-pr__diff {
|
||||
corner-shape: superellipse(1.5);
|
||||
}
|
||||
|
||||
.chat-attachment-image-button::after {
|
||||
.chat-attachment-image-button.chat-attachment-image-button::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
border-radius: calc(var(--chat-attachment-radius) - 1px);
|
||||
corner-shape: superellipse(1.5);
|
||||
background: color-mix(in srgb, var(--text-strong) 8%, transparent);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
@@ -5754,7 +5755,8 @@ button.chat-pr__diff {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
padding: 0;
|
||||
border-radius: 50%;
|
||||
border-radius: calc(7px * var(--openclaw-corner-radius-scale));
|
||||
corner-shape: superellipse(1.5);
|
||||
border: none;
|
||||
background: color-mix(in srgb, var(--bg) 82%, transparent);
|
||||
color: var(--text-strong);
|
||||
@@ -5796,11 +5798,11 @@ button.chat-pr__diff {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 9px;
|
||||
width: fit-content;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
height: 100%;
|
||||
padding: 7px 10px;
|
||||
padding: 8px 12px;
|
||||
overflow: hidden;
|
||||
font-size: 0.72rem;
|
||||
color: var(--text);
|
||||
@@ -5811,13 +5813,13 @@ button.chat-pr__diff {
|
||||
}
|
||||
|
||||
.chat-attachment-thumb--file:is(:hover, :focus-within) .chat-attachment-file {
|
||||
background: color-mix(in srgb, var(--panel) 86%, var(--text));
|
||||
background: color-mix(in srgb, var(--chat-composer-surface, var(--popover)) 86%, var(--text));
|
||||
}
|
||||
|
||||
.chat-attachment-file--pasted-text {
|
||||
justify-content: flex-start;
|
||||
gap: 8px;
|
||||
padding: 10px 42px 10px 10px;
|
||||
gap: 10px;
|
||||
padding: 8px 32px 8px 12px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
@@ -5826,11 +5828,19 @@ button.chat-pr__diff {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 0 0 auto;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.chat-attachment-file--pasted-text .chat-attachment-file__icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
.chat-attachment-file__icon svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
stroke: currentColor;
|
||||
fill: none;
|
||||
stroke-width: 1.8px;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
}
|
||||
|
||||
.chat-attachment-file__name {
|
||||
@@ -5842,19 +5852,28 @@ button.chat-pr__diff {
|
||||
}
|
||||
|
||||
.chat-attachment-file--pasted-text .chat-attachment-file__name {
|
||||
font-size: 0.86rem;
|
||||
line-height: 1.2;
|
||||
font-size: 0.72rem;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.chat-attachment-file__body {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
max-width: 220px;
|
||||
max-width: 100%;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.chat-attachment-file__type {
|
||||
overflow: hidden;
|
||||
color: var(--muted);
|
||||
font-size: 0.64rem;
|
||||
line-height: 1.1;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.chat-attachment-text-action {
|
||||
display: inline-flex;
|
||||
width: fit-content;
|
||||
@@ -5867,10 +5886,14 @@ button.chat-pr__diff {
|
||||
background: transparent;
|
||||
cursor: var(--cursor-action);
|
||||
font: inherit;
|
||||
overflow: hidden;
|
||||
font-size: 0.64rem;
|
||||
line-height: 1.15;
|
||||
text-align: left;
|
||||
text-overflow: ellipsis;
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 2px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.chat-attachment-text-action:hover {
|
||||
|
||||
Reference in New Issue
Block a user