fix(webchat): scope attachment button input

This commit is contained in:
Jason O'Neal
2026-05-18 23:18:51 -04:00
committed by Peter Steinberger
parent 0c044596c5
commit cf70bdcceb
3 changed files with 34 additions and 3 deletions

View File

@@ -61,6 +61,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- WebChat: summarize internal message-tool source replies so tool cards no longer duplicate the visible reply body. (#84773) Thanks @jason-allen-oneal.
- WebChat: scope the visible attachment button to its own composer file input so clicking Upload reliably opens the file picker. (#83952, fixes #47983) Thanks @jason-allen-oneal.
- Gateway: preserve deferred lifecycle-error cleanup across later non-terminal events so provider timeouts can persist failed session state instead of leaving sessions stuck running. (#85256, fixes #63819) Thanks @samzong.
- Agents/subagents: report tool-only child progress during timeout summaries instead of showing no visible output.
- Telegram/ACP: preserve explicit `:topic:` conversation suffixes when inbound ACP targets do not carry a separate thread id.

View File

@@ -1070,6 +1070,26 @@ describe("chat attachment picker", () => {
expect(getChatAttachmentDataUrl(attachments[0])).toBe(`data:image/png;base64,${base64}`);
});
it("opens the scoped file input from the visible attach button", () => {
const container = renderChatView();
const input = requireElement(
container,
".agent-chat__file-input",
"attachment file input",
) as HTMLInputElement;
const attachButton = requireElement(
container,
`[aria-label="${t("chat.composer.attachFile")}"]`,
"attach button",
) as HTMLButtonElement;
const clickInput = vi.spyOn(input, "click").mockImplementation(() => undefined);
attachButton.click();
expect(attachButton.type).toBe("button");
expect(clickInput).toHaveBeenCalledTimes(1);
});
it("accepts and previews non-video file attachments", async () => {
const onAttachmentsChange = vi.fn();
const container = renderChatView({ onAttachmentsChange });

View File

@@ -427,6 +427,17 @@ function focusComposerFromChrome(event: MouseEvent, connected: boolean) {
?.focus({ preventScroll: true });
}
function clickComposerFileInput(event: MouseEvent) {
const target = event.currentTarget;
if (!(target instanceof HTMLElement)) {
return;
}
target
.closest(".agent-chat__input")
?.querySelector<HTMLInputElement>(".agent-chat__file-input")
?.click();
}
function restoreHistoryCaret(target: HTMLTextAreaElement, direction: "up" | "down") {
requestAnimationFrame(() => {
if (document.activeElement !== target) {
@@ -1562,10 +1573,9 @@ export function renderChat(props: ChatProps) {
<div class="agent-chat__toolbar">
<div class="agent-chat__toolbar-left">
<button
type="button"
class="agent-chat__input-btn"
@click=${() => {
document.querySelector<HTMLInputElement>(".agent-chat__file-input")?.click();
}}
@click=${clickComposerFileInput}
title=${t("chat.composer.attachFile")}
aria-label=${t("chat.composer.attachFile")}
?disabled=${!props.connected}