Files
clawdbot/apps/macos/Sources/Clawdis/Resources/WebChat/components/Messages.js
2025-12-09 17:23:00 +00:00

281 lines
10 KiB
JavaScript

var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { html, LitElement } from "lit";
import { customElement, property } from "lit/decorators.js";
import { renderTool } from "../tools/index.js";
import { formatUsage } from "../utils/format.js";
import { i18n } from "../utils/i18n.js";
import { formatClock, renderSurfaceChip } from "../utils/message-meta.js";
import "./ThinkingBlock.js";
let UserMessage = class UserMessage extends LitElement {
createRenderRoot() {
return this;
}
connectedCallback() {
super.connectedCallback();
this.style.display = "block";
}
render() {
const content = typeof this.message.content === "string"
? this.message.content
: this.message.content.find((c) => c.type === "text")?.text || "";
return html `
<div class="px-4 mb-1 flex items-center gap-2 text-[11px] uppercase tracking-[0.08em] text-muted-foreground">
${renderSurfaceChip(this.message.surface, this.message.senderHost, this.message.senderIp)}
<span class="opacity-70">${formatClock(this.message.timestamp)}</span>
</div>
<div class="flex justify-start mx-4">
<div class="user-message-container py-2 px-4 rounded-xl">
<markdown-block .content=${content}></markdown-block>
${this.message.attachments && this.message.attachments.length > 0
? html `
<div class="mt-3 flex flex-wrap gap-2">
${this.message.attachments.map((attachment) => html ` <attachment-tile .attachment=${attachment}></attachment-tile> `)}
</div>
`
: ""}
</div>
</div>
`;
}
};
__decorate([
property({ type: Object })
], UserMessage.prototype, "message", void 0);
UserMessage = __decorate([
customElement("user-message")
], UserMessage);
export { UserMessage };
let AssistantMessage = class AssistantMessage extends LitElement {
constructor() {
super(...arguments);
this.hideToolCalls = false;
this.isStreaming = false;
}
createRenderRoot() {
return this;
}
connectedCallback() {
super.connectedCallback();
this.style.display = "block";
}
render() {
// Render content in the order it appears
const orderedParts = [];
for (const chunk of this.message.content) {
if (chunk.type === "text" && chunk.text.trim() !== "") {
orderedParts.push(html `<markdown-block .content=${chunk.text}></markdown-block>`);
}
else if (chunk.type === "thinking" && chunk.thinking.trim() !== "") {
orderedParts.push(html `<thinking-block .content=${chunk.thinking} .isStreaming=${this.isStreaming}></thinking-block>`);
}
else if (chunk.type === "toolCall") {
if (!this.hideToolCalls) {
const tool = this.tools?.find((t) => t.name === chunk.name);
const pending = this.pendingToolCalls?.has(chunk.id) ?? false;
const result = this.toolResultsById?.get(chunk.id);
// A tool call is aborted if the message was aborted and there's no result for this tool call
const aborted = this.message.stopReason === "aborted" && !result;
orderedParts.push(html `<tool-message
.tool=${tool}
.toolCall=${chunk}
.result=${result}
.pending=${pending}
.aborted=${aborted}
.isStreaming=${this.isStreaming}
></tool-message>`);
}
}
}
return html `
<div>
${orderedParts.length ? html ` <div class="px-4 flex flex-col gap-3">${orderedParts}</div> ` : ""}
${this.message.usage && !this.isStreaming
? this.onCostClick
? html ` <div class="px-4 mt-2 text-xs text-muted-foreground cursor-pointer hover:text-foreground transition-colors" @click=${this.onCostClick}>${formatUsage(this.message.usage)}</div> `
: html ` <div class="px-4 mt-2 text-xs text-muted-foreground">${formatUsage(this.message.usage)}</div> `
: ""}
${this.message.stopReason === "error" && this.message.errorMessage
? html `
<div class="mx-4 mt-3 p-3 bg-destructive/10 text-destructive rounded-lg text-sm overflow-hidden">
<strong>${i18n("Error:")}</strong> ${this.message.errorMessage}
</div>
`
: ""}
${this.message.stopReason === "aborted"
? html `<span class="text-sm text-destructive italic">${i18n("Request aborted")}</span>`
: ""}
</div>
`;
}
};
__decorate([
property({ type: Object })
], AssistantMessage.prototype, "message", void 0);
__decorate([
property({ type: Array })
], AssistantMessage.prototype, "tools", void 0);
__decorate([
property({ type: Object })
], AssistantMessage.prototype, "pendingToolCalls", void 0);
__decorate([
property({ type: Boolean })
], AssistantMessage.prototype, "hideToolCalls", void 0);
__decorate([
property({ type: Object })
], AssistantMessage.prototype, "toolResultsById", void 0);
__decorate([
property({ type: Boolean })
], AssistantMessage.prototype, "isStreaming", void 0);
__decorate([
property({ attribute: false })
], AssistantMessage.prototype, "onCostClick", void 0);
AssistantMessage = __decorate([
customElement("assistant-message")
], AssistantMessage);
export { AssistantMessage };
let ToolMessageDebugView = class ToolMessageDebugView extends LitElement {
constructor() {
super(...arguments);
this.hasResult = false;
}
createRenderRoot() {
return this; // light DOM for shared styles
}
connectedCallback() {
super.connectedCallback();
this.style.display = "block";
}
pretty(value) {
try {
if (typeof value === "string") {
const maybeJson = JSON.parse(value);
return { content: JSON.stringify(maybeJson, null, 2), isJson: true };
}
return { content: JSON.stringify(value, null, 2), isJson: true };
}
catch {
return { content: typeof value === "string" ? value : String(value), isJson: false };
}
}
render() {
const textOutput = this.result?.content
?.filter((c) => c.type === "text")
.map((c) => c.text)
.join("\n") || "";
const output = this.pretty(textOutput);
const details = this.pretty(this.result?.details);
return html `
<div class="mt-3 flex flex-col gap-2">
<div>
<div class="text-xs font-medium mb-1 text-muted-foreground">${i18n("Call")}</div>
<code-block .code=${this.pretty(this.callArgs).content} language="json"></code-block>
</div>
<div>
<div class="text-xs font-medium mb-1 text-muted-foreground">${i18n("Result")}</div>
${this.hasResult
? html `<code-block .code=${output.content} language="${output.isJson ? "json" : "text"}"></code-block>
<code-block .code=${details.content} language="${details.isJson ? "json" : "text"}"></code-block>`
: html `<div class="text-xs text-muted-foreground">${i18n("(no result)")}</div>`}
</div>
</div>
`;
}
};
__decorate([
property({ type: Object })
], ToolMessageDebugView.prototype, "callArgs", void 0);
__decorate([
property({ type: Object })
], ToolMessageDebugView.prototype, "result", void 0);
__decorate([
property({ type: Boolean })
], ToolMessageDebugView.prototype, "hasResult", void 0);
ToolMessageDebugView = __decorate([
customElement("tool-message-debug")
], ToolMessageDebugView);
export { ToolMessageDebugView };
let ToolMessage = class ToolMessage extends LitElement {
constructor() {
super(...arguments);
this.pending = false;
this.aborted = false;
this.isStreaming = false;
}
createRenderRoot() {
return this;
}
connectedCallback() {
super.connectedCallback();
this.style.display = "block";
}
render() {
const toolName = this.tool?.name || this.toolCall.name;
// Render tool content (renderer handles errors and styling)
const result = this.aborted
? {
role: "toolResult",
isError: true,
content: [],
toolCallId: this.toolCall.id,
toolName: this.toolCall.name,
timestamp: Date.now(),
}
: this.result;
const renderResult = renderTool(toolName, this.toolCall.arguments, result, !this.aborted && (this.isStreaming || this.pending));
// Handle custom rendering (no card wrapper)
if (renderResult.isCustom) {
return renderResult.content;
}
// Default: wrap in card
return html `
<div class="p-2.5 border border-border rounded-md bg-card text-card-foreground shadow-xs">
${renderResult.content}
</div>
`;
}
};
__decorate([
property({ type: Object })
], ToolMessage.prototype, "toolCall", void 0);
__decorate([
property({ type: Object })
], ToolMessage.prototype, "tool", void 0);
__decorate([
property({ type: Object })
], ToolMessage.prototype, "result", void 0);
__decorate([
property({ type: Boolean })
], ToolMessage.prototype, "pending", void 0);
__decorate([
property({ type: Boolean })
], ToolMessage.prototype, "aborted", void 0);
__decorate([
property({ type: Boolean })
], ToolMessage.prototype, "isStreaming", void 0);
ToolMessage = __decorate([
customElement("tool-message")
], ToolMessage);
export { ToolMessage };
let AbortedMessage = class AbortedMessage extends LitElement {
createRenderRoot() {
return this;
}
connectedCallback() {
super.connectedCallback();
this.style.display = "block";
}
render() {
return html `<span class="text-sm text-destructive italic">${i18n("Request aborted")}</span>`;
}
};
AbortedMessage = __decorate([
customElement("aborted-message")
], AbortedMessage);
export { AbortedMessage };
//# sourceMappingURL=Messages.js.map