fix(webchat): support image-only sends
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { abortChatRun, loadChatHistory, sendChatMessage, type ChatAttachment } from "./controllers/chat";
|
||||
import { abortChatRun, loadChatHistory, sendChatMessage } from "./controllers/chat";
|
||||
import { loadSessions } from "./controllers/sessions";
|
||||
import { generateUUID } from "./uuid";
|
||||
import { resetToolStream } from "./app-tool-stream";
|
||||
@@ -8,12 +8,13 @@ import { normalizeBasePath } from "./navigation";
|
||||
import type { GatewayHelloOk } from "./gateway";
|
||||
import { parseAgentSessionKey } from "../../../src/sessions/session-key-utils.js";
|
||||
import type { ClawdbotApp } from "./app";
|
||||
import type { ChatAttachment, ChatQueueItem } from "./ui-types";
|
||||
|
||||
type ChatHost = {
|
||||
connected: boolean;
|
||||
chatMessage: string;
|
||||
chatAttachments: ChatAttachment[];
|
||||
chatQueue: Array<{ id: string; text: string; createdAt: number }>;
|
||||
chatQueue: ChatQueueItem[];
|
||||
chatRunId: string | null;
|
||||
chatSending: boolean;
|
||||
sessionKey: string;
|
||||
@@ -46,15 +47,17 @@ export async function handleAbortChat(host: ChatHost) {
|
||||
await abortChatRun(host as unknown as ClawdbotApp);
|
||||
}
|
||||
|
||||
function enqueueChatMessage(host: ChatHost, text: string) {
|
||||
function enqueueChatMessage(host: ChatHost, text: string, attachments?: ChatAttachment[]) {
|
||||
const trimmed = text.trim();
|
||||
if (!trimmed) return;
|
||||
const hasAttachments = Boolean(attachments && attachments.length > 0);
|
||||
if (!trimmed && !hasAttachments) return;
|
||||
host.chatQueue = [
|
||||
...host.chatQueue,
|
||||
{
|
||||
id: generateUUID(),
|
||||
text: trimmed,
|
||||
createdAt: Date.now(),
|
||||
attachments: hasAttachments ? attachments?.map((att) => ({ ...att })) : undefined,
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -62,19 +65,31 @@ function enqueueChatMessage(host: ChatHost, text: string) {
|
||||
async function sendChatMessageNow(
|
||||
host: ChatHost,
|
||||
message: string,
|
||||
opts?: { previousDraft?: string; restoreDraft?: boolean; attachments?: ChatAttachment[] },
|
||||
opts?: {
|
||||
previousDraft?: string;
|
||||
restoreDraft?: boolean;
|
||||
attachments?: ChatAttachment[];
|
||||
previousAttachments?: ChatAttachment[];
|
||||
restoreAttachments?: boolean;
|
||||
},
|
||||
) {
|
||||
resetToolStream(host as unknown as Parameters<typeof resetToolStream>[0]);
|
||||
const ok = await sendChatMessage(host as unknown as ClawdbotApp, message, opts?.attachments);
|
||||
if (!ok && opts?.previousDraft != null) {
|
||||
host.chatMessage = opts.previousDraft;
|
||||
}
|
||||
if (!ok && opts?.previousAttachments) {
|
||||
host.chatAttachments = opts.previousAttachments;
|
||||
}
|
||||
if (ok) {
|
||||
setLastActiveSessionKey(host as unknown as Parameters<typeof setLastActiveSessionKey>[0], host.sessionKey);
|
||||
}
|
||||
if (ok && opts?.restoreDraft && opts.previousDraft?.trim()) {
|
||||
host.chatMessage = opts.previousDraft;
|
||||
}
|
||||
if (ok && opts?.restoreAttachments && opts.previousAttachments?.length) {
|
||||
host.chatAttachments = opts.previousAttachments;
|
||||
}
|
||||
scheduleChatScroll(host as unknown as Parameters<typeof scheduleChatScroll>[0]);
|
||||
if (ok && !host.chatRunId) {
|
||||
void flushChatQueue(host);
|
||||
@@ -87,7 +102,7 @@ async function flushChatQueue(host: ChatHost) {
|
||||
const [next, ...rest] = host.chatQueue;
|
||||
if (!next) return;
|
||||
host.chatQueue = rest;
|
||||
const ok = await sendChatMessageNow(host, next.text);
|
||||
const ok = await sendChatMessageNow(host, next.text, { attachments: next.attachments });
|
||||
if (!ok) {
|
||||
host.chatQueue = [next, ...host.chatQueue];
|
||||
}
|
||||
@@ -106,7 +121,8 @@ export async function handleSendChat(
|
||||
const previousDraft = host.chatMessage;
|
||||
const message = (messageOverride ?? host.chatMessage).trim();
|
||||
const attachments = host.chatAttachments ?? [];
|
||||
const hasAttachments = attachments.length > 0;
|
||||
const attachmentsToSend = messageOverride == null ? attachments : [];
|
||||
const hasAttachments = attachmentsToSend.length > 0;
|
||||
|
||||
// Allow sending with just attachments (no message text required)
|
||||
if (!message && !hasAttachments) return;
|
||||
@@ -123,14 +139,16 @@ export async function handleSendChat(
|
||||
}
|
||||
|
||||
if (isChatBusy(host)) {
|
||||
enqueueChatMessage(host, message);
|
||||
enqueueChatMessage(host, message, attachmentsToSend);
|
||||
return;
|
||||
}
|
||||
|
||||
await sendChatMessageNow(host, message, {
|
||||
previousDraft: messageOverride == null ? previousDraft : undefined,
|
||||
restoreDraft: Boolean(messageOverride && opts?.restoreDraft),
|
||||
attachments: hasAttachments ? attachments : undefined,
|
||||
attachments: hasAttachments ? attachmentsToSend : undefined,
|
||||
previousAttachments: messageOverride == null ? attachments : undefined,
|
||||
restoreAttachments: Boolean(messageOverride && opts?.restoreDraft),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -431,6 +431,7 @@ export function renderApp(state: AppViewState) {
|
||||
onSessionKeyChange: (next) => {
|
||||
state.sessionKey = next;
|
||||
state.chatMessage = "";
|
||||
state.chatAttachments = [];
|
||||
state.chatStream = null;
|
||||
state.chatStreamStartedAt = null;
|
||||
state.chatRunId = null;
|
||||
|
||||
@@ -19,7 +19,7 @@ import type {
|
||||
SkillStatusReport,
|
||||
StatusSummary,
|
||||
} from "./types";
|
||||
import type { ChatQueueItem, CronFormState } from "./ui-types";
|
||||
import type { ChatAttachment, ChatQueueItem, CronFormState } from "./ui-types";
|
||||
import type { EventLogEntry } from "./app-events";
|
||||
import type { SkillMessage } from "./controllers/skills";
|
||||
import type {
|
||||
@@ -49,6 +49,7 @@ export type AppViewState = {
|
||||
chatLoading: boolean;
|
||||
chatSending: boolean;
|
||||
chatMessage: string;
|
||||
chatAttachments: ChatAttachment[];
|
||||
chatMessages: unknown[];
|
||||
chatToolMessages: unknown[];
|
||||
chatStream: string | null;
|
||||
|
||||
@@ -24,7 +24,7 @@ import type {
|
||||
StatusSummary,
|
||||
NostrProfile,
|
||||
} from "./types";
|
||||
import { type ChatQueueItem, type CronFormState } from "./ui-types";
|
||||
import { type ChatAttachment, type ChatQueueItem, type CronFormState } from "./ui-types";
|
||||
import type { EventLogEntry } from "./app-events";
|
||||
import { DEFAULT_CRON_FORM, DEFAULT_LOG_LEVEL_FILTERS } from "./app-defaults";
|
||||
import type {
|
||||
@@ -129,7 +129,7 @@ export class ClawdbotApp extends LitElement {
|
||||
@state() chatAvatarUrl: string | null = null;
|
||||
@state() chatThinkingLevel: string | null = null;
|
||||
@state() chatQueue: ChatQueueItem[] = [];
|
||||
@state() chatAttachments: Array<{ id: string; dataUrl: string; mimeType: string }> = [];
|
||||
@state() chatAttachments: ChatAttachment[] = [];
|
||||
// Sidebar state for tool output viewing
|
||||
@state() sidebarOpen = false;
|
||||
@state() sidebarContent: string | null = null;
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import { extractText } from "../chat/message-extract";
|
||||
import type { GatewayBrowserClient } from "../gateway";
|
||||
import { generateUUID } from "../uuid";
|
||||
|
||||
export type ChatAttachment = {
|
||||
id: string;
|
||||
dataUrl: string;
|
||||
mimeType: string;
|
||||
};
|
||||
import type { ChatAttachment } from "../ui-types";
|
||||
|
||||
export type ChatState = {
|
||||
client: GatewayBrowserClient | null;
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
export type ChatAttachment = {
|
||||
id: string;
|
||||
dataUrl: string;
|
||||
mimeType: string;
|
||||
};
|
||||
|
||||
export type ChatQueueItem = {
|
||||
id: string;
|
||||
text: string;
|
||||
createdAt: number;
|
||||
attachments?: ChatAttachment[];
|
||||
};
|
||||
|
||||
export const CRON_CHANNEL_LAST = "last";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { html, nothing } from "lit";
|
||||
import { repeat } from "lit/directives/repeat.js";
|
||||
import type { SessionsListResult } from "../types";
|
||||
import type { ChatQueueItem } from "../ui-types";
|
||||
import type { ChatAttachment, ChatQueueItem } from "../ui-types";
|
||||
import type { ChatItem, MessageGroup } from "../types/chat-types";
|
||||
import { icons } from "../icons";
|
||||
import {
|
||||
@@ -22,12 +22,6 @@ export type CompactionIndicatorStatus = {
|
||||
completedAt: number | null;
|
||||
};
|
||||
|
||||
export type ChatAttachment = {
|
||||
id: string;
|
||||
dataUrl: string;
|
||||
mimeType: string;
|
||||
};
|
||||
|
||||
export type ChatProps = {
|
||||
sessionKey: string;
|
||||
onSessionKeyChange: (next: string) => void;
|
||||
@@ -305,7 +299,12 @@ export function renderChat(props: ChatProps) {
|
||||
${props.queue.map(
|
||||
(item) => html`
|
||||
<div class="chat-queue__item">
|
||||
<div class="chat-queue__text">${item.text}</div>
|
||||
<div class="chat-queue__text">
|
||||
${item.text ||
|
||||
(item.attachments?.length
|
||||
? `Image (${item.attachments.length})`
|
||||
: "")}
|
||||
</div>
|
||||
<button
|
||||
class="btn chat-queue__remove"
|
||||
type="button"
|
||||
|
||||
Reference in New Issue
Block a user