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

View File

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

View File

@ -1,10 +1,6 @@
import { describe, expect, it } from "vitest";
import {
handleChatEvent,
type ChatEventPayload,
type ChatState,
} from "./chat";
import { handleChatEvent, type ChatEventPayload, type ChatState } from "./chat";
function createState(overrides: Partial<ChatState> = {}): ChatState {
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.
// See https://github.com/clawdbot/clawdbot/issues/1909
if (
payload.runId &&
state.chatRunId &&
payload.runId !== state.chatRunId
) {
if (payload.runId && state.chatRunId && payload.runId !== state.chatRunId) {
if (payload.state === "final") return "final";
return null;
}

View File

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