format fix

This commit is contained in:
Cyrus Goh 2026-01-25 21:40:51 -08:00
parent 07650765d2
commit 6bc21fae4e
No known key found for this signature in database
GPG Key ID: D547D2AF3FD0FD5A
5 changed files with 96 additions and 107 deletions

View File

@ -413,79 +413,80 @@ export function renderApp(state: AppViewState) {
: nothing : nothing
} }
${state.tab === "chat" ${
? renderChat({ state.tab === "chat"
sessionKey: state.sessionKey, ? renderChat({
onSessionKeyChange: (next) => { sessionKey: state.sessionKey,
state.sessionKey = next; onSessionKeyChange: (next) => {
state.chatMessage = ""; state.sessionKey = next;
state.chatAttachments = []; state.chatMessage = "";
state.chatStream = null; state.chatAttachments = [];
state.chatStreamStartedAt = null; state.chatStream = null;
state.chatRunId = null; state.chatStreamStartedAt = null;
state.chatQueue = []; state.chatRunId = null;
state.resetToolStream(); state.chatQueue = [];
state.resetChatScroll(); state.resetToolStream();
state.applySettings({ state.resetChatScroll();
...state.settings, state.applySettings({
sessionKey: next, ...state.settings,
lastActiveSessionKey: next, sessionKey: next,
}); lastActiveSessionKey: next,
void state.loadAssistantIdentity(); });
void loadChatHistory(state); void state.loadAssistantIdentity();
void refreshChatAvatar(state); void loadChatHistory(state);
}, void refreshChatAvatar(state);
thinkingLevel: state.chatThinkingLevel, },
showThinking, thinkingLevel: state.chatThinkingLevel,
loading: state.chatLoading, showThinking,
sending: state.chatSending, loading: state.chatLoading,
compactionStatus: state.compactionStatus, sending: state.chatSending,
assistantAvatarUrl: chatAvatarUrl, compactionStatus: state.compactionStatus,
messages: state.chatMessages, assistantAvatarUrl: chatAvatarUrl,
toolMessages: state.chatToolMessages, messages: state.chatMessages,
stream: state.chatStream, toolMessages: state.chatToolMessages,
streamStartedAt: state.chatStreamStartedAt, stream: state.chatStream,
draft: state.chatMessage, streamStartedAt: state.chatStreamStartedAt,
queue: state.chatQueue, draft: state.chatMessage,
connected: state.connected, queue: state.chatQueue,
canSend: state.connected, connected: state.connected,
disabledReason: chatDisabledReason, canSend: state.connected,
error: state.lastError, disabledReason: chatDisabledReason,
sessions: state.sessionsResult, error: state.lastError,
focusMode: chatFocus, sessions: state.sessionsResult,
onRefresh: () => { focusMode: chatFocus,
state.resetToolStream(); onRefresh: () => {
return Promise.all([loadChatHistory(state), refreshChatAvatar(state)]); state.resetToolStream();
}, return Promise.all([loadChatHistory(state), refreshChatAvatar(state)]);
onToggleFocusMode: () => { },
if (state.onboarding) return; onToggleFocusMode: () => {
state.applySettings({ if (state.onboarding) return;
...state.settings, state.applySettings({
chatFocusMode: !state.settings.chatFocusMode, ...state.settings,
}); chatFocusMode: !state.settings.chatFocusMode,
}, });
onChatScroll: (event) => state.handleChatScroll(event), },
onDraftChange: (next) => (state.chatMessage = next), onChatScroll: (event) => state.handleChatScroll(event),
attachments: state.chatAttachments, onDraftChange: (next) => (state.chatMessage = next),
onAttachmentsChange: (next) => (state.chatAttachments = next), attachments: state.chatAttachments,
onSend: () => state.handleSendChat(), onAttachmentsChange: (next) => (state.chatAttachments = next),
canAbort: Boolean(state.chatRunId), onSend: () => state.handleSendChat(),
onAbort: () => void state.handleAbortChat(), canAbort: Boolean(state.chatRunId),
onQueueRemove: (id) => state.removeQueuedMessage(id), onAbort: () => void state.handleAbortChat(),
onNewSession: () => onQueueRemove: (id) => state.removeQueuedMessage(id),
state.handleSendChat("/new", { restoreDraft: true }), onNewSession: () => state.handleSendChat("/new", { restoreDraft: true }),
// Sidebar props for tool output viewing // Sidebar props for tool output viewing
sidebarOpen: state.sidebarOpen, sidebarOpen: state.sidebarOpen,
sidebarContent: state.sidebarContent, sidebarContent: state.sidebarContent,
sidebarError: state.sidebarError, sidebarError: state.sidebarError,
splitRatio: state.splitRatio, splitRatio: state.splitRatio,
onOpenSidebar: (content: string) => state.handleOpenSidebar(content), onOpenSidebar: (content: string) => state.handleOpenSidebar(content),
onCloseSidebar: () => state.handleCloseSidebar(), onCloseSidebar: () => state.handleCloseSidebar(),
onSplitRatioChange: (ratio: number) => state.handleSplitRatioChange(ratio), onSplitRatioChange: (ratio: number) => state.handleSplitRatioChange(ratio),
assistantName: state.assistantName, assistantName: state.assistantName,
assistantAvatar: state.assistantAvatar, assistantAvatar: state.assistantAvatar,
}) })
: nothing} : nothing
}
${ ${
state.tab === "config" state.tab === "config"

View File

@ -35,9 +35,7 @@ function extractImages(message: unknown): ImageBlock[] {
const data = source.data as string; const data = source.data as string;
const mediaType = (source.media_type as string) || "image/png"; const mediaType = (source.media_type as string) || "image/png";
// If data is already a data URL, use it directly // If data is already a data URL, use it directly
const url = data.startsWith("data:") const url = data.startsWith("data:") ? data : `data:${mediaType};base64,${data}`;
? data
: `data:${mediaType};base64,${data}`;
images.push({ url }); images.push({ url });
} else if (typeof b.url === "string") { } else if (typeof b.url === "string") {
images.push({ url: b.url }); images.push({ url: b.url });
@ -260,14 +258,18 @@ function renderGroupedMessage(
<div class="${bubbleClasses}"> <div class="${bubbleClasses}">
${canCopyMarkdown ? renderCopyAsMarkdownButton(markdown!) : nothing} ${canCopyMarkdown ? renderCopyAsMarkdownButton(markdown!) : nothing}
${renderMessageImages(images)} ${renderMessageImages(images)}
${reasoningMarkdown ${
? html`<div class="chat-thinking">${unsafeHTML( reasoningMarkdown
toSanitizedMarkdownHtml(reasoningMarkdown), ? html`<div class="chat-thinking">${unsafeHTML(
)}</div>` toSanitizedMarkdownHtml(reasoningMarkdown),
: nothing} )}</div>`
${markdown : nothing
? html`<div class="chat-text">${unsafeHTML(toSanitizedMarkdownHtml(markdown))}</div>` }
: nothing} ${
markdown
? html`<div class="chat-text">${unsafeHTML(toSanitizedMarkdownHtml(markdown))}</div>`
: nothing
}
${toolCards.map((card) => renderToolCardSidebar(card, onOpenSidebar))} ${toolCards.map((card) => renderToolCardSidebar(card, onOpenSidebar))}
</div> </div>
`; `;

View File

@ -1,10 +1,6 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { import { handleChatEvent, type ChatEventPayload, type ChatState } from "./chat";
handleChatEvent,
type ChatEventPayload,
type ChatState,
} from "./chat";
function createState(overrides: Partial<ChatState> = {}): ChatState { function createState(overrides: Partial<ChatState> = {}): ChatState {
return { return {

View File

@ -159,11 +159,7 @@ export function handleChatEvent(state: ChatState, payload?: ChatEventPayload) {
// Final from another run (e.g. sub-agent announce): refresh history to show new message. // Final from another run (e.g. sub-agent announce): refresh history to show new message.
// See https://github.com/clawdbot/clawdbot/issues/1909 // See https://github.com/clawdbot/clawdbot/issues/1909
if ( if (payload.runId && state.chatRunId && payload.runId !== state.chatRunId) {
payload.runId &&
state.chatRunId &&
payload.runId !== state.chatRunId
) {
if (payload.state === "final") return "final"; if (payload.state === "final") return "final";
return null; return null;
} }

View File

@ -99,10 +99,7 @@ function generateAttachmentId(): string {
return `att-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`; return `att-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
} }
function handlePaste( function handlePaste(e: ClipboardEvent, props: ChatProps) {
e: ClipboardEvent,
props: ChatProps,
) {
const items = e.clipboardData?.items; const items = e.clipboardData?.items;
if (!items || !props.onAttachmentsChange) return; if (!items || !props.onAttachmentsChange) return;
@ -156,9 +153,7 @@ function renderAttachmentPreview(props: ChatProps) {
type="button" type="button"
aria-label="Remove attachment" aria-label="Remove attachment"
@click=${() => { @click=${() => {
const next = (props.attachments ?? []).filter( const next = (props.attachments ?? []).filter((a) => a.id !== att.id);
(a) => a.id !== att.id,
);
props.onAttachmentsChange?.(next); props.onAttachmentsChange?.(next);
}} }}
> >
@ -305,10 +300,10 @@ export function renderChat(props: ChatProps) {
(item) => html` (item) => html`
<div class="chat-queue__item"> <div class="chat-queue__item">
<div class="chat-queue__text"> <div class="chat-queue__text">
${item.text || ${
(item.attachments?.length item.text ||
? `Image (${item.attachments.length})` (item.attachments?.length ? `Image (${item.attachments.length})` : "")
: "")} }
</div> </div>
<button <button
class="btn chat-queue__remove" class="btn chat-queue__remove"
@ -343,8 +338,7 @@ export function renderChat(props: ChatProps) {
e.preventDefault(); e.preventDefault();
if (canCompose) props.onSend(); if (canCompose) props.onSend();
}} }}
@input=${(e: Event) => @input=${(e: Event) => props.onDraftChange((e.target as HTMLTextAreaElement).value)}
props.onDraftChange((e.target as HTMLTextAreaElement).value)}
@paste=${(e: ClipboardEvent) => handlePaste(e, props)} @paste=${(e: ClipboardEvent) => handlePaste(e, props)}
placeholder=${composePlaceholder} placeholder=${composePlaceholder}
></textarea> ></textarea>