mac: bundle web chat assets
This commit is contained in:
39
apps/macos/Sources/Clawdis/Resources/WebChat/components/AgentInterface.d.ts
vendored
Normal file
39
apps/macos/Sources/Clawdis/Resources/WebChat/components/AgentInterface.d.ts
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
import { LitElement } from "lit";
|
||||
import "./MessageEditor.js";
|
||||
import "./MessageList.js";
|
||||
import "./Messages.js";
|
||||
import type { Agent } from "../agent/agent.js";
|
||||
import "./StreamingMessageContainer.js";
|
||||
import type { Attachment } from "../utils/attachment-utils.js";
|
||||
export declare class AgentInterface extends LitElement {
|
||||
session?: Agent;
|
||||
enableAttachments: boolean;
|
||||
enableModelSelector: boolean;
|
||||
enableThinkingSelector: boolean;
|
||||
showThemeToggle: boolean;
|
||||
onApiKeyRequired?: (provider: string) => Promise<boolean>;
|
||||
onBeforeSend?: () => void | Promise<void>;
|
||||
onBeforeToolCall?: (toolName: string, args: any) => boolean | Promise<boolean>;
|
||||
onCostClick?: () => void;
|
||||
private _messageEditor;
|
||||
private _streamingContainer;
|
||||
private _autoScroll;
|
||||
private _lastScrollTop;
|
||||
private _lastClientHeight;
|
||||
private _scrollContainer?;
|
||||
private _resizeObserver?;
|
||||
private _unsubscribeSession?;
|
||||
setInput(text: string, attachments?: Attachment[]): void;
|
||||
setAutoScroll(enabled: boolean): void;
|
||||
protected createRenderRoot(): HTMLElement | DocumentFragment;
|
||||
willUpdate(changedProperties: Map<string, any>): void;
|
||||
connectedCallback(): Promise<void>;
|
||||
disconnectedCallback(): void;
|
||||
private setupSessionSubscription;
|
||||
private _handleScroll;
|
||||
sendMessage(input: string, attachments?: Attachment[]): Promise<void>;
|
||||
private renderMessages;
|
||||
private renderStats;
|
||||
render(): import("lit-html").TemplateResult<1>;
|
||||
}
|
||||
//# sourceMappingURL=AgentInterface.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"AgentInterface.d.ts","sourceRoot":"","sources":["../../src/components/AgentInterface.ts"],"names":[],"mappings":"AACA,OAAO,EAAQ,UAAU,EAAE,MAAM,KAAK,CAAC;AAIvC,OAAO,oBAAoB,CAAC;AAC5B,OAAO,kBAAkB,CAAC;AAC1B,OAAO,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,KAAK,EAAc,MAAM,mBAAmB,CAAC;AAE3D,OAAO,gCAAgC,CAAC;AACxC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAK/D,qBACa,cAAe,SAAQ,UAAU;IAEb,OAAO,CAAC,EAAE,KAAK,CAAC;IACnB,iBAAiB,UAAQ;IACzB,mBAAmB,UAAQ;IAC3B,sBAAsB,UAAQ;IAC9B,eAAe,UAAS;IAErB,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAE1D,YAAY,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE1C,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAE/E,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IAGhC,OAAO,CAAC,cAAc,CAAiB;IAC1B,OAAO,CAAC,mBAAmB,CAA6B;IAE9F,OAAO,CAAC,WAAW,CAAQ;IAC3B,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,gBAAgB,CAAC,CAAc;IACvC,OAAO,CAAC,eAAe,CAAC,CAAiB;IACzC,OAAO,CAAC,mBAAmB,CAAC,CAAa;IAElC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,UAAU,EAAE;IAWjD,aAAa,CAAC,OAAO,EAAE,OAAO;cAIlB,gBAAgB,IAAI,WAAW,GAAG,gBAAgB;IAI5D,UAAU,CAAC,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC;IASxC,iBAAiB;IAkCvB,oBAAoB;IAmB7B,OAAO,CAAC,wBAAwB;IAqBhC,OAAO,CAAC,aAAa,CAwBnB;IAEW,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,UAAU,EAAE;IAsClE,OAAO,CAAC,cAAc;IAmCtB,OAAO,CAAC,WAAW;IAgDV,MAAM;CA4Cf"}
|
||||
@@ -0,0 +1,330 @@
|
||||
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, query } from "lit/decorators.js";
|
||||
import { ModelSelector } from "../dialogs/ModelSelector.js";
|
||||
import "./MessageEditor.js";
|
||||
import "./MessageList.js";
|
||||
import "./Messages.js"; // Import for side effects to register the custom elements
|
||||
import { getAppStorage } from "../storage/app-storage.js";
|
||||
import "./StreamingMessageContainer.js";
|
||||
import { formatUsage } from "../utils/format.js";
|
||||
import { i18n } from "../utils/i18n.js";
|
||||
let AgentInterface = class AgentInterface extends LitElement {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.enableAttachments = true;
|
||||
this.enableModelSelector = true;
|
||||
this.enableThinkingSelector = true;
|
||||
this.showThemeToggle = false;
|
||||
this._autoScroll = true;
|
||||
this._lastScrollTop = 0;
|
||||
this._lastClientHeight = 0;
|
||||
this._handleScroll = (_ev) => {
|
||||
if (!this._scrollContainer)
|
||||
return;
|
||||
const currentScrollTop = this._scrollContainer.scrollTop;
|
||||
const scrollHeight = this._scrollContainer.scrollHeight;
|
||||
const clientHeight = this._scrollContainer.clientHeight;
|
||||
const distanceFromBottom = scrollHeight - currentScrollTop - clientHeight;
|
||||
// Ignore relayout due to message editor getting pushed up by stats
|
||||
if (clientHeight < this._lastClientHeight) {
|
||||
this._lastClientHeight = clientHeight;
|
||||
return;
|
||||
}
|
||||
// Only disable auto-scroll if user scrolled UP or is far from bottom
|
||||
if (currentScrollTop !== 0 && currentScrollTop < this._lastScrollTop && distanceFromBottom > 50) {
|
||||
this._autoScroll = false;
|
||||
}
|
||||
else if (distanceFromBottom < 10) {
|
||||
// Re-enable if very close to bottom
|
||||
this._autoScroll = true;
|
||||
}
|
||||
this._lastScrollTop = currentScrollTop;
|
||||
this._lastClientHeight = clientHeight;
|
||||
};
|
||||
}
|
||||
setInput(text, attachments) {
|
||||
const update = () => {
|
||||
if (!this._messageEditor)
|
||||
requestAnimationFrame(update);
|
||||
else {
|
||||
this._messageEditor.value = text;
|
||||
this._messageEditor.attachments = attachments || [];
|
||||
}
|
||||
};
|
||||
update();
|
||||
}
|
||||
setAutoScroll(enabled) {
|
||||
this._autoScroll = enabled;
|
||||
}
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
willUpdate(changedProperties) {
|
||||
super.willUpdate(changedProperties);
|
||||
// Re-subscribe when session property changes
|
||||
if (changedProperties.has("session")) {
|
||||
this.setupSessionSubscription();
|
||||
}
|
||||
}
|
||||
async connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.style.display = "flex";
|
||||
this.style.flexDirection = "column";
|
||||
this.style.height = "100%";
|
||||
this.style.minHeight = "0";
|
||||
// Wait for first render to get scroll container
|
||||
await this.updateComplete;
|
||||
this._scrollContainer = this.querySelector(".overflow-y-auto");
|
||||
if (this._scrollContainer) {
|
||||
// Set up ResizeObserver to detect content changes
|
||||
this._resizeObserver = new ResizeObserver(() => {
|
||||
if (this._autoScroll && this._scrollContainer) {
|
||||
this._scrollContainer.scrollTop = this._scrollContainer.scrollHeight;
|
||||
}
|
||||
});
|
||||
// Observe the content container inside the scroll container
|
||||
const contentContainer = this._scrollContainer.querySelector(".max-w-3xl");
|
||||
if (contentContainer) {
|
||||
this._resizeObserver.observe(contentContainer);
|
||||
}
|
||||
// Set up scroll listener with better detection
|
||||
this._scrollContainer.addEventListener("scroll", this._handleScroll);
|
||||
}
|
||||
// Subscribe to external session if provided
|
||||
this.setupSessionSubscription();
|
||||
}
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
// Clean up observers and listeners
|
||||
if (this._resizeObserver) {
|
||||
this._resizeObserver.disconnect();
|
||||
this._resizeObserver = undefined;
|
||||
}
|
||||
if (this._scrollContainer) {
|
||||
this._scrollContainer.removeEventListener("scroll", this._handleScroll);
|
||||
}
|
||||
if (this._unsubscribeSession) {
|
||||
this._unsubscribeSession();
|
||||
this._unsubscribeSession = undefined;
|
||||
}
|
||||
}
|
||||
setupSessionSubscription() {
|
||||
if (this._unsubscribeSession) {
|
||||
this._unsubscribeSession();
|
||||
this._unsubscribeSession = undefined;
|
||||
}
|
||||
if (!this.session)
|
||||
return;
|
||||
this._unsubscribeSession = this.session.subscribe(async (ev) => {
|
||||
if (ev.type === "state-update") {
|
||||
if (this._streamingContainer) {
|
||||
this._streamingContainer.isStreaming = ev.state.isStreaming;
|
||||
this._streamingContainer.setMessage(ev.state.streamMessage, !ev.state.isStreaming);
|
||||
}
|
||||
this.requestUpdate();
|
||||
}
|
||||
else if (ev.type === "error-no-model") {
|
||||
// TODO show some UI feedback
|
||||
}
|
||||
else if (ev.type === "error-no-api-key") {
|
||||
// Handled by onApiKeyRequired callback
|
||||
}
|
||||
});
|
||||
}
|
||||
async sendMessage(input, attachments) {
|
||||
if ((!input.trim() && attachments?.length === 0) || this.session?.state.isStreaming)
|
||||
return;
|
||||
const session = this.session;
|
||||
if (!session)
|
||||
throw new Error("No session set on AgentInterface");
|
||||
if (!session.state.model)
|
||||
throw new Error("No model set on AgentInterface");
|
||||
// Check if API key exists for the provider (only needed in direct mode)
|
||||
const provider = session.state.model.provider;
|
||||
const apiKey = await getAppStorage().providerKeys.get(provider);
|
||||
// If no API key, prompt for it
|
||||
if (!apiKey) {
|
||||
if (!this.onApiKeyRequired) {
|
||||
console.error("No API key configured and no onApiKeyRequired handler set");
|
||||
return;
|
||||
}
|
||||
const success = await this.onApiKeyRequired(provider);
|
||||
// If still no API key, abort the send
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Call onBeforeSend hook before sending
|
||||
if (this.onBeforeSend) {
|
||||
await this.onBeforeSend();
|
||||
}
|
||||
// Only clear editor after we know we can send
|
||||
this._messageEditor.value = "";
|
||||
this._messageEditor.attachments = [];
|
||||
this._autoScroll = true; // Enable auto-scroll when sending a message
|
||||
await this.session?.prompt(input, attachments);
|
||||
}
|
||||
renderMessages() {
|
||||
if (!this.session)
|
||||
return html `<div class="p-4 text-center text-muted-foreground">${i18n("No session available")}</div>`;
|
||||
const state = this.session.state;
|
||||
// Build a map of tool results to allow inline rendering in assistant messages
|
||||
const toolResultsById = new Map();
|
||||
for (const message of state.messages) {
|
||||
if (message.role === "toolResult") {
|
||||
toolResultsById.set(message.toolCallId, message);
|
||||
}
|
||||
}
|
||||
return html `
|
||||
<div class="flex flex-col gap-3">
|
||||
<!-- Stable messages list - won't re-render during streaming -->
|
||||
<message-list
|
||||
.messages=${this.session.state.messages}
|
||||
.tools=${state.tools}
|
||||
.pendingToolCalls=${this.session ? this.session.state.pendingToolCalls : new Set()}
|
||||
.isStreaming=${state.isStreaming}
|
||||
.onCostClick=${this.onCostClick}
|
||||
></message-list>
|
||||
|
||||
<!-- Streaming message container - manages its own updates -->
|
||||
<streaming-message-container
|
||||
class="${state.isStreaming ? "" : "hidden"}"
|
||||
.tools=${state.tools}
|
||||
.isStreaming=${state.isStreaming}
|
||||
.pendingToolCalls=${state.pendingToolCalls}
|
||||
.toolResultsById=${toolResultsById}
|
||||
.onCostClick=${this.onCostClick}
|
||||
></streaming-message-container>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
renderStats() {
|
||||
if (!this.session)
|
||||
return html `<div class="text-xs h-5"></div>`;
|
||||
const state = this.session.state;
|
||||
const totals = state.messages
|
||||
.filter((m) => m.role === "assistant")
|
||||
.reduce((acc, msg) => {
|
||||
const usage = msg.usage;
|
||||
if (usage) {
|
||||
acc.input += usage.input;
|
||||
acc.output += usage.output;
|
||||
acc.cacheRead += usage.cacheRead;
|
||||
acc.cacheWrite += usage.cacheWrite;
|
||||
acc.cost.total += usage.cost.total;
|
||||
}
|
||||
return acc;
|
||||
}, {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
||||
});
|
||||
const hasTotals = totals.input || totals.output || totals.cacheRead || totals.cacheWrite;
|
||||
const totalsText = hasTotals ? formatUsage(totals) : "";
|
||||
return html `
|
||||
<div class="text-xs text-muted-foreground flex justify-between items-center h-5">
|
||||
<div class="flex items-center gap-1">
|
||||
${this.showThemeToggle ? html `<theme-toggle></theme-toggle>` : html ``}
|
||||
</div>
|
||||
<div class="flex ml-auto items-center gap-3">
|
||||
${totalsText
|
||||
? this.onCostClick
|
||||
? html `<span class="cursor-pointer hover:text-foreground transition-colors" @click=${this.onCostClick}>${totalsText}</span>`
|
||||
: html `<span>${totalsText}</span>`
|
||||
: ""}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
render() {
|
||||
if (!this.session)
|
||||
return html `<div class="p-4 text-center text-muted-foreground">${i18n("No session set")}</div>`;
|
||||
const session = this.session;
|
||||
const state = this.session.state;
|
||||
return html `
|
||||
<div class="flex flex-col h-full bg-background text-foreground">
|
||||
<!-- Messages Area -->
|
||||
<div class="flex-1 overflow-y-auto">
|
||||
<div class="max-w-3xl mx-auto p-4 pb-0">${this.renderMessages()}</div>
|
||||
</div>
|
||||
|
||||
<!-- Input Area -->
|
||||
<div class="shrink-0">
|
||||
<div class="max-w-3xl mx-auto px-2">
|
||||
<message-editor
|
||||
.isStreaming=${state.isStreaming}
|
||||
.currentModel=${state.model}
|
||||
.thinkingLevel=${state.thinkingLevel}
|
||||
.showAttachmentButton=${this.enableAttachments}
|
||||
.showModelSelector=${this.enableModelSelector}
|
||||
.showThinkingSelector=${this.enableThinkingSelector}
|
||||
.onSend=${(input, attachments) => {
|
||||
this.sendMessage(input, attachments);
|
||||
}}
|
||||
.onAbort=${() => session.abort()}
|
||||
.onModelSelect=${() => {
|
||||
ModelSelector.open(state.model, (model) => session.setModel(model));
|
||||
}}
|
||||
.onThinkingChange=${this.enableThinkingSelector
|
||||
? (level) => {
|
||||
session.setThinkingLevel(level);
|
||||
}
|
||||
: undefined}
|
||||
></message-editor>
|
||||
${this.renderStats()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
};
|
||||
__decorate([
|
||||
property({ attribute: false })
|
||||
], AgentInterface.prototype, "session", void 0);
|
||||
__decorate([
|
||||
property({ type: Boolean })
|
||||
], AgentInterface.prototype, "enableAttachments", void 0);
|
||||
__decorate([
|
||||
property({ type: Boolean })
|
||||
], AgentInterface.prototype, "enableModelSelector", void 0);
|
||||
__decorate([
|
||||
property({ type: Boolean })
|
||||
], AgentInterface.prototype, "enableThinkingSelector", void 0);
|
||||
__decorate([
|
||||
property({ type: Boolean })
|
||||
], AgentInterface.prototype, "showThemeToggle", void 0);
|
||||
__decorate([
|
||||
property({ attribute: false })
|
||||
], AgentInterface.prototype, "onApiKeyRequired", void 0);
|
||||
__decorate([
|
||||
property({ attribute: false })
|
||||
], AgentInterface.prototype, "onBeforeSend", void 0);
|
||||
__decorate([
|
||||
property({ attribute: false })
|
||||
], AgentInterface.prototype, "onBeforeToolCall", void 0);
|
||||
__decorate([
|
||||
property({ attribute: false })
|
||||
], AgentInterface.prototype, "onCostClick", void 0);
|
||||
__decorate([
|
||||
query("message-editor")
|
||||
], AgentInterface.prototype, "_messageEditor", void 0);
|
||||
__decorate([
|
||||
query("streaming-message-container")
|
||||
], AgentInterface.prototype, "_streamingContainer", void 0);
|
||||
AgentInterface = __decorate([
|
||||
customElement("agent-interface")
|
||||
], AgentInterface);
|
||||
export { AgentInterface };
|
||||
// Register custom element with guard
|
||||
if (!customElements.get("agent-interface")) {
|
||||
customElements.define("agent-interface", AgentInterface);
|
||||
}
|
||||
//# sourceMappingURL=AgentInterface.js.map
|
||||
File diff suppressed because one or more lines are too long
12
apps/macos/Sources/Clawdis/Resources/WebChat/components/AttachmentTile.d.ts
vendored
Normal file
12
apps/macos/Sources/Clawdis/Resources/WebChat/components/AttachmentTile.d.ts
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
import { LitElement } from "lit";
|
||||
import type { Attachment } from "../utils/attachment-utils.js";
|
||||
export declare class AttachmentTile extends LitElement {
|
||||
attachment: Attachment;
|
||||
showDelete: boolean;
|
||||
onDelete?: () => void;
|
||||
protected createRenderRoot(): HTMLElement | DocumentFragment;
|
||||
connectedCallback(): void;
|
||||
private handleClick;
|
||||
render(): import("lit-html").TemplateResult<1>;
|
||||
}
|
||||
//# sourceMappingURL=AttachmentTile.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"AttachmentTile.d.ts","sourceRoot":"","sources":["../../src/components/AttachmentTile.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,KAAK,CAAC;AAKjC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAG/D,qBACa,cAAe,SAAQ,UAAU;IACjB,UAAU,EAAG,UAAU,CAAC;IACvB,UAAU,UAAS;IACpC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;cAEf,gBAAgB,IAAI,WAAW,GAAG,gBAAgB;IAI5D,iBAAiB,IAAI,IAAI;IAMlC,OAAO,CAAC,WAAW,CAEjB;IAEO,MAAM;CAmFf"}
|
||||
@@ -0,0 +1,115 @@
|
||||
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 { icon } from "@mariozechner/mini-lit/dist/icons.js";
|
||||
import { LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { html } from "lit/html.js";
|
||||
import { FileSpreadsheet, FileText, X } from "lucide";
|
||||
import { AttachmentOverlay } from "../dialogs/AttachmentOverlay.js";
|
||||
import { i18n } from "../utils/i18n.js";
|
||||
let AttachmentTile = class AttachmentTile extends LitElement {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.showDelete = false;
|
||||
this.handleClick = () => {
|
||||
AttachmentOverlay.open(this.attachment);
|
||||
};
|
||||
}
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.style.display = "block";
|
||||
this.classList.add("max-h-16");
|
||||
}
|
||||
render() {
|
||||
const hasPreview = !!this.attachment.preview;
|
||||
const isImage = this.attachment.type === "image";
|
||||
const isPdf = this.attachment.mimeType === "application/pdf";
|
||||
const isDocx = this.attachment.mimeType?.includes("wordprocessingml") ||
|
||||
this.attachment.fileName.toLowerCase().endsWith(".docx");
|
||||
const isPptx = this.attachment.mimeType?.includes("presentationml") ||
|
||||
this.attachment.fileName.toLowerCase().endsWith(".pptx");
|
||||
const isExcel = this.attachment.mimeType?.includes("spreadsheetml") ||
|
||||
this.attachment.fileName.toLowerCase().endsWith(".xlsx") ||
|
||||
this.attachment.fileName.toLowerCase().endsWith(".xls");
|
||||
// Choose the appropriate icon
|
||||
const getDocumentIcon = () => {
|
||||
if (isExcel)
|
||||
return icon(FileSpreadsheet, "md");
|
||||
return icon(FileText, "md");
|
||||
};
|
||||
return html `
|
||||
<div class="relative group inline-block">
|
||||
${hasPreview
|
||||
? html `
|
||||
<div class="relative">
|
||||
<img
|
||||
src="data:${isImage ? this.attachment.mimeType : "image/png"};base64,${this.attachment.preview}"
|
||||
class="w-16 h-16 object-cover rounded-lg border border-input cursor-pointer hover:opacity-80 transition-opacity"
|
||||
alt="${this.attachment.fileName}"
|
||||
title="${this.attachment.fileName}"
|
||||
@click=${this.handleClick}
|
||||
/>
|
||||
${isPdf
|
||||
? html `
|
||||
<!-- PDF badge overlay -->
|
||||
<div class="absolute bottom-0 left-0 right-0 bg-background/90 px-1 py-0.5 rounded-b-lg">
|
||||
<div class="text-[10px] text-muted-foreground text-center font-medium">${i18n("PDF")}</div>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`
|
||||
: html `
|
||||
<!-- Fallback: document icon + filename -->
|
||||
<div
|
||||
class="w-16 h-16 rounded-lg border border-input cursor-pointer hover:opacity-80 transition-opacity bg-muted text-muted-foreground flex flex-col items-center justify-center p-2"
|
||||
@click=${this.handleClick}
|
||||
title="${this.attachment.fileName}"
|
||||
>
|
||||
${getDocumentIcon()}
|
||||
<div class="text-[10px] text-center truncate w-full">
|
||||
${this.attachment.fileName.length > 10
|
||||
? this.attachment.fileName.substring(0, 8) + "..."
|
||||
: this.attachment.fileName}
|
||||
</div>
|
||||
</div>
|
||||
`}
|
||||
${this.showDelete
|
||||
? html `
|
||||
<button
|
||||
@click=${(e) => {
|
||||
e.stopPropagation();
|
||||
this.onDelete?.();
|
||||
}}
|
||||
class="absolute -top-1 -right-1 w-5 h-5 bg-background hover:bg-muted text-muted-foreground hover:text-foreground rounded-full flex items-center justify-center opacity-100 hover:opacity-100 [@media(hover:hover)]:opacity-0 [@media(hover:hover)]:group-hover:opacity-100 transition-opacity border border-input shadow-sm"
|
||||
title="${i18n("Remove")}"
|
||||
>
|
||||
${icon(X, "xs")}
|
||||
</button>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
};
|
||||
__decorate([
|
||||
property({ type: Object })
|
||||
], AttachmentTile.prototype, "attachment", void 0);
|
||||
__decorate([
|
||||
property({ type: Boolean })
|
||||
], AttachmentTile.prototype, "showDelete", void 0);
|
||||
__decorate([
|
||||
property()
|
||||
], AttachmentTile.prototype, "onDelete", void 0);
|
||||
AttachmentTile = __decorate([
|
||||
customElement("attachment-tile")
|
||||
], AttachmentTile);
|
||||
export { AttachmentTile };
|
||||
//# sourceMappingURL=AttachmentTile.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"AttachmentTile.js","sourceRoot":"","sources":["../../src/components/AttachmentTile.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,sCAAsC,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAE,MAAM,KAAK,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACnC,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAEpE,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAGjC,IAAM,cAAc,GAApB,MAAM,cAAe,SAAQ,UAAU;IAAvC;;QAEuB,eAAU,GAAG,KAAK,CAAC;QAaxC,gBAAW,GAAG,GAAG,EAAE;YAC1B,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACzC,CAAC,CAAC;IAqFH,CAAC;IAjGmB,gBAAgB;QAClC,OAAO,IAAI,CAAC;IACb,CAAC;IAEQ,iBAAiB;QACzB,KAAK,CAAC,iBAAiB,EAAE,CAAC;QAC1B,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,OAAO,CAAC;QAC7B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IAChC,CAAC;IAMQ,MAAM;QACd,MAAM,UAAU,GAAG,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;QAC7C,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,KAAK,OAAO,CAAC;QACjD,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,QAAQ,KAAK,iBAAiB,CAAC;QAC7D,MAAM,MAAM,GACX,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,QAAQ,CAAC,kBAAkB,CAAC;YACtD,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAC1D,MAAM,MAAM,GACX,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,QAAQ,CAAC,gBAAgB,CAAC;YACpD,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAC1D,MAAM,OAAO,GACZ,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,QAAQ,CAAC,eAAe,CAAC;YACnD,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC;YACxD,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAEzD,8BAA8B;QAC9B,MAAM,eAAe,GAAG,GAAG,EAAE;YAC5B,IAAI,OAAO;gBAAE,OAAO,IAAI,CAAC,eAAe,EAAE,IAAI,CAAC,CAAC;YAChD,OAAO,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QAC7B,CAAC,CAAC;QAEF,OAAO,IAAI,CAAA;;MAGR,UAAU;YACT,CAAC,CAAC,IAAI,CAAA;;;qBAGS,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,WAAW,WAAW,IAAI,CAAC,UAAU,CAAC,OAAO;;gBAEvF,IAAI,CAAC,UAAU,CAAC,QAAQ;kBACtB,IAAI,CAAC,UAAU,CAAC,QAAQ;kBACxB,IAAI,CAAC,WAAW;;UAGzB,KAAK;gBACJ,CAAC,CAAC,IAAI,CAAA;;;qFAGqE,IAAI,CAAC,KAAK,CAAC;;WAErF;gBACD,CAAC,CAAC,EACJ;;OAED;YACD,CAAC,CAAC,IAAI,CAAA;;;;iBAIK,IAAI,CAAC,WAAW;iBAChB,IAAI,CAAC,UAAU,CAAC,QAAQ;;UAE/B,eAAe,EAAE;;WAGjB,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,MAAM,GAAG,EAAE;gBACnC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,KAAK;gBAClD,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,QACpB;;;OAIL;MAEC,IAAI,CAAC,UAAU;YACd,CAAC,CAAC,IAAI,CAAA;;iBAEK,CAAC,CAAQ,EAAE,EAAE;gBACrB,CAAC,CAAC,eAAe,EAAE,CAAC;gBACpB,IAAI,CAAC,QAAQ,EAAE,EAAE,CAAC;YACnB,CAAC;;iBAEQ,IAAI,CAAC,QAAQ,CAAC;;UAErB,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC;;OAEhB;YACD,CAAC,CAAC,EACJ;;GAED,CAAC;IACH,CAAC;CACD,CAAA;AArG4B;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;kDAAyB;AACvB;IAA5B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;kDAAoB;AACpC;IAAX,QAAQ,EAAE;gDAAuB;AAHtB,cAAc;IAD1B,aAAa,CAAC,iBAAiB,CAAC;GACpB,cAAc,CAsG1B"}
|
||||
12
apps/macos/Sources/Clawdis/Resources/WebChat/components/ConsoleBlock.d.ts
vendored
Normal file
12
apps/macos/Sources/Clawdis/Resources/WebChat/components/ConsoleBlock.d.ts
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
import { LitElement } from "lit";
|
||||
export declare class ConsoleBlock extends LitElement {
|
||||
content: string;
|
||||
variant: "default" | "error";
|
||||
private copied;
|
||||
protected createRenderRoot(): HTMLElement | DocumentFragment;
|
||||
connectedCallback(): void;
|
||||
private copy;
|
||||
updated(): void;
|
||||
render(): import("lit-html").TemplateResult<1>;
|
||||
}
|
||||
//# sourceMappingURL=ConsoleBlock.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ConsoleBlock.d.ts","sourceRoot":"","sources":["../../src/components/ConsoleBlock.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,KAAK,CAAC;AAMjC,qBAAa,YAAa,SAAQ,UAAU;IAC/B,OAAO,EAAE,MAAM,CAAM;IACrB,OAAO,EAAE,SAAS,GAAG,OAAO,CAAa;IAC5C,OAAO,CAAC,MAAM,CAAS;cAEb,gBAAgB,IAAI,WAAW,GAAG,gBAAgB;IAI5D,iBAAiB,IAAI,IAAI;YAKpB,IAAI;IAYT,OAAO;IAQP,MAAM;CAyBf"}
|
||||
@@ -0,0 +1,84 @@
|
||||
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 { icon } from "@mariozechner/mini-lit";
|
||||
import { LitElement } from "lit";
|
||||
import { property, state } from "lit/decorators.js";
|
||||
import { html } from "lit/html.js";
|
||||
import { Check, Copy } from "lucide";
|
||||
import { i18n } from "../utils/i18n.js";
|
||||
export class ConsoleBlock extends LitElement {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.content = "";
|
||||
this.variant = "default";
|
||||
this.copied = false;
|
||||
}
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.style.display = "block";
|
||||
}
|
||||
async copy() {
|
||||
try {
|
||||
await navigator.clipboard.writeText(this.content || "");
|
||||
this.copied = true;
|
||||
setTimeout(() => {
|
||||
this.copied = false;
|
||||
}, 1500);
|
||||
}
|
||||
catch (e) {
|
||||
console.error("Copy failed", e);
|
||||
}
|
||||
}
|
||||
updated() {
|
||||
// Auto-scroll to bottom on content changes
|
||||
const container = this.querySelector(".console-scroll");
|
||||
if (container) {
|
||||
container.scrollTop = container.scrollHeight;
|
||||
}
|
||||
}
|
||||
render() {
|
||||
const isError = this.variant === "error";
|
||||
const textClass = isError ? "text-destructive" : "text-foreground";
|
||||
return html `
|
||||
<div class="border border-border rounded-lg overflow-hidden">
|
||||
<div class="flex items-center justify-between px-3 py-1.5 bg-muted border-b border-border">
|
||||
<span class="text-xs text-muted-foreground font-mono">${i18n("console")}</span>
|
||||
<button
|
||||
@click=${() => this.copy()}
|
||||
class="flex items-center gap-1 px-2 py-0.5 text-xs rounded hover:bg-accent text-muted-foreground hover:text-accent-foreground transition-colors"
|
||||
title="${i18n("Copy output")}"
|
||||
>
|
||||
${this.copied ? icon(Check, "sm") : icon(Copy, "sm")}
|
||||
${this.copied ? html `<span>${i18n("Copied!")}</span>` : ""}
|
||||
</button>
|
||||
</div>
|
||||
<div class="console-scroll overflow-auto max-h-64">
|
||||
<pre class="!bg-background !border-0 !rounded-none m-0 p-3 text-xs ${textClass} font-mono whitespace-pre-wrap">
|
||||
${this.content || ""}</pre
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
__decorate([
|
||||
property()
|
||||
], ConsoleBlock.prototype, "content", void 0);
|
||||
__decorate([
|
||||
property()
|
||||
], ConsoleBlock.prototype, "variant", void 0);
|
||||
__decorate([
|
||||
state()
|
||||
], ConsoleBlock.prototype, "copied", void 0);
|
||||
// Register custom element
|
||||
if (!customElements.get("console-block")) {
|
||||
customElements.define("console-block", ConsoleBlock);
|
||||
}
|
||||
//# sourceMappingURL=ConsoleBlock.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ConsoleBlock.js","sourceRoot":"","sources":["../../src/components/ConsoleBlock.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,KAAK,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACnC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAExC,MAAM,OAAO,YAAa,SAAQ,UAAU;IAA5C;;QACa,YAAO,GAAW,EAAE,CAAC;QACrB,YAAO,GAAwB,SAAS,CAAC;QACpC,WAAM,GAAG,KAAK,CAAC;IAwDjC,CAAC;IAtDmB,gBAAgB;QAClC,OAAO,IAAI,CAAC;IACb,CAAC;IAEQ,iBAAiB;QACzB,KAAK,CAAC,iBAAiB,EAAE,CAAC;QAC1B,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,OAAO,CAAC;IAC9B,CAAC;IAEO,KAAK,CAAC,IAAI;QACjB,IAAI,CAAC;YACJ,MAAM,SAAS,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;YACxD,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;YACnB,UAAU,CAAC,GAAG,EAAE;gBACf,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;YACrB,CAAC,EAAE,IAAI,CAAC,CAAC;QACV,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACZ,OAAO,CAAC,KAAK,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC;QACjC,CAAC;IACF,CAAC;IAEQ,OAAO;QACf,2CAA2C;QAC3C,MAAM,SAAS,GAAG,IAAI,CAAC,aAAa,CAAC,iBAAiB,CAAuB,CAAC;QAC9E,IAAI,SAAS,EAAE,CAAC;YACf,SAAS,CAAC,SAAS,GAAG,SAAS,CAAC,YAAY,CAAC;QAC9C,CAAC;IACF,CAAC;IAEQ,MAAM;QACd,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,KAAK,OAAO,CAAC;QACzC,MAAM,SAAS,GAAG,OAAO,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,iBAAiB,CAAC;QAEnE,OAAO,IAAI,CAAA;;;6DAGgD,IAAI,CAAC,SAAS,CAAC;;eAE7D,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE;;eAEjB,IAAI,CAAC,aAAa,CAAC;;QAE1B,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC;QAClD,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAA,SAAS,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE;;;;0EAIU,SAAS;EACjF,IAAI,CAAC,OAAO,IAAI,EAAE;;;;GAIjB,CAAC;IACH,CAAC;CACD;AA1DY;IAAX,QAAQ,EAAE;6CAAsB;AACrB;IAAX,QAAQ,EAAE;6CAA0C;AACpC;IAAhB,KAAK,EAAE;4CAAwB;AA0DjC,0BAA0B;AAC1B,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,eAAe,CAAC,EAAE,CAAC;IAC1C,cAAc,CAAC,MAAM,CAAC,eAAe,EAAE,YAAY,CAAC,CAAC;AACtD,CAAC"}
|
||||
17
apps/macos/Sources/Clawdis/Resources/WebChat/components/CustomProviderCard.d.ts
vendored
Normal file
17
apps/macos/Sources/Clawdis/Resources/WebChat/components/CustomProviderCard.d.ts
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
import { LitElement, type TemplateResult } from "lit";
|
||||
import type { CustomProvider } from "../storage/stores/custom-providers-store.js";
|
||||
export declare class CustomProviderCard extends LitElement {
|
||||
provider: CustomProvider;
|
||||
isAutoDiscovery: boolean;
|
||||
status?: {
|
||||
modelCount: number;
|
||||
status: "connected" | "disconnected" | "checking";
|
||||
};
|
||||
onRefresh?: (provider: CustomProvider) => void;
|
||||
onEdit?: (provider: CustomProvider) => void;
|
||||
onDelete?: (provider: CustomProvider) => void;
|
||||
protected createRenderRoot(): this;
|
||||
private renderStatus;
|
||||
render(): TemplateResult;
|
||||
}
|
||||
//# sourceMappingURL=CustomProviderCard.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"CustomProviderCard.d.ts","sourceRoot":"","sources":["../../src/components/CustomProviderCard.ts"],"names":[],"mappings":"AAEA,OAAO,EAAQ,UAAU,EAAE,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAE5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6CAA6C,CAAC;AAElF,qBACa,kBAAmB,SAAQ,UAAU;IACrB,QAAQ,EAAG,cAAc,CAAC;IACzB,eAAe,UAAS;IACzB,MAAM,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,WAAW,GAAG,cAAc,GAAG,UAAU,CAAA;KAAE,CAAC;IACnG,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,cAAc,KAAK,IAAI,CAAC;IAC/C,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,cAAc,KAAK,IAAI,CAAC;IAC5C,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,cAAc,KAAK,IAAI,CAAC;IAE1D,SAAS,CAAC,gBAAgB;IAI1B,OAAO,CAAC,YAAY;IAgCpB,MAAM,IAAI,cAAc;CAgDxB"}
|
||||
@@ -0,0 +1,110 @@
|
||||
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 { i18n } from "@mariozechner/mini-lit";
|
||||
import { Button } from "@mariozechner/mini-lit/dist/Button.js";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
let CustomProviderCard = class CustomProviderCard extends LitElement {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.isAutoDiscovery = false;
|
||||
}
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
renderStatus() {
|
||||
if (!this.isAutoDiscovery) {
|
||||
return html `
|
||||
<div class="text-xs text-muted-foreground mt-1">
|
||||
${i18n("Models")}: ${this.provider.models?.length || 0}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
if (!this.status)
|
||||
return html ``;
|
||||
const statusIcon = this.status.status === "connected"
|
||||
? html `<span class="text-green-500">●</span>`
|
||||
: this.status.status === "checking"
|
||||
? html `<span class="text-yellow-500">●</span>`
|
||||
: html `<span class="text-red-500">●</span>`;
|
||||
const statusText = this.status.status === "connected"
|
||||
? `${this.status.modelCount} ${i18n("models")}`
|
||||
: this.status.status === "checking"
|
||||
? i18n("Checking...")
|
||||
: i18n("Disconnected");
|
||||
return html `
|
||||
<div class="text-xs text-muted-foreground mt-1 flex items-center gap-1">
|
||||
${statusIcon} ${statusText}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
render() {
|
||||
return html `
|
||||
<div class="border border-border rounded-lg p-4 space-y-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex-1">
|
||||
<div class="font-medium text-sm text-foreground">${this.provider.name}</div>
|
||||
<div class="text-xs text-muted-foreground mt-1">
|
||||
<span class="capitalize">${this.provider.type}</span>
|
||||
${this.provider.baseUrl ? html ` • ${this.provider.baseUrl}` : ""}
|
||||
</div>
|
||||
${this.renderStatus()}
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
${this.isAutoDiscovery && this.onRefresh
|
||||
? Button({
|
||||
onClick: () => this.onRefresh?.(this.provider),
|
||||
variant: "ghost",
|
||||
size: "sm",
|
||||
children: i18n("Refresh"),
|
||||
})
|
||||
: ""}
|
||||
${this.onEdit
|
||||
? Button({
|
||||
onClick: () => this.onEdit?.(this.provider),
|
||||
variant: "ghost",
|
||||
size: "sm",
|
||||
children: i18n("Edit"),
|
||||
})
|
||||
: ""}
|
||||
${this.onDelete
|
||||
? Button({
|
||||
onClick: () => this.onDelete?.(this.provider),
|
||||
variant: "ghost",
|
||||
size: "sm",
|
||||
children: i18n("Delete"),
|
||||
})
|
||||
: ""}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
};
|
||||
__decorate([
|
||||
property({ type: Object })
|
||||
], CustomProviderCard.prototype, "provider", void 0);
|
||||
__decorate([
|
||||
property({ type: Boolean })
|
||||
], CustomProviderCard.prototype, "isAutoDiscovery", void 0);
|
||||
__decorate([
|
||||
property({ type: Object })
|
||||
], CustomProviderCard.prototype, "status", void 0);
|
||||
__decorate([
|
||||
property()
|
||||
], CustomProviderCard.prototype, "onRefresh", void 0);
|
||||
__decorate([
|
||||
property()
|
||||
], CustomProviderCard.prototype, "onEdit", void 0);
|
||||
__decorate([
|
||||
property()
|
||||
], CustomProviderCard.prototype, "onDelete", void 0);
|
||||
CustomProviderCard = __decorate([
|
||||
customElement("custom-provider-card")
|
||||
], CustomProviderCard);
|
||||
export { CustomProviderCard };
|
||||
//# sourceMappingURL=CustomProviderCard.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"CustomProviderCard.js","sourceRoot":"","sources":["../../src/components/CustomProviderCard.ts"],"names":[],"mappings":"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"}
|
||||
15
apps/macos/Sources/Clawdis/Resources/WebChat/components/ExpandableSection.d.ts
vendored
Normal file
15
apps/macos/Sources/Clawdis/Resources/WebChat/components/ExpandableSection.d.ts
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
import { LitElement, type TemplateResult } from "lit";
|
||||
/**
|
||||
* Reusable expandable section component for tool renderers.
|
||||
* Captures children in connectedCallback and re-renders them in the details area.
|
||||
*/
|
||||
export declare class ExpandableSection extends LitElement {
|
||||
summary: string;
|
||||
defaultExpanded: boolean;
|
||||
private expanded;
|
||||
private capturedChildren;
|
||||
protected createRenderRoot(): this;
|
||||
connectedCallback(): void;
|
||||
render(): TemplateResult;
|
||||
}
|
||||
//# sourceMappingURL=ExpandableSection.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ExpandableSection.d.ts","sourceRoot":"","sources":["../../src/components/ExpandableSection.ts"],"names":[],"mappings":"AACA,OAAO,EAAQ,UAAU,EAAE,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAI5D;;;GAGG;AACH,qBACa,iBAAkB,SAAQ,UAAU;IACpC,OAAO,EAAG,MAAM,CAAC;IACA,eAAe,UAAS;IAC5C,OAAO,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,gBAAgB,CAAc;IAEtC,SAAS,CAAC,gBAAgB;IAIjB,iBAAiB;IASjB,MAAM,IAAI,cAAc;CAgBjC"}
|
||||
@@ -0,0 +1,63 @@
|
||||
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 { icon } from "@mariozechner/mini-lit";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import { ChevronDown, ChevronRight } from "lucide";
|
||||
/**
|
||||
* Reusable expandable section component for tool renderers.
|
||||
* Captures children in connectedCallback and re-renders them in the details area.
|
||||
*/
|
||||
let ExpandableSection = class ExpandableSection extends LitElement {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.defaultExpanded = false;
|
||||
this.expanded = false;
|
||||
this.capturedChildren = [];
|
||||
}
|
||||
createRenderRoot() {
|
||||
return this; // light DOM
|
||||
}
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
// Capture children before first render
|
||||
this.capturedChildren = Array.from(this.childNodes);
|
||||
// Clear children (we'll re-insert them in render)
|
||||
this.innerHTML = "";
|
||||
this.expanded = this.defaultExpanded;
|
||||
}
|
||||
render() {
|
||||
return html `
|
||||
<div>
|
||||
<button
|
||||
@click=${() => {
|
||||
this.expanded = !this.expanded;
|
||||
}}
|
||||
class="flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground transition-colors w-full text-left"
|
||||
>
|
||||
${icon(this.expanded ? ChevronDown : ChevronRight, "sm")}
|
||||
<span>${this.summary}</span>
|
||||
</button>
|
||||
${this.expanded ? html `<div class="mt-2">${this.capturedChildren}</div>` : ""}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
};
|
||||
__decorate([
|
||||
property()
|
||||
], ExpandableSection.prototype, "summary", void 0);
|
||||
__decorate([
|
||||
property({ type: Boolean })
|
||||
], ExpandableSection.prototype, "defaultExpanded", void 0);
|
||||
__decorate([
|
||||
state()
|
||||
], ExpandableSection.prototype, "expanded", void 0);
|
||||
ExpandableSection = __decorate([
|
||||
customElement("expandable-section")
|
||||
], ExpandableSection);
|
||||
export { ExpandableSection };
|
||||
//# sourceMappingURL=ExpandableSection.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ExpandableSection.js","sourceRoot":"","sources":["../../src/components/ExpandableSection.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAC9C,OAAO,EAAE,IAAI,EAAE,UAAU,EAAuB,MAAM,KAAK,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAEnD;;;GAGG;AAEI,IAAM,iBAAiB,GAAvB,MAAM,iBAAkB,SAAQ,UAAU;IAA1C;;QAEuB,oBAAe,GAAG,KAAK,CAAC;QACpC,aAAQ,GAAG,KAAK,CAAC;QAC1B,qBAAgB,GAAW,EAAE,CAAC;IA+BvC,CAAC;IA7BU,gBAAgB;QACzB,OAAO,IAAI,CAAC,CAAC,YAAY;IAC1B,CAAC;IAEQ,iBAAiB;QACzB,KAAK,CAAC,iBAAiB,EAAE,CAAC;QAC1B,uCAAuC;QACvC,IAAI,CAAC,gBAAgB,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACpD,kDAAkD;QAClD,IAAI,CAAC,SAAS,GAAG,EAAE,CAAC;QACpB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,eAAe,CAAC;IACtC,CAAC;IAEQ,MAAM;QACd,OAAO,IAAI,CAAA;;;cAGC,GAAG,EAAE;YACb,IAAI,CAAC,QAAQ,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC;QAChC,CAAC;;;OAGC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,YAAY,EAAE,IAAI,CAAC;aAChD,IAAI,CAAC,OAAO;;MAEnB,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAA,qBAAqB,IAAI,CAAC,gBAAgB,QAAQ,CAAC,CAAC,CAAC,EAAE;;GAE9E,CAAC;IACH,CAAC;CACD,CAAA;AAlCY;IAAX,QAAQ,EAAE;kDAAkB;AACA;IAA5B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;0DAAyB;AACpC;IAAhB,KAAK,EAAE;mDAA0B;AAHtB,iBAAiB;IAD7B,aAAa,CAAC,oBAAoB,CAAC;GACvB,iBAAiB,CAmC7B"}
|
||||
26
apps/macos/Sources/Clawdis/Resources/WebChat/components/Input.d.ts
vendored
Normal file
26
apps/macos/Sources/Clawdis/Resources/WebChat/components/Input.d.ts
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
import { type BaseComponentProps } from "@mariozechner/mini-lit/dist/mini.js";
|
||||
import { type Ref } from "lit/directives/ref.js";
|
||||
export type InputType = "text" | "email" | "password" | "number" | "url" | "tel" | "search";
|
||||
export type InputSize = "sm" | "md" | "lg";
|
||||
export interface InputProps extends BaseComponentProps {
|
||||
type?: InputType;
|
||||
size?: InputSize;
|
||||
value?: string;
|
||||
placeholder?: string;
|
||||
label?: string;
|
||||
error?: string;
|
||||
disabled?: boolean;
|
||||
required?: boolean;
|
||||
name?: string;
|
||||
autocomplete?: string;
|
||||
min?: number;
|
||||
max?: number;
|
||||
step?: number;
|
||||
inputRef?: Ref<HTMLInputElement>;
|
||||
onInput?: (e: Event) => void;
|
||||
onChange?: (e: Event) => void;
|
||||
onKeyDown?: (e: KeyboardEvent) => void;
|
||||
onKeyUp?: (e: KeyboardEvent) => void;
|
||||
}
|
||||
export declare const Input: import("@mariozechner/mini-lit/dist/mini.js").Component<InputProps>;
|
||||
//# sourceMappingURL=Input.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"Input.d.ts","sourceRoot":"","sources":["../../src/components/Input.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,kBAAkB,EAAM,MAAM,qCAAqC,CAAC;AAElF,OAAO,EAAE,KAAK,GAAG,EAAO,MAAM,uBAAuB,CAAC;AAGtD,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,OAAO,GAAG,UAAU,GAAG,QAAQ,GAAG,KAAK,GAAG,KAAK,GAAG,QAAQ,CAAC;AAC5F,MAAM,MAAM,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE3C,MAAM,WAAW,UAAW,SAAQ,kBAAkB;IACrD,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,GAAG,CAAC,gBAAgB,CAAC,CAAC;IACjC,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;IAC7B,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;IAC9B,SAAS,CAAC,EAAE,CAAC,CAAC,EAAE,aAAa,KAAK,IAAI,CAAC;IACvC,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,aAAa,KAAK,IAAI,CAAC;CACrC;AAED,eAAO,MAAM,KAAK,qEAmFjB,CAAC"}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { fc } from "@mariozechner/mini-lit/dist/mini.js";
|
||||
import { html } from "lit";
|
||||
import { ref } from "lit/directives/ref.js";
|
||||
import { i18n } from "../utils/i18n.js";
|
||||
export const Input = fc(({ type = "text", size = "md", value = "", placeholder = "", label = "", error = "", disabled = false, required = false, name = "", autocomplete = "", min, max, step, inputRef, onInput, onChange, onKeyDown, onKeyUp, className = "", }) => {
|
||||
const sizeClasses = {
|
||||
sm: "h-8 px-3 py-1 text-sm",
|
||||
md: "h-9 px-3 py-1 text-sm md:text-sm",
|
||||
lg: "h-10 px-4 py-1 text-base",
|
||||
};
|
||||
const baseClasses = "flex w-full min-w-0 rounded-md border bg-transparent text-foreground shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium";
|
||||
const interactionClasses = "placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground";
|
||||
const focusClasses = "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]";
|
||||
const darkClasses = "dark:bg-input/30";
|
||||
const stateClasses = error
|
||||
? "border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40"
|
||||
: "border-input";
|
||||
const disabledClasses = "disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50";
|
||||
const handleInput = (e) => {
|
||||
onInput?.(e);
|
||||
};
|
||||
const handleChange = (e) => {
|
||||
onChange?.(e);
|
||||
};
|
||||
return html `
|
||||
<div class="flex flex-col gap-1.5 ${className}">
|
||||
${label
|
||||
? html `
|
||||
<label class="text-sm font-medium text-foreground">
|
||||
${label} ${required ? html `<span class="text-destructive">${i18n("*")}</span>` : ""}
|
||||
</label>
|
||||
`
|
||||
: ""}
|
||||
<input
|
||||
type="${type}"
|
||||
class="${baseClasses} ${sizeClasses[size]} ${interactionClasses} ${focusClasses} ${darkClasses} ${stateClasses} ${disabledClasses}"
|
||||
.value=${value}
|
||||
placeholder="${placeholder}"
|
||||
?disabled=${disabled}
|
||||
?required=${required}
|
||||
?aria-invalid=${!!error}
|
||||
name="${name}"
|
||||
autocomplete="${autocomplete}"
|
||||
min="${min ?? ""}"
|
||||
max="${max ?? ""}"
|
||||
step="${step ?? ""}"
|
||||
@input=${handleInput}
|
||||
@change=${handleChange}
|
||||
@keydown=${onKeyDown}
|
||||
@keyup=${onKeyUp}
|
||||
${inputRef ? ref(inputRef) : ""}
|
||||
/>
|
||||
${error ? html `<span class="text-sm text-destructive">${error}</span>` : ""}
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
//# sourceMappingURL=Input.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"Input.js","sourceRoot":"","sources":["../../src/components/Input.ts"],"names":[],"mappings":"AAAA,OAAO,EAA2B,EAAE,EAAE,MAAM,qCAAqC,CAAC;AAClF,OAAO,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAC3B,OAAO,EAAY,GAAG,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AA0BxC,MAAM,CAAC,MAAM,KAAK,GAAG,EAAE,CACtB,CAAC,EACA,IAAI,GAAG,MAAM,EACb,IAAI,GAAG,IAAI,EACX,KAAK,GAAG,EAAE,EACV,WAAW,GAAG,EAAE,EAChB,KAAK,GAAG,EAAE,EACV,KAAK,GAAG,EAAE,EACV,QAAQ,GAAG,KAAK,EAChB,QAAQ,GAAG,KAAK,EAChB,IAAI,GAAG,EAAE,EACT,YAAY,GAAG,EAAE,EACjB,GAAG,EACH,GAAG,EACH,IAAI,EACJ,QAAQ,EACR,OAAO,EACP,QAAQ,EACR,SAAS,EACT,OAAO,EACP,SAAS,GAAG,EAAE,GACd,EAAE,EAAE;IACJ,MAAM,WAAW,GAAG;QACnB,EAAE,EAAE,uBAAuB;QAC3B,EAAE,EAAE,kCAAkC;QACtC,EAAE,EAAE,0BAA0B;KAC9B,CAAC;IAEF,MAAM,WAAW,GAChB,qNAAqN,CAAC;IACvN,MAAM,kBAAkB,GACvB,0FAA0F,CAAC;IAC5F,MAAM,YAAY,GAAG,+EAA+E,CAAC;IACrG,MAAM,WAAW,GAAG,kBAAkB,CAAC;IACvC,MAAM,YAAY,GAAG,KAAK;QACzB,CAAC,CAAC,2FAA2F;QAC7F,CAAC,CAAC,cAAc,CAAC;IAClB,MAAM,eAAe,GAAG,8EAA8E,CAAC;IAEvG,MAAM,WAAW,GAAG,CAAC,CAAQ,EAAE,EAAE;QAChC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;IACd,CAAC,CAAC;IAEF,MAAM,YAAY,GAAG,CAAC,CAAQ,EAAE,EAAE;QACjC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC;IACf,CAAC,CAAC;IAEF,OAAO,IAAI,CAAA;uCAC0B,SAAS;MAE3C,KAAK;QACJ,CAAC,CAAC,IAAI,CAAA;;UAEF,KAAK,IAAI,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAA,kCAAkC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE;;OAEpF;QACD,CAAC,CAAC,EACJ;;aAES,IAAI;cACH,WAAW,IACnB,WAAW,CAAC,IAAI,CACjB,IAAI,kBAAkB,IAAI,YAAY,IAAI,WAAW,IAAI,YAAY,IAAI,eAAe;cAC/E,KAAK;oBACC,WAAW;iBACd,QAAQ;iBACR,QAAQ;qBACJ,CAAC,CAAC,KAAK;aACf,IAAI;qBACI,YAAY;YACrB,GAAG,IAAI,EAAE;YACT,GAAG,IAAI,EAAE;aACR,IAAI,IAAI,EAAE;cACT,WAAW;eACV,YAAY;gBACX,SAAS;cACX,OAAO;OACd,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE;;MAE9B,KAAK,CAAC,CAAC,CAAC,IAAI,CAAA,0CAA0C,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE;;GAE5E,CAAC;AACH,CAAC,CACD,CAAC"}
|
||||
43
apps/macos/Sources/Clawdis/Resources/WebChat/components/MessageEditor.d.ts
vendored
Normal file
43
apps/macos/Sources/Clawdis/Resources/WebChat/components/MessageEditor.d.ts
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
import type { Model } from "@mariozechner/pi-ai";
|
||||
import { LitElement } from "lit";
|
||||
import { type Attachment } from "../utils/attachment-utils.js";
|
||||
import "./AttachmentTile.js";
|
||||
export declare class MessageEditor extends LitElement {
|
||||
private _value;
|
||||
private textareaRef;
|
||||
get value(): string;
|
||||
set value(val: string);
|
||||
isStreaming: boolean;
|
||||
currentModel?: Model<any>;
|
||||
thinkingLevel: "off" | "minimal" | "low" | "medium" | "high";
|
||||
showAttachmentButton: boolean;
|
||||
showModelSelector: boolean;
|
||||
showThinkingSelector: boolean;
|
||||
onInput?: (value: string) => void;
|
||||
onSend?: (input: string, attachments: Attachment[]) => void;
|
||||
onAbort?: () => void;
|
||||
onModelSelect?: () => void;
|
||||
onThinkingChange?: (level: "off" | "minimal" | "low" | "medium" | "high") => void;
|
||||
onFilesChange?: (files: Attachment[]) => void;
|
||||
attachments: Attachment[];
|
||||
maxFiles: number;
|
||||
maxFileSize: number;
|
||||
acceptedTypes: string;
|
||||
processingFiles: boolean;
|
||||
isDragging: boolean;
|
||||
private fileInputRef;
|
||||
protected createRenderRoot(): HTMLElement | DocumentFragment;
|
||||
private handleTextareaInput;
|
||||
private handleKeyDown;
|
||||
private handlePaste;
|
||||
private handleSend;
|
||||
private handleAttachmentClick;
|
||||
private handleFilesSelected;
|
||||
private removeFile;
|
||||
private handleDragOver;
|
||||
private handleDragLeave;
|
||||
private handleDrop;
|
||||
firstUpdated(): void;
|
||||
render(): import("lit-html").TemplateResult<1>;
|
||||
}
|
||||
//# sourceMappingURL=MessageEditor.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"MessageEditor.d.ts","sourceRoot":"","sources":["../../src/components/MessageEditor.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAQ,UAAU,EAAE,MAAM,KAAK,CAAC;AAIvC,OAAO,EAAE,KAAK,UAAU,EAAkB,MAAM,8BAA8B,CAAC;AAE/E,OAAO,qBAAqB,CAAC;AAE7B,qBACa,aAAc,SAAQ,UAAU;IAC5C,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,WAAW,CAAoC;IAEvD,IACI,KAAK,IAIM,MAAM,CAFpB;IAED,IAAI,KAAK,CAAC,GAAG,EAAE,MAAM,EAIpB;IAEW,WAAW,UAAS;IACpB,YAAY,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IAC1B,aAAa,EAAE,KAAK,GAAG,SAAS,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAS;IACrE,oBAAoB,UAAQ;IAC5B,iBAAiB,UAAQ;IACzB,oBAAoB,UAAQ;IAC5B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,KAAK,IAAI,CAAC;IAC5D,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,SAAS,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,KAAK,IAAI,CAAC;IAClF,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,IAAI,CAAC;IAC9C,WAAW,EAAE,UAAU,EAAE,CAAM;IAC/B,QAAQ,SAAM;IACd,WAAW,SAAoB;IAC/B,aAAa,SACqF;IAErG,eAAe,UAAS;IACxB,UAAU,UAAS;IAC5B,OAAO,CAAC,YAAY,CAAiC;cAElC,gBAAgB,IAAI,WAAW,GAAG,gBAAgB;IAIrE,OAAO,CAAC,mBAAmB,CAIzB;IAEF,OAAO,CAAC,aAAa,CAUnB;IAEF,OAAO,CAAC,WAAW,CAgDjB;IAEF,OAAO,CAAC,UAAU,CAEhB;IAEF,OAAO,CAAC,qBAAqB,CAE3B;YAEY,mBAAmB;IAmCjC,OAAO,CAAC,UAAU;IAKlB,OAAO,CAAC,cAAc,CAMpB;IAEF,OAAO,CAAC,eAAe,CAUrB;IAEF,OAAO,CAAC,UAAU,CAkChB;IAEO,YAAY;IAOZ,MAAM;CAqKf"}
|
||||
@@ -0,0 +1,414 @@
|
||||
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 { icon } from "@mariozechner/mini-lit";
|
||||
import { Button } from "@mariozechner/mini-lit/dist/Button.js";
|
||||
import { Select } from "@mariozechner/mini-lit/dist/Select.js";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import { createRef, ref } from "lit/directives/ref.js";
|
||||
import { Brain, Loader2, Paperclip, Send, Sparkles, Square } from "lucide";
|
||||
import { loadAttachment } from "../utils/attachment-utils.js";
|
||||
import { i18n } from "../utils/i18n.js";
|
||||
import "./AttachmentTile.js";
|
||||
let MessageEditor = class MessageEditor extends LitElement {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this._value = "";
|
||||
this.textareaRef = createRef();
|
||||
this.isStreaming = false;
|
||||
this.thinkingLevel = "off";
|
||||
this.showAttachmentButton = true;
|
||||
this.showModelSelector = true;
|
||||
this.showThinkingSelector = true;
|
||||
this.attachments = [];
|
||||
this.maxFiles = 10;
|
||||
this.maxFileSize = 20 * 1024 * 1024; // 20MB
|
||||
this.acceptedTypes = "image/*,application/pdf,.docx,.pptx,.xlsx,.xls,.txt,.md,.json,.xml,.html,.css,.js,.ts,.jsx,.tsx,.yml,.yaml";
|
||||
this.processingFiles = false;
|
||||
this.isDragging = false;
|
||||
this.fileInputRef = createRef();
|
||||
this.handleTextareaInput = (e) => {
|
||||
const textarea = e.target;
|
||||
this.value = textarea.value;
|
||||
this.onInput?.(this.value);
|
||||
};
|
||||
this.handleKeyDown = (e) => {
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
if (!this.isStreaming && !this.processingFiles && (this.value.trim() || this.attachments.length > 0)) {
|
||||
this.handleSend();
|
||||
}
|
||||
}
|
||||
else if (e.key === "Escape" && this.isStreaming) {
|
||||
e.preventDefault();
|
||||
this.onAbort?.();
|
||||
}
|
||||
};
|
||||
this.handlePaste = async (e) => {
|
||||
const items = e.clipboardData?.items;
|
||||
if (!items)
|
||||
return;
|
||||
const imageFiles = [];
|
||||
// Check for image items in clipboard
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items[i];
|
||||
if (item.type.startsWith("image/")) {
|
||||
const file = item.getAsFile();
|
||||
if (file) {
|
||||
imageFiles.push(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
// If we found images, process them
|
||||
if (imageFiles.length > 0) {
|
||||
e.preventDefault(); // Prevent default paste behavior
|
||||
if (imageFiles.length + this.attachments.length > this.maxFiles) {
|
||||
alert(`Maximum ${this.maxFiles} files allowed`);
|
||||
return;
|
||||
}
|
||||
this.processingFiles = true;
|
||||
const newAttachments = [];
|
||||
for (const file of imageFiles) {
|
||||
try {
|
||||
if (file.size > this.maxFileSize) {
|
||||
alert(`Image exceeds maximum size of ${Math.round(this.maxFileSize / 1024 / 1024)}MB`);
|
||||
continue;
|
||||
}
|
||||
const attachment = await loadAttachment(file);
|
||||
newAttachments.push(attachment);
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Error processing pasted image:", error);
|
||||
alert(`Failed to process pasted image: ${String(error)}`);
|
||||
}
|
||||
}
|
||||
this.attachments = [...this.attachments, ...newAttachments];
|
||||
this.onFilesChange?.(this.attachments);
|
||||
this.processingFiles = false;
|
||||
}
|
||||
};
|
||||
this.handleSend = () => {
|
||||
this.onSend?.(this.value, this.attachments);
|
||||
};
|
||||
this.handleAttachmentClick = () => {
|
||||
this.fileInputRef.value?.click();
|
||||
};
|
||||
this.handleDragOver = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (!this.isDragging) {
|
||||
this.isDragging = true;
|
||||
}
|
||||
};
|
||||
this.handleDragLeave = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
// Only set isDragging to false if we're leaving the entire component
|
||||
const rect = e.currentTarget.getBoundingClientRect();
|
||||
const x = e.clientX;
|
||||
const y = e.clientY;
|
||||
if (x <= rect.left || x >= rect.right || y <= rect.top || y >= rect.bottom) {
|
||||
this.isDragging = false;
|
||||
}
|
||||
};
|
||||
this.handleDrop = async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.isDragging = false;
|
||||
const files = Array.from(e.dataTransfer?.files || []);
|
||||
if (files.length === 0)
|
||||
return;
|
||||
if (files.length + this.attachments.length > this.maxFiles) {
|
||||
alert(`Maximum ${this.maxFiles} files allowed`);
|
||||
return;
|
||||
}
|
||||
this.processingFiles = true;
|
||||
const newAttachments = [];
|
||||
for (const file of files) {
|
||||
try {
|
||||
if (file.size > this.maxFileSize) {
|
||||
alert(`${file.name} exceeds maximum size of ${Math.round(this.maxFileSize / 1024 / 1024)}MB`);
|
||||
continue;
|
||||
}
|
||||
const attachment = await loadAttachment(file);
|
||||
newAttachments.push(attachment);
|
||||
}
|
||||
catch (error) {
|
||||
console.error(`Error processing ${file.name}:`, error);
|
||||
alert(`Failed to process ${file.name}: ${String(error)}`);
|
||||
}
|
||||
}
|
||||
this.attachments = [...this.attachments, ...newAttachments];
|
||||
this.onFilesChange?.(this.attachments);
|
||||
this.processingFiles = false;
|
||||
};
|
||||
}
|
||||
get value() {
|
||||
return this._value;
|
||||
}
|
||||
set value(val) {
|
||||
const oldValue = this._value;
|
||||
this._value = val;
|
||||
this.requestUpdate("value", oldValue);
|
||||
}
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
async handleFilesSelected(e) {
|
||||
const input = e.target;
|
||||
const files = Array.from(input.files || []);
|
||||
if (files.length === 0)
|
||||
return;
|
||||
if (files.length + this.attachments.length > this.maxFiles) {
|
||||
alert(`Maximum ${this.maxFiles} files allowed`);
|
||||
input.value = "";
|
||||
return;
|
||||
}
|
||||
this.processingFiles = true;
|
||||
const newAttachments = [];
|
||||
for (const file of files) {
|
||||
try {
|
||||
if (file.size > this.maxFileSize) {
|
||||
alert(`${file.name} exceeds maximum size of ${Math.round(this.maxFileSize / 1024 / 1024)}MB`);
|
||||
continue;
|
||||
}
|
||||
const attachment = await loadAttachment(file);
|
||||
newAttachments.push(attachment);
|
||||
}
|
||||
catch (error) {
|
||||
console.error(`Error processing ${file.name}:`, error);
|
||||
alert(`Failed to process ${file.name}: ${String(error)}`);
|
||||
}
|
||||
}
|
||||
this.attachments = [...this.attachments, ...newAttachments];
|
||||
this.onFilesChange?.(this.attachments);
|
||||
this.processingFiles = false;
|
||||
input.value = ""; // Reset input
|
||||
}
|
||||
removeFile(fileId) {
|
||||
this.attachments = this.attachments.filter((f) => f.id !== fileId);
|
||||
this.onFilesChange?.(this.attachments);
|
||||
}
|
||||
firstUpdated() {
|
||||
const textarea = this.textareaRef.value;
|
||||
if (textarea) {
|
||||
textarea.focus();
|
||||
}
|
||||
}
|
||||
render() {
|
||||
// Check if current model supports thinking/reasoning
|
||||
const model = this.currentModel;
|
||||
const supportsThinking = model?.reasoning === true; // Models with reasoning:true support thinking
|
||||
return html `
|
||||
<div
|
||||
class="bg-card rounded-xl border shadow-sm relative ${this.isDragging ? "border-primary border-2 bg-primary/5" : "border-border"}"
|
||||
@dragover=${this.handleDragOver}
|
||||
@dragleave=${this.handleDragLeave}
|
||||
@drop=${this.handleDrop}
|
||||
>
|
||||
<!-- Drag overlay -->
|
||||
${this.isDragging
|
||||
? html `
|
||||
<div class="absolute inset-0 bg-primary/10 rounded-xl pointer-events-none z-10 flex items-center justify-center">
|
||||
<div class="text-primary font-medium">${i18n("Drop files here")}</div>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
|
||||
<!-- Attachments -->
|
||||
${this.attachments.length > 0
|
||||
? html `
|
||||
<div class="px-4 pt-3 pb-2 flex flex-wrap gap-2">
|
||||
${this.attachments.map((attachment) => html `
|
||||
<attachment-tile
|
||||
.attachment=${attachment}
|
||||
.showDelete=${true}
|
||||
.onDelete=${() => this.removeFile(attachment.id)}
|
||||
></attachment-tile>
|
||||
`)}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
|
||||
<textarea
|
||||
class="w-full bg-transparent p-4 text-foreground placeholder-muted-foreground outline-none resize-none overflow-y-auto"
|
||||
placeholder=${i18n("Type a message...")}
|
||||
rows="1"
|
||||
style="max-height: 200px; field-sizing: content; min-height: 1lh; height: auto;"
|
||||
.value=${this.value}
|
||||
@input=${this.handleTextareaInput}
|
||||
@keydown=${this.handleKeyDown}
|
||||
@paste=${this.handlePaste}
|
||||
${ref(this.textareaRef)}
|
||||
></textarea>
|
||||
|
||||
<!-- Hidden file input -->
|
||||
<input
|
||||
type="file"
|
||||
${ref(this.fileInputRef)}
|
||||
@change=${this.handleFilesSelected}
|
||||
accept=${this.acceptedTypes}
|
||||
multiple
|
||||
style="display: none;"
|
||||
/>
|
||||
|
||||
<!-- Button Row -->
|
||||
<div class="px-2 pb-2 flex items-center justify-between">
|
||||
<!-- Left side - attachment and thinking selector -->
|
||||
<div class="flex gap-2 items-center">
|
||||
${this.showAttachmentButton
|
||||
? this.processingFiles
|
||||
? html `
|
||||
<div class="h-8 w-8 flex items-center justify-center">
|
||||
${icon(Loader2, "sm", "animate-spin text-muted-foreground")}
|
||||
</div>
|
||||
`
|
||||
: html `
|
||||
${Button({
|
||||
variant: "ghost",
|
||||
size: "icon",
|
||||
className: "h-8 w-8",
|
||||
onClick: this.handleAttachmentClick,
|
||||
children: icon(Paperclip, "sm"),
|
||||
})}
|
||||
`
|
||||
: ""}
|
||||
${supportsThinking && this.showThinkingSelector
|
||||
? html `
|
||||
${Select({
|
||||
value: this.thinkingLevel,
|
||||
placeholder: i18n("Off"),
|
||||
options: [
|
||||
{ value: "off", label: i18n("Off"), icon: icon(Brain, "sm") },
|
||||
{ value: "minimal", label: i18n("Minimal"), icon: icon(Brain, "sm") },
|
||||
{ value: "low", label: i18n("Low"), icon: icon(Brain, "sm") },
|
||||
{ value: "medium", label: i18n("Medium"), icon: icon(Brain, "sm") },
|
||||
{ value: "high", label: i18n("High"), icon: icon(Brain, "sm") },
|
||||
],
|
||||
onChange: (value) => {
|
||||
this.onThinkingChange?.(value);
|
||||
},
|
||||
width: "80px",
|
||||
size: "sm",
|
||||
variant: "ghost",
|
||||
fitContent: true,
|
||||
})}
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
|
||||
<!-- Model selector and send on the right -->
|
||||
<div class="flex gap-2 items-center">
|
||||
${this.showModelSelector && this.currentModel
|
||||
? html `
|
||||
${Button({
|
||||
variant: "ghost",
|
||||
size: "sm",
|
||||
onClick: () => {
|
||||
// Focus textarea before opening model selector so focus returns there
|
||||
this.textareaRef.value?.focus();
|
||||
// Wait for next frame to ensure focus takes effect before dialog captures it
|
||||
requestAnimationFrame(() => {
|
||||
this.onModelSelect?.();
|
||||
});
|
||||
},
|
||||
children: html `
|
||||
${icon(Sparkles, "sm")}
|
||||
<span class="ml-1">${this.currentModel.id}</span>
|
||||
`,
|
||||
className: "h-8 text-xs truncate",
|
||||
})}
|
||||
`
|
||||
: ""}
|
||||
${this.isStreaming
|
||||
? html `
|
||||
${Button({
|
||||
variant: "ghost",
|
||||
size: "icon",
|
||||
onClick: this.onAbort,
|
||||
children: icon(Square, "sm"),
|
||||
className: "h-8 w-8",
|
||||
})}
|
||||
`
|
||||
: html `
|
||||
${Button({
|
||||
variant: "ghost",
|
||||
size: "icon",
|
||||
onClick: this.handleSend,
|
||||
disabled: (!this.value.trim() && this.attachments.length === 0) || this.processingFiles,
|
||||
children: html `<div style="transform: rotate(-45deg)">${icon(Send, "sm")}</div>`,
|
||||
className: "h-8 w-8",
|
||||
})}
|
||||
`}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
};
|
||||
__decorate([
|
||||
property()
|
||||
], MessageEditor.prototype, "value", null);
|
||||
__decorate([
|
||||
property()
|
||||
], MessageEditor.prototype, "isStreaming", void 0);
|
||||
__decorate([
|
||||
property()
|
||||
], MessageEditor.prototype, "currentModel", void 0);
|
||||
__decorate([
|
||||
property()
|
||||
], MessageEditor.prototype, "thinkingLevel", void 0);
|
||||
__decorate([
|
||||
property()
|
||||
], MessageEditor.prototype, "showAttachmentButton", void 0);
|
||||
__decorate([
|
||||
property()
|
||||
], MessageEditor.prototype, "showModelSelector", void 0);
|
||||
__decorate([
|
||||
property()
|
||||
], MessageEditor.prototype, "showThinkingSelector", void 0);
|
||||
__decorate([
|
||||
property()
|
||||
], MessageEditor.prototype, "onInput", void 0);
|
||||
__decorate([
|
||||
property()
|
||||
], MessageEditor.prototype, "onSend", void 0);
|
||||
__decorate([
|
||||
property()
|
||||
], MessageEditor.prototype, "onAbort", void 0);
|
||||
__decorate([
|
||||
property()
|
||||
], MessageEditor.prototype, "onModelSelect", void 0);
|
||||
__decorate([
|
||||
property()
|
||||
], MessageEditor.prototype, "onThinkingChange", void 0);
|
||||
__decorate([
|
||||
property()
|
||||
], MessageEditor.prototype, "onFilesChange", void 0);
|
||||
__decorate([
|
||||
property()
|
||||
], MessageEditor.prototype, "attachments", void 0);
|
||||
__decorate([
|
||||
property()
|
||||
], MessageEditor.prototype, "maxFiles", void 0);
|
||||
__decorate([
|
||||
property()
|
||||
], MessageEditor.prototype, "maxFileSize", void 0);
|
||||
__decorate([
|
||||
property()
|
||||
], MessageEditor.prototype, "acceptedTypes", void 0);
|
||||
__decorate([
|
||||
state()
|
||||
], MessageEditor.prototype, "processingFiles", void 0);
|
||||
__decorate([
|
||||
state()
|
||||
], MessageEditor.prototype, "isDragging", void 0);
|
||||
MessageEditor = __decorate([
|
||||
customElement("message-editor")
|
||||
], MessageEditor);
|
||||
export { MessageEditor };
|
||||
//# sourceMappingURL=MessageEditor.js.map
|
||||
File diff suppressed because one or more lines are too long
15
apps/macos/Sources/Clawdis/Resources/WebChat/components/MessageList.d.ts
vendored
Normal file
15
apps/macos/Sources/Clawdis/Resources/WebChat/components/MessageList.d.ts
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
import type { AgentTool } from "@mariozechner/pi-ai";
|
||||
import { LitElement, type TemplateResult } from "lit";
|
||||
import type { AppMessage } from "./Messages.js";
|
||||
export declare class MessageList extends LitElement {
|
||||
messages: AppMessage[];
|
||||
tools: AgentTool[];
|
||||
pendingToolCalls?: Set<string>;
|
||||
isStreaming: boolean;
|
||||
onCostClick?: () => void;
|
||||
protected createRenderRoot(): HTMLElement | DocumentFragment;
|
||||
connectedCallback(): void;
|
||||
private buildRenderItems;
|
||||
render(): TemplateResult<1>;
|
||||
}
|
||||
//# sourceMappingURL=MessageList.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../src/components/MessageList.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,SAAS,EAGT,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAQ,UAAU,EAAE,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAG5D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAGhD,qBAAa,WAAY,SAAQ,UAAU;IACf,QAAQ,EAAE,UAAU,EAAE,CAAM;IAC5B,KAAK,EAAE,SAAS,EAAE,CAAM;IACvB,gBAAgB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC9B,WAAW,EAAE,OAAO,CAAS;IAC1B,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;cAEtC,gBAAgB,IAAI,WAAW,GAAG,gBAAgB;IAI5D,iBAAiB,IAAI,IAAI;IAKlC,OAAO,CAAC,gBAAgB;IAuDf,MAAM;CAUf"}
|
||||
@@ -0,0 +1,104 @@
|
||||
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 { property } from "lit/decorators.js";
|
||||
import { repeat } from "lit/directives/repeat.js";
|
||||
import { renderMessage } from "./message-renderer-registry.js";
|
||||
export class MessageList extends LitElement {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.messages = [];
|
||||
this.tools = [];
|
||||
this.isStreaming = false;
|
||||
}
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.style.display = "block";
|
||||
}
|
||||
buildRenderItems() {
|
||||
// Map tool results by call id for quick lookup
|
||||
const resultByCallId = new Map();
|
||||
for (const message of this.messages) {
|
||||
if (message.role === "toolResult") {
|
||||
resultByCallId.set(message.toolCallId, message);
|
||||
}
|
||||
}
|
||||
const items = [];
|
||||
let index = 0;
|
||||
for (const msg of this.messages) {
|
||||
// Skip artifact messages - they're for session persistence only, not UI display
|
||||
if (msg.role === "artifact") {
|
||||
continue;
|
||||
}
|
||||
// Try custom renderer first
|
||||
const customTemplate = renderMessage(msg);
|
||||
if (customTemplate) {
|
||||
items.push({ key: `msg:${index}`, template: customTemplate });
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
// Fall back to built-in renderers
|
||||
if (msg.role === "user") {
|
||||
items.push({
|
||||
key: `msg:${index}`,
|
||||
template: html `<user-message .message=${msg}></user-message>`,
|
||||
});
|
||||
index++;
|
||||
}
|
||||
else if (msg.role === "assistant") {
|
||||
const amsg = msg;
|
||||
items.push({
|
||||
key: `msg:${index}`,
|
||||
template: html `<assistant-message
|
||||
.message=${amsg}
|
||||
.tools=${this.tools}
|
||||
.isStreaming=${false}
|
||||
.pendingToolCalls=${this.pendingToolCalls}
|
||||
.toolResultsById=${resultByCallId}
|
||||
.hideToolCalls=${false}
|
||||
.onCostClick=${this.onCostClick}
|
||||
></assistant-message>`,
|
||||
});
|
||||
index++;
|
||||
}
|
||||
else {
|
||||
// Skip standalone toolResult messages; they are rendered via paired tool-message above
|
||||
// Skip unknown roles
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
render() {
|
||||
const items = this.buildRenderItems();
|
||||
return html `<div class="flex flex-col gap-3">
|
||||
${repeat(items, (it) => it.key, (it) => it.template)}
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
__decorate([
|
||||
property({ type: Array })
|
||||
], MessageList.prototype, "messages", void 0);
|
||||
__decorate([
|
||||
property({ type: Array })
|
||||
], MessageList.prototype, "tools", void 0);
|
||||
__decorate([
|
||||
property({ type: Object })
|
||||
], MessageList.prototype, "pendingToolCalls", void 0);
|
||||
__decorate([
|
||||
property({ type: Boolean })
|
||||
], MessageList.prototype, "isStreaming", void 0);
|
||||
__decorate([
|
||||
property({ attribute: false })
|
||||
], MessageList.prototype, "onCostClick", void 0);
|
||||
// Register custom element
|
||||
if (!customElements.get("message-list")) {
|
||||
customElements.define("message-list", MessageList);
|
||||
}
|
||||
//# sourceMappingURL=MessageList.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"MessageList.js","sourceRoot":"","sources":["../../src/components/MessageList.ts"],"names":[],"mappings":"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"}
|
||||
64
apps/macos/Sources/Clawdis/Resources/WebChat/components/Messages.d.ts
vendored
Normal file
64
apps/macos/Sources/Clawdis/Resources/WebChat/components/Messages.d.ts
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
import type { AgentTool, AssistantMessage as AssistantMessageType, ToolCall, ToolResultMessage as ToolResultMessageType, UserMessage as UserMessageType } from "@mariozechner/pi-ai";
|
||||
import { LitElement, type TemplateResult } from "lit";
|
||||
import type { Attachment } from "../utils/attachment-utils.js";
|
||||
import "./ThinkingBlock.js";
|
||||
export type UserMessageWithAttachments = UserMessageType & {
|
||||
attachments?: Attachment[];
|
||||
};
|
||||
export interface ArtifactMessage {
|
||||
role: "artifact";
|
||||
action: "create" | "update" | "delete";
|
||||
filename: string;
|
||||
content?: string;
|
||||
title?: string;
|
||||
timestamp: string;
|
||||
}
|
||||
type BaseMessage = AssistantMessageType | UserMessageWithAttachments | ToolResultMessageType | ArtifactMessage;
|
||||
export interface CustomMessages {
|
||||
}
|
||||
export type AppMessage = BaseMessage | CustomMessages[keyof CustomMessages];
|
||||
export declare class UserMessage extends LitElement {
|
||||
message: UserMessageWithAttachments;
|
||||
protected createRenderRoot(): HTMLElement | DocumentFragment;
|
||||
connectedCallback(): void;
|
||||
render(): TemplateResult<1>;
|
||||
}
|
||||
export declare class AssistantMessage extends LitElement {
|
||||
message: AssistantMessageType;
|
||||
tools?: AgentTool<any>[];
|
||||
pendingToolCalls?: Set<string>;
|
||||
hideToolCalls: boolean;
|
||||
toolResultsById?: Map<string, ToolResultMessageType>;
|
||||
isStreaming: boolean;
|
||||
onCostClick?: () => void;
|
||||
protected createRenderRoot(): HTMLElement | DocumentFragment;
|
||||
connectedCallback(): void;
|
||||
render(): TemplateResult<1>;
|
||||
}
|
||||
export declare class ToolMessageDebugView extends LitElement {
|
||||
callArgs: any;
|
||||
result?: ToolResultMessageType;
|
||||
hasResult: boolean;
|
||||
protected createRenderRoot(): HTMLElement | DocumentFragment;
|
||||
connectedCallback(): void;
|
||||
private pretty;
|
||||
render(): TemplateResult<1>;
|
||||
}
|
||||
export declare class ToolMessage extends LitElement {
|
||||
toolCall: ToolCall;
|
||||
tool?: AgentTool<any>;
|
||||
result?: ToolResultMessageType;
|
||||
pending: boolean;
|
||||
aborted: boolean;
|
||||
isStreaming: boolean;
|
||||
protected createRenderRoot(): HTMLElement | DocumentFragment;
|
||||
connectedCallback(): void;
|
||||
render(): TemplateResult;
|
||||
}
|
||||
export declare class AbortedMessage extends LitElement {
|
||||
protected createRenderRoot(): HTMLElement | DocumentFragment;
|
||||
connectedCallback(): void;
|
||||
protected render(): unknown;
|
||||
}
|
||||
export {};
|
||||
//# sourceMappingURL=Messages.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"Messages.d.ts","sourceRoot":"","sources":["../../src/components/Messages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,SAAS,EACT,gBAAgB,IAAI,oBAAoB,EACxC,QAAQ,EACR,iBAAiB,IAAI,qBAAqB,EAC1C,WAAW,IAAI,eAAe,EAC9B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAQ,UAAU,EAAE,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAG5D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAG/D,OAAO,oBAAoB,CAAC;AAE5B,MAAM,MAAM,0BAA0B,GAAG,eAAe,GAAG;IAAE,WAAW,CAAC,EAAE,UAAU,EAAE,CAAA;CAAE,CAAC;AAG1F,MAAM,WAAW,eAAe;IAC/B,IAAI,EAAE,UAAU,CAAC;IACjB,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CAClB;AAGD,KAAK,WAAW,GAAG,oBAAoB,GAAG,0BAA0B,GAAG,qBAAqB,GAAG,eAAe,CAAC;AAS/G,MAAM,WAAW,cAAc;CAE9B;AAGD,MAAM,MAAM,UAAU,GAAG,WAAW,GAAG,cAAc,CAAC,MAAM,cAAc,CAAC,CAAC;AAE5E,qBACa,WAAY,SAAQ,UAAU;IACd,OAAO,EAAG,0BAA0B,CAAC;cAE9C,gBAAgB,IAAI,WAAW,GAAG,gBAAgB;IAI5D,iBAAiB,IAAI,IAAI;IAKzB,MAAM;CAyBf;AAED,qBACa,gBAAiB,SAAQ,UAAU;IACnB,OAAO,EAAG,oBAAoB,CAAC;IAChC,KAAK,CAAC,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC;IACxB,gBAAgB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC9B,aAAa,UAAS;IACvB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;IACpD,WAAW,EAAE,OAAO,CAAS;IAC1B,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;cAEtC,gBAAgB,IAAI,WAAW,GAAG,gBAAgB;IAI5D,iBAAiB,IAAI,IAAI;IAKzB,MAAM;CA2Df;AAED,qBACa,oBAAqB,SAAQ,UAAU;IACvB,QAAQ,EAAE,GAAG,CAAC;IACd,MAAM,CAAC,EAAE,qBAAqB,CAAC;IAC9B,SAAS,EAAE,OAAO,CAAS;cAErC,gBAAgB,IAAI,WAAW,GAAG,gBAAgB;IAI5D,iBAAiB,IAAI,IAAI;IAKlC,OAAO,CAAC,MAAM;IAYL,MAAM;CA2Bf;AAED,qBACa,WAAY,SAAQ,UAAU;IACd,QAAQ,EAAG,QAAQ,CAAC;IACpB,IAAI,CAAC,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC;IACtB,MAAM,CAAC,EAAE,qBAAqB,CAAC;IAC9B,OAAO,EAAE,OAAO,CAAS;IACzB,OAAO,EAAE,OAAO,CAAS;IACzB,WAAW,EAAE,OAAO,CAAS;cAEvC,gBAAgB,IAAI,WAAW,GAAG,gBAAgB;IAI5D,iBAAiB,IAAI,IAAI;IAKzB,MAAM;CAiCf;AAED,qBACa,cAAe,SAAQ,UAAU;cAC1B,gBAAgB,IAAI,WAAW,GAAG,gBAAgB;IAI5D,iBAAiB,IAAI,IAAI;cAKf,MAAM,IAAI,OAAO;CAGpC"}
|
||||
@@ -0,0 +1,275 @@
|
||||
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 "./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="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
|
||||
File diff suppressed because one or more lines are too long
16
apps/macos/Sources/Clawdis/Resources/WebChat/components/ProviderKeyInput.d.ts
vendored
Normal file
16
apps/macos/Sources/Clawdis/Resources/WebChat/components/ProviderKeyInput.d.ts
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
import { LitElement } from "lit";
|
||||
export declare class ProviderKeyInput extends LitElement {
|
||||
provider: string;
|
||||
private keyInput;
|
||||
private testing;
|
||||
private failed;
|
||||
private hasKey;
|
||||
private inputChanged;
|
||||
protected createRenderRoot(): this;
|
||||
connectedCallback(): Promise<void>;
|
||||
private checkKeyStatus;
|
||||
private testApiKey;
|
||||
private saveKey;
|
||||
render(): import("lit-html").TemplateResult<1>;
|
||||
}
|
||||
//# sourceMappingURL=ProviderKeyInput.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ProviderKeyInput.d.ts","sourceRoot":"","sources":["../../src/components/ProviderKeyInput.ts"],"names":[],"mappings":"AAIA,OAAO,EAAQ,UAAU,EAAE,MAAM,KAAK,CAAC;AAkBvC,qBACa,gBAAiB,SAAQ,UAAU;IACnC,QAAQ,SAAM;IACjB,OAAO,CAAC,QAAQ,CAAM;IACtB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,YAAY,CAAS;IAEtC,SAAS,CAAC,gBAAgB;IAIX,iBAAiB;YAKlB,cAAc;YASd,UAAU;YAgCV,OAAO;IAiCrB,MAAM;CAqCN"}
|
||||
@@ -0,0 +1,170 @@
|
||||
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 { i18n } from "@mariozechner/mini-lit";
|
||||
import { Badge } from "@mariozechner/mini-lit/dist/Badge.js";
|
||||
import { Button } from "@mariozechner/mini-lit/dist/Button.js";
|
||||
import { complete, getModel } from "@mariozechner/pi-ai";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import { getAppStorage } from "../storage/app-storage.js";
|
||||
import { applyProxyIfNeeded } from "../utils/proxy-utils.js";
|
||||
import { Input } from "./Input.js";
|
||||
// Test models for each provider
|
||||
const TEST_MODELS = {
|
||||
anthropic: "claude-3-5-haiku-20241022",
|
||||
openai: "gpt-4o-mini",
|
||||
google: "gemini-2.5-flash",
|
||||
groq: "openai/gpt-oss-20b",
|
||||
openrouter: "z-ai/glm-4.6",
|
||||
cerebras: "gpt-oss-120b",
|
||||
xai: "grok-4-fast-non-reasoning",
|
||||
zai: "glm-4.5-air",
|
||||
};
|
||||
let ProviderKeyInput = class ProviderKeyInput extends LitElement {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.provider = "";
|
||||
this.keyInput = "";
|
||||
this.testing = false;
|
||||
this.failed = false;
|
||||
this.hasKey = false;
|
||||
this.inputChanged = false;
|
||||
}
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
async connectedCallback() {
|
||||
super.connectedCallback();
|
||||
await this.checkKeyStatus();
|
||||
}
|
||||
async checkKeyStatus() {
|
||||
try {
|
||||
const key = await getAppStorage().providerKeys.get(this.provider);
|
||||
this.hasKey = !!key;
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Failed to check key status:", error);
|
||||
}
|
||||
}
|
||||
async testApiKey(provider, apiKey) {
|
||||
try {
|
||||
const modelId = TEST_MODELS[provider];
|
||||
// Returning true here for Ollama and friends. Can' know which model to use for testing
|
||||
if (!modelId)
|
||||
return true;
|
||||
let model = getModel(provider, modelId);
|
||||
if (!model)
|
||||
return false;
|
||||
// Get proxy URL from settings (if available)
|
||||
const proxyEnabled = await getAppStorage().settings.get("proxy.enabled");
|
||||
const proxyUrl = await getAppStorage().settings.get("proxy.url");
|
||||
// Apply proxy only if this provider/key combination requires it
|
||||
model = applyProxyIfNeeded(model, apiKey, proxyEnabled ? proxyUrl || undefined : undefined);
|
||||
const context = {
|
||||
messages: [{ role: "user", content: "Reply with: ok", timestamp: Date.now() }],
|
||||
};
|
||||
const result = await complete(model, context, {
|
||||
apiKey,
|
||||
maxTokens: 200,
|
||||
});
|
||||
return result.stopReason === "stop";
|
||||
}
|
||||
catch (error) {
|
||||
console.error(`API key test failed for ${provider}:`, error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
async saveKey() {
|
||||
if (!this.keyInput)
|
||||
return;
|
||||
this.testing = true;
|
||||
this.failed = false;
|
||||
const success = await this.testApiKey(this.provider, this.keyInput);
|
||||
this.testing = false;
|
||||
if (success) {
|
||||
try {
|
||||
await getAppStorage().providerKeys.set(this.provider, this.keyInput);
|
||||
this.hasKey = true;
|
||||
this.inputChanged = false;
|
||||
this.requestUpdate();
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Failed to save API key:", error);
|
||||
this.failed = true;
|
||||
setTimeout(() => {
|
||||
this.failed = false;
|
||||
this.requestUpdate();
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.failed = true;
|
||||
setTimeout(() => {
|
||||
this.failed = false;
|
||||
this.requestUpdate();
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
render() {
|
||||
return html `
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm font-medium capitalize text-foreground">${this.provider}</span>
|
||||
${this.testing
|
||||
? Badge({ children: i18n("Testing..."), variant: "secondary" })
|
||||
: this.hasKey
|
||||
? html `<span class="text-green-600 dark:text-green-400">✓</span>`
|
||||
: ""}
|
||||
${this.failed ? Badge({ children: i18n("✗ Invalid"), variant: "destructive" }) : ""}
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
${Input({
|
||||
type: "password",
|
||||
placeholder: this.hasKey ? "••••••••••••" : i18n("Enter API key"),
|
||||
value: this.keyInput,
|
||||
onInput: (e) => {
|
||||
this.keyInput = e.target.value;
|
||||
this.inputChanged = true;
|
||||
this.requestUpdate();
|
||||
},
|
||||
className: "flex-1",
|
||||
})}
|
||||
${Button({
|
||||
onClick: () => this.saveKey(),
|
||||
variant: "default",
|
||||
size: "sm",
|
||||
disabled: !this.keyInput || this.testing || (this.hasKey && !this.inputChanged),
|
||||
children: i18n("Save"),
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
};
|
||||
__decorate([
|
||||
property()
|
||||
], ProviderKeyInput.prototype, "provider", void 0);
|
||||
__decorate([
|
||||
state()
|
||||
], ProviderKeyInput.prototype, "keyInput", void 0);
|
||||
__decorate([
|
||||
state()
|
||||
], ProviderKeyInput.prototype, "testing", void 0);
|
||||
__decorate([
|
||||
state()
|
||||
], ProviderKeyInput.prototype, "failed", void 0);
|
||||
__decorate([
|
||||
state()
|
||||
], ProviderKeyInput.prototype, "hasKey", void 0);
|
||||
__decorate([
|
||||
state()
|
||||
], ProviderKeyInput.prototype, "inputChanged", void 0);
|
||||
ProviderKeyInput = __decorate([
|
||||
customElement("provider-key-input")
|
||||
], ProviderKeyInput);
|
||||
export { ProviderKeyInput };
|
||||
//# sourceMappingURL=ProviderKeyInput.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ProviderKeyInput.js","sourceRoot":"","sources":["../../src/components/ProviderKeyInput.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAC9C,OAAO,EAAE,KAAK,EAAE,MAAM,sCAAsC,CAAC;AAC7D,OAAO,EAAE,MAAM,EAAE,MAAM,uCAAuC,CAAC;AAC/D,OAAO,EAAgB,QAAQ,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACvE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,KAAK,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,gCAAgC;AAChC,MAAM,WAAW,GAA2B;IAC3C,SAAS,EAAE,2BAA2B;IACtC,MAAM,EAAE,aAAa;IACrB,MAAM,EAAE,kBAAkB;IAC1B,IAAI,EAAE,oBAAoB;IAC1B,UAAU,EAAE,cAAc;IAC1B,QAAQ,EAAE,cAAc;IACxB,GAAG,EAAE,2BAA2B;IAChC,GAAG,EAAE,aAAa;CAClB,CAAC;AAGK,IAAM,gBAAgB,GAAtB,MAAM,gBAAiB,SAAQ,UAAU;IAAzC;;QACM,aAAQ,GAAG,EAAE,CAAC;QACT,aAAQ,GAAG,EAAE,CAAC;QACd,YAAO,GAAG,KAAK,CAAC;QAChB,WAAM,GAAG,KAAK,CAAC;QACf,WAAM,GAAG,KAAK,CAAC;QACf,iBAAY,GAAG,KAAK,CAAC;IA0HvC,CAAC;IAxHU,gBAAgB;QACzB,OAAO,IAAI,CAAC;IACb,CAAC;IAEQ,KAAK,CAAC,iBAAiB;QAC/B,KAAK,CAAC,iBAAiB,EAAE,CAAC;QAC1B,MAAM,IAAI,CAAC,cAAc,EAAE,CAAC;IAC7B,CAAC;IAEO,KAAK,CAAC,cAAc;QAC3B,IAAI,CAAC;YACJ,MAAM,GAAG,GAAG,MAAM,aAAa,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAClE,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC;QACrB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,OAAO,CAAC,KAAK,CAAC,6BAA6B,EAAE,KAAK,CAAC,CAAC;QACrD,CAAC;IACF,CAAC;IAEO,KAAK,CAAC,UAAU,CAAC,QAAgB,EAAE,MAAc;QACxD,IAAI,CAAC;YACJ,MAAM,OAAO,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;YACtC,uFAAuF;YACvF,IAAI,CAAC,OAAO;gBAAE,OAAO,IAAI,CAAC;YAE1B,IAAI,KAAK,GAAG,QAAQ,CAAC,QAAe,EAAE,OAAO,CAAC,CAAC;YAC/C,IAAI,CAAC,KAAK;gBAAE,OAAO,KAAK,CAAC;YAEzB,6CAA6C;YAC7C,MAAM,YAAY,GAAG,MAAM,aAAa,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAU,eAAe,CAAC,CAAC;YAClF,MAAM,QAAQ,GAAG,MAAM,aAAa,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAS,WAAW,CAAC,CAAC;YAEzE,gEAAgE;YAChE,KAAK,GAAG,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,QAAQ,IAAI,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;YAE5F,MAAM,OAAO,GAAY;gBACxB,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;aAC9E,CAAC;YAEF,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE;gBAC7C,MAAM;gBACN,SAAS,EAAE,GAAG;aACP,CAAC,CAAC;YAEV,OAAO,MAAM,CAAC,UAAU,KAAK,MAAM,CAAC;QACrC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,OAAO,CAAC,KAAK,CAAC,2BAA2B,QAAQ,GAAG,EAAE,KAAK,CAAC,CAAC;YAC7D,OAAO,KAAK,CAAC;QACd,CAAC;IACF,CAAC;IAEO,KAAK,CAAC,OAAO;QACpB,IAAI,CAAC,IAAI,CAAC,QAAQ;YAAE,OAAO;QAE3B,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;QAEpB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;QAEpE,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;QAErB,IAAI,OAAO,EAAE,CAAC;YACb,IAAI,CAAC;gBACJ,MAAM,aAAa,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;gBACrE,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;gBACnB,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC;gBAC1B,IAAI,CAAC,aAAa,EAAE,CAAC;YACtB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBAChB,OAAO,CAAC,KAAK,CAAC,yBAAyB,EAAE,KAAK,CAAC,CAAC;gBAChD,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;gBACnB,UAAU,CAAC,GAAG,EAAE;oBACf,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;oBACpB,IAAI,CAAC,aAAa,EAAE,CAAC;gBACtB,CAAC,EAAE,IAAI,CAAC,CAAC;YACV,CAAC;QACF,CAAC;aAAM,CAAC;YACP,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;YACnB,UAAU,CAAC,GAAG,EAAE;gBACf,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;gBACpB,IAAI,CAAC,aAAa,EAAE,CAAC;YACtB,CAAC,EAAE,IAAI,CAAC,CAAC;QACV,CAAC;IACF,CAAC;IAED,MAAM;QACL,OAAO,IAAI,CAAA;;;oEAGuD,IAAI,CAAC,QAAQ;OAE3E,IAAI,CAAC,OAAO;YACX,CAAC,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,YAAY,CAAC,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;YAC/D,CAAC,CAAC,IAAI,CAAC,MAAM;gBACZ,CAAC,CAAC,IAAI,CAAA,2DAA2D;gBACjE,CAAC,CAAC,EACL;OACE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE;;;OAGjF,KAAK,CAAC;YACP,IAAI,EAAE,UAAU;YAChB,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC;YACjE,KAAK,EAAE,IAAI,CAAC,QAAQ;YACpB,OAAO,EAAE,CAAC,CAAQ,EAAE,EAAE;gBACrB,IAAI,CAAC,QAAQ,GAAI,CAAC,CAAC,MAA2B,CAAC,KAAK,CAAC;gBACrD,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;gBACzB,IAAI,CAAC,aAAa,EAAE,CAAC;YACtB,CAAC;YACD,SAAS,EAAE,QAAQ;SACnB,CAAC;OACA,MAAM,CAAC;YACR,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE;YAC7B,OAAO,EAAE,SAAS;YAClB,IAAI,EAAE,IAAI;YACV,QAAQ,EAAE,CAAC,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC;YAC/E,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC;SACtB,CAAC;;;GAGJ,CAAC;IACH,CAAC;CACD,CAAA;AA/HY;IAAX,QAAQ,EAAE;kDAAe;AACT;IAAhB,KAAK,EAAE;kDAAuB;AACd;IAAhB,KAAK,EAAE;iDAAyB;AAChB;IAAhB,KAAK,EAAE;gDAAwB;AACf;IAAhB,KAAK,EAAE;gDAAwB;AACf;IAAhB,KAAK,EAAE;sDAA8B;AAN1B,gBAAgB;IAD5B,aAAa,CAAC,oBAAoB,CAAC;GACvB,gBAAgB,CAgI5B"}
|
||||
85
apps/macos/Sources/Clawdis/Resources/WebChat/components/SandboxedIframe.d.ts
vendored
Normal file
85
apps/macos/Sources/Clawdis/Resources/WebChat/components/SandboxedIframe.d.ts
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
import { LitElement } from "lit";
|
||||
import { type MessageConsumer } from "./sandbox/RuntimeMessageRouter.js";
|
||||
import type { SandboxRuntimeProvider } from "./sandbox/SandboxRuntimeProvider.js";
|
||||
export interface SandboxFile {
|
||||
fileName: string;
|
||||
content: string | Uint8Array;
|
||||
mimeType: string;
|
||||
}
|
||||
export interface SandboxResult {
|
||||
success: boolean;
|
||||
console: Array<{
|
||||
type: string;
|
||||
text: string;
|
||||
}>;
|
||||
files?: SandboxFile[];
|
||||
error?: {
|
||||
message: string;
|
||||
stack: string;
|
||||
};
|
||||
returnValue?: any;
|
||||
}
|
||||
/**
|
||||
* Function that returns the URL to the sandbox HTML file.
|
||||
* Used in browser extensions to load sandbox.html via chrome.runtime.getURL().
|
||||
*/
|
||||
export type SandboxUrlProvider = () => string;
|
||||
/**
|
||||
* Configuration for prepareHtmlDocument
|
||||
*/
|
||||
export interface PrepareHtmlOptions {
|
||||
/** True if this is an HTML artifact (inject into existing HTML), false if REPL (wrap in HTML) */
|
||||
isHtmlArtifact: boolean;
|
||||
/** True if this is a standalone download (no runtime bridge, no navigation interceptor) */
|
||||
isStandalone?: boolean;
|
||||
}
|
||||
export declare class SandboxIframe extends LitElement {
|
||||
private iframe?;
|
||||
/**
|
||||
* Optional: Provide a function that returns the sandbox HTML URL.
|
||||
* If provided, the iframe will use this URL instead of srcdoc.
|
||||
* This is required for browser extensions with strict CSP.
|
||||
*/
|
||||
sandboxUrlProvider?: SandboxUrlProvider;
|
||||
createRenderRoot(): this;
|
||||
connectedCallback(): void;
|
||||
disconnectedCallback(): void;
|
||||
/**
|
||||
* Load HTML content into sandbox and keep it displayed (for HTML artifacts)
|
||||
* @param sandboxId Unique ID
|
||||
* @param htmlContent Full HTML content
|
||||
* @param providers Runtime providers to inject
|
||||
* @param consumers Message consumers to register (optional)
|
||||
*/
|
||||
loadContent(sandboxId: string, htmlContent: string, providers?: SandboxRuntimeProvider[], consumers?: MessageConsumer[]): void;
|
||||
private loadViaSandboxUrl;
|
||||
private loadViaSrcdoc;
|
||||
/**
|
||||
* Execute code in sandbox
|
||||
* @param sandboxId Unique ID for this execution
|
||||
* @param code User code (plain JS for REPL, or full HTML for artifacts)
|
||||
* @param providers Runtime providers to inject
|
||||
* @param consumers Additional message consumers (optional, execute has its own internal consumer)
|
||||
* @param signal Abort signal
|
||||
* @returns Promise resolving to execution result
|
||||
*/
|
||||
execute(sandboxId: string, code: string, providers?: SandboxRuntimeProvider[], consumers?: MessageConsumer[], signal?: AbortSignal, isHtmlArtifact?: boolean): Promise<SandboxResult>;
|
||||
/**
|
||||
* Validate HTML using DOMParser - returns error message if invalid, null if valid
|
||||
* Note: JavaScript syntax validation is done in sandbox.js to avoid CSP restrictions
|
||||
*/
|
||||
private validateHtml;
|
||||
/**
|
||||
* Prepare complete HTML document with runtime + user code
|
||||
* PUBLIC so HtmlArtifact can use it for download button
|
||||
*/
|
||||
prepareHtmlDocument(sandboxId: string, userCode: string, providers?: SandboxRuntimeProvider[], options?: PrepareHtmlOptions): string;
|
||||
/**
|
||||
* Generate runtime script from providers
|
||||
* @param sandboxId Unique sandbox ID
|
||||
* @param providers Runtime providers
|
||||
* @param isStandalone If true, skip runtime bridge and navigation interceptor (for standalone downloads)
|
||||
*/
|
||||
private getRuntimeScript;
|
||||
}
|
||||
//# sourceMappingURL=SandboxedIframe.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"SandboxedIframe.d.ts","sourceRoot":"","sources":["../../src/components/SandboxedIframe.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,KAAK,CAAC;AAIjC,OAAO,EAAE,KAAK,eAAe,EAA0B,MAAM,mCAAmC,CAAC;AACjG,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAElF,MAAM,WAAW,WAAW;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/C,KAAK,CAAC,EAAE,WAAW,EAAE,CAAC;IACtB,KAAK,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3C,WAAW,CAAC,EAAE,GAAG,CAAC;CAClB;AAED;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG,MAAM,MAAM,CAAC;AAE9C;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAClC,iGAAiG;IACjG,cAAc,EAAE,OAAO,CAAC;IACxB,2FAA2F;IAC3F,YAAY,CAAC,EAAE,OAAO,CAAC;CACvB;AAWD,qBACa,aAAc,SAAQ,UAAU;IAC5C,OAAO,CAAC,MAAM,CAAC,CAAoB;IAEnC;;;;OAIG;IAC6B,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IAExE,gBAAgB;IAIP,iBAAiB;IAIjB,oBAAoB;IAQ7B;;;;;;OAMG;IACI,WAAW,CACjB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,SAAS,GAAE,sBAAsB,EAAO,EACxC,SAAS,GAAE,eAAe,EAAO,GAC/B,IAAI;IAkDP,OAAO,CAAC,iBAAiB;IAuEzB,OAAO,CAAC,aAAa;IAyBrB;;;;;;;;OAQG;IACU,OAAO,CACnB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,SAAS,GAAE,sBAAsB,EAAO,EACxC,SAAS,GAAE,eAAe,EAAO,EACjC,MAAM,CAAC,EAAE,WAAW,EACpB,cAAc,GAAE,OAAe,GAC7B,OAAO,CAAC,aAAa,CAAC;IAmKzB;;;OAGG;IACH,OAAO,CAAC,YAAY;IAiBpB;;;OAGG;IACI,mBAAmB,CACzB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,SAAS,GAAE,sBAAsB,EAAO,EACxC,OAAO,CAAC,EAAE,kBAAkB,GAC1B,MAAM;IAmFT;;;;;OAKG;IACH,OAAO,CAAC,gBAAgB;CAgGxB"}
|
||||
@@ -0,0 +1,511 @@
|
||||
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 { LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { ConsoleRuntimeProvider } from "./sandbox/ConsoleRuntimeProvider.js";
|
||||
import { RuntimeMessageBridge } from "./sandbox/RuntimeMessageBridge.js";
|
||||
import { RUNTIME_MESSAGE_ROUTER } from "./sandbox/RuntimeMessageRouter.js";
|
||||
/**
|
||||
* Escape HTML special sequences in code to prevent premature tag closure
|
||||
* @param code Code that will be injected into <script> tags
|
||||
* @returns Escaped code safe for injection
|
||||
*/
|
||||
function escapeScriptContent(code) {
|
||||
return code.replace(/<\/script/gi, "<\\/script");
|
||||
}
|
||||
let SandboxIframe = class SandboxIframe extends LitElement {
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
}
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
// Note: We don't unregister the sandbox here for loadContent() mode
|
||||
// because the caller (HtmlArtifact) owns the sandbox lifecycle.
|
||||
// For execute() mode, the sandbox is unregistered in the cleanup function.
|
||||
this.iframe?.remove();
|
||||
}
|
||||
/**
|
||||
* Load HTML content into sandbox and keep it displayed (for HTML artifacts)
|
||||
* @param sandboxId Unique ID
|
||||
* @param htmlContent Full HTML content
|
||||
* @param providers Runtime providers to inject
|
||||
* @param consumers Message consumers to register (optional)
|
||||
*/
|
||||
loadContent(sandboxId, htmlContent, providers = [], consumers = []) {
|
||||
// Unregister previous sandbox if exists
|
||||
try {
|
||||
RUNTIME_MESSAGE_ROUTER.unregisterSandbox(sandboxId);
|
||||
}
|
||||
catch {
|
||||
// Sandbox might not exist, that's ok
|
||||
}
|
||||
providers = [new ConsoleRuntimeProvider(), ...providers];
|
||||
RUNTIME_MESSAGE_ROUTER.registerSandbox(sandboxId, providers, consumers);
|
||||
// loadContent is always used for HTML artifacts (not standalone)
|
||||
const completeHtml = this.prepareHtmlDocument(sandboxId, htmlContent, providers, {
|
||||
isHtmlArtifact: true,
|
||||
isStandalone: false,
|
||||
});
|
||||
// Validate HTML before loading
|
||||
const validationError = this.validateHtml(completeHtml);
|
||||
if (validationError) {
|
||||
console.error("HTML validation failed:", validationError);
|
||||
// Show error in iframe instead of crashing
|
||||
this.iframe?.remove();
|
||||
this.iframe = document.createElement("iframe");
|
||||
this.iframe.style.cssText = "width: 100%; height: 100%; border: none;";
|
||||
this.iframe.srcdoc = `
|
||||
<html>
|
||||
<body style="font-family: monospace; padding: 20px; background: #fff; color: #000;">
|
||||
<h3 style="color: #c00;">HTML Validation Error</h3>
|
||||
<pre style="background: #f5f5f5; padding: 10px; border-radius: 4px; overflow-x: auto; white-space: pre-wrap;">${validationError}</pre>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
this.appendChild(this.iframe);
|
||||
return;
|
||||
}
|
||||
// Remove previous iframe if exists
|
||||
this.iframe?.remove();
|
||||
if (this.sandboxUrlProvider) {
|
||||
// Browser extension mode: use sandbox.html with postMessage
|
||||
this.loadViaSandboxUrl(sandboxId, completeHtml);
|
||||
}
|
||||
else {
|
||||
// Web mode: use srcdoc
|
||||
this.loadViaSrcdoc(sandboxId, completeHtml);
|
||||
}
|
||||
}
|
||||
loadViaSandboxUrl(sandboxId, completeHtml) {
|
||||
// Create iframe pointing to sandbox URL
|
||||
this.iframe = document.createElement("iframe");
|
||||
this.iframe.sandbox.add("allow-scripts");
|
||||
this.iframe.sandbox.add("allow-modals");
|
||||
this.iframe.style.width = "100%";
|
||||
this.iframe.style.height = "100%";
|
||||
this.iframe.style.border = "none";
|
||||
this.iframe.src = this.sandboxUrlProvider();
|
||||
// Update router with iframe reference BEFORE appending to DOM
|
||||
RUNTIME_MESSAGE_ROUTER.setSandboxIframe(sandboxId, this.iframe);
|
||||
// Listen for open-external-url messages from iframe
|
||||
const externalUrlHandler = (e) => {
|
||||
if (e.data.type === "open-external-url" && e.source === this.iframe?.contentWindow) {
|
||||
// Use chrome.tabs API to open in new tab
|
||||
const chromeAPI = globalThis.chrome;
|
||||
if (chromeAPI?.tabs) {
|
||||
chromeAPI.tabs.create({ url: e.data.url });
|
||||
}
|
||||
else {
|
||||
// Fallback for non-extension context
|
||||
window.open(e.data.url, "_blank");
|
||||
}
|
||||
}
|
||||
};
|
||||
window.addEventListener("message", externalUrlHandler);
|
||||
// Listen for sandbox-ready and sandbox-error messages directly
|
||||
const readyHandler = (e) => {
|
||||
if (e.data.type === "sandbox-ready" && e.source === this.iframe?.contentWindow) {
|
||||
window.removeEventListener("message", readyHandler);
|
||||
window.removeEventListener("message", errorHandler);
|
||||
// Send content to sandbox
|
||||
this.iframe?.contentWindow?.postMessage({
|
||||
type: "sandbox-load",
|
||||
sandboxId,
|
||||
code: completeHtml,
|
||||
}, "*");
|
||||
}
|
||||
};
|
||||
const errorHandler = (e) => {
|
||||
if (e.data.type === "sandbox-error" && e.source === this.iframe?.contentWindow) {
|
||||
window.removeEventListener("message", readyHandler);
|
||||
window.removeEventListener("message", errorHandler);
|
||||
// The sandbox.js already sent us the error via postMessage.
|
||||
// We need to convert it to an execution-error message that the execute() consumer will handle.
|
||||
// Simulate receiving an execution-error from the sandbox
|
||||
window.postMessage({
|
||||
sandboxId: sandboxId,
|
||||
type: "execution-error",
|
||||
error: { message: e.data.error, stack: e.data.stack },
|
||||
}, "*");
|
||||
}
|
||||
};
|
||||
window.addEventListener("message", readyHandler);
|
||||
window.addEventListener("message", errorHandler);
|
||||
this.appendChild(this.iframe);
|
||||
}
|
||||
loadViaSrcdoc(sandboxId, completeHtml) {
|
||||
// Create iframe with srcdoc
|
||||
this.iframe = document.createElement("iframe");
|
||||
this.iframe.sandbox.add("allow-scripts");
|
||||
this.iframe.sandbox.add("allow-modals");
|
||||
this.iframe.style.width = "100%";
|
||||
this.iframe.style.height = "100%";
|
||||
this.iframe.style.border = "none";
|
||||
this.iframe.srcdoc = completeHtml;
|
||||
// Update router with iframe reference BEFORE appending to DOM
|
||||
RUNTIME_MESSAGE_ROUTER.setSandboxIframe(sandboxId, this.iframe);
|
||||
// Listen for open-external-url messages from iframe
|
||||
const externalUrlHandler = (e) => {
|
||||
if (e.data.type === "open-external-url" && e.source === this.iframe?.contentWindow) {
|
||||
// Fallback for non-extension context
|
||||
window.open(e.data.url, "_blank");
|
||||
}
|
||||
};
|
||||
window.addEventListener("message", externalUrlHandler);
|
||||
this.appendChild(this.iframe);
|
||||
}
|
||||
/**
|
||||
* Execute code in sandbox
|
||||
* @param sandboxId Unique ID for this execution
|
||||
* @param code User code (plain JS for REPL, or full HTML for artifacts)
|
||||
* @param providers Runtime providers to inject
|
||||
* @param consumers Additional message consumers (optional, execute has its own internal consumer)
|
||||
* @param signal Abort signal
|
||||
* @returns Promise resolving to execution result
|
||||
*/
|
||||
async execute(sandboxId, code, providers = [], consumers = [], signal, isHtmlArtifact = false) {
|
||||
if (signal?.aborted) {
|
||||
throw new Error("Execution aborted");
|
||||
}
|
||||
const consoleProvider = new ConsoleRuntimeProvider();
|
||||
providers = [consoleProvider, ...providers];
|
||||
RUNTIME_MESSAGE_ROUTER.registerSandbox(sandboxId, providers, consumers);
|
||||
// Notify providers that execution is starting
|
||||
for (const provider of providers) {
|
||||
provider.onExecutionStart?.(sandboxId, signal);
|
||||
}
|
||||
const files = [];
|
||||
let completed = false;
|
||||
return new Promise((resolve, reject) => {
|
||||
// 4. Create execution consumer for lifecycle messages
|
||||
const executionConsumer = {
|
||||
async handleMessage(message) {
|
||||
if (message.type === "file-returned") {
|
||||
files.push({
|
||||
fileName: message.fileName,
|
||||
content: message.content,
|
||||
mimeType: message.mimeType,
|
||||
});
|
||||
}
|
||||
else if (message.type === "execution-complete") {
|
||||
completed = true;
|
||||
cleanup();
|
||||
resolve({
|
||||
success: true,
|
||||
console: consoleProvider.getLogs(),
|
||||
files,
|
||||
returnValue: message.returnValue,
|
||||
});
|
||||
}
|
||||
else if (message.type === "execution-error") {
|
||||
completed = true;
|
||||
cleanup();
|
||||
resolve({ success: false, console: consoleProvider.getLogs(), error: message.error, files });
|
||||
}
|
||||
},
|
||||
};
|
||||
RUNTIME_MESSAGE_ROUTER.addConsumer(sandboxId, executionConsumer);
|
||||
const cleanup = () => {
|
||||
// Notify providers that execution has ended
|
||||
for (const provider of providers) {
|
||||
provider.onExecutionEnd?.(sandboxId);
|
||||
}
|
||||
RUNTIME_MESSAGE_ROUTER.unregisterSandbox(sandboxId);
|
||||
signal?.removeEventListener("abort", abortHandler);
|
||||
clearTimeout(timeoutId);
|
||||
this.iframe?.remove();
|
||||
this.iframe = undefined;
|
||||
};
|
||||
// Abort handler
|
||||
const abortHandler = () => {
|
||||
if (!completed) {
|
||||
completed = true;
|
||||
cleanup();
|
||||
reject(new Error("Execution aborted"));
|
||||
}
|
||||
};
|
||||
if (signal) {
|
||||
signal.addEventListener("abort", abortHandler);
|
||||
}
|
||||
// Timeout handler (30 seconds)
|
||||
const timeoutId = setTimeout(() => {
|
||||
if (!completed) {
|
||||
completed = true;
|
||||
cleanup();
|
||||
resolve({
|
||||
success: false,
|
||||
console: consoleProvider.getLogs(),
|
||||
error: { message: "Execution timeout (120s)", stack: "" },
|
||||
files,
|
||||
});
|
||||
}
|
||||
}, 120000);
|
||||
// 4. Prepare HTML and create iframe
|
||||
const completeHtml = this.prepareHtmlDocument(sandboxId, code, providers, {
|
||||
isHtmlArtifact,
|
||||
isStandalone: false,
|
||||
});
|
||||
// 5. Validate HTML before sending to sandbox
|
||||
const validationError = this.validateHtml(completeHtml);
|
||||
if (validationError) {
|
||||
reject(new Error(`HTML validation failed: ${validationError}`));
|
||||
return;
|
||||
}
|
||||
if (this.sandboxUrlProvider) {
|
||||
// Browser extension mode: wait for sandbox-ready
|
||||
this.iframe = document.createElement("iframe");
|
||||
this.iframe.sandbox.add("allow-scripts", "allow-modals");
|
||||
this.iframe.style.cssText = "width: 100%; height: 100%; border: none;";
|
||||
this.iframe.src = this.sandboxUrlProvider();
|
||||
// Update router with iframe reference BEFORE appending to DOM
|
||||
RUNTIME_MESSAGE_ROUTER.setSandboxIframe(sandboxId, this.iframe);
|
||||
// Listen for sandbox-ready and sandbox-error messages
|
||||
const readyHandler = (e) => {
|
||||
if (e.data.type === "sandbox-ready" && e.source === this.iframe?.contentWindow) {
|
||||
window.removeEventListener("message", readyHandler);
|
||||
window.removeEventListener("message", errorHandler);
|
||||
// Send content to sandbox
|
||||
this.iframe?.contentWindow?.postMessage({
|
||||
type: "sandbox-load",
|
||||
sandboxId,
|
||||
code: completeHtml,
|
||||
}, "*");
|
||||
}
|
||||
};
|
||||
const errorHandler = (e) => {
|
||||
if (e.data.type === "sandbox-error" && e.source === this.iframe?.contentWindow) {
|
||||
window.removeEventListener("message", readyHandler);
|
||||
window.removeEventListener("message", errorHandler);
|
||||
// Convert sandbox-error to execution-error for the execution consumer
|
||||
window.postMessage({
|
||||
sandboxId: sandboxId,
|
||||
type: "execution-error",
|
||||
error: { message: e.data.error, stack: e.data.stack },
|
||||
}, "*");
|
||||
}
|
||||
};
|
||||
window.addEventListener("message", readyHandler);
|
||||
window.addEventListener("message", errorHandler);
|
||||
this.appendChild(this.iframe);
|
||||
}
|
||||
else {
|
||||
// Web mode: use srcdoc
|
||||
this.iframe = document.createElement("iframe");
|
||||
this.iframe.sandbox.add("allow-scripts", "allow-modals");
|
||||
this.iframe.style.cssText = "width: 100%; height: 100%; border: none; display: none;";
|
||||
this.iframe.srcdoc = completeHtml;
|
||||
// Update router with iframe reference BEFORE appending to DOM
|
||||
RUNTIME_MESSAGE_ROUTER.setSandboxIframe(sandboxId, this.iframe);
|
||||
this.appendChild(this.iframe);
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Validate HTML using DOMParser - returns error message if invalid, null if valid
|
||||
* Note: JavaScript syntax validation is done in sandbox.js to avoid CSP restrictions
|
||||
*/
|
||||
validateHtml(html) {
|
||||
try {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(html, "text/html");
|
||||
// Check for parser errors
|
||||
const parserError = doc.querySelector("parsererror");
|
||||
if (parserError) {
|
||||
return parserError.textContent || "Unknown parse error";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
catch (error) {
|
||||
return error.message || "Unknown validation error";
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Prepare complete HTML document with runtime + user code
|
||||
* PUBLIC so HtmlArtifact can use it for download button
|
||||
*/
|
||||
prepareHtmlDocument(sandboxId, userCode, providers = [], options) {
|
||||
// Default options
|
||||
const opts = {
|
||||
isHtmlArtifact: false,
|
||||
isStandalone: false,
|
||||
...options,
|
||||
};
|
||||
// Runtime script that will be injected
|
||||
const runtime = this.getRuntimeScript(sandboxId, providers, opts.isStandalone || false);
|
||||
// Only check for HTML tags if explicitly marked as HTML artifact
|
||||
// For javascript_repl, userCode is JavaScript that may contain HTML in string literals
|
||||
if (opts.isHtmlArtifact) {
|
||||
// HTML Artifact - inject runtime into existing HTML
|
||||
const headMatch = userCode.match(/<head[^>]*>/i);
|
||||
if (headMatch) {
|
||||
const index = headMatch.index + headMatch[0].length;
|
||||
return userCode.slice(0, index) + runtime + userCode.slice(index);
|
||||
}
|
||||
const htmlMatch = userCode.match(/<html[^>]*>/i);
|
||||
if (htmlMatch) {
|
||||
const index = htmlMatch.index + htmlMatch[0].length;
|
||||
return userCode.slice(0, index) + runtime + userCode.slice(index);
|
||||
}
|
||||
// Fallback: prepend runtime
|
||||
return runtime + userCode;
|
||||
}
|
||||
else {
|
||||
// REPL - wrap code in HTML with runtime and call complete() when done
|
||||
// Escape </script> in user code to prevent premature tag closure
|
||||
const escapedUserCode = escapeScriptContent(userCode);
|
||||
return `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
${runtime}
|
||||
</head>
|
||||
<body>
|
||||
<script type="module">
|
||||
(async () => {
|
||||
try {
|
||||
// Wrap user code in async function to capture return value
|
||||
const userCodeFunc = async () => {
|
||||
${escapedUserCode}
|
||||
};
|
||||
|
||||
const returnValue = await userCodeFunc();
|
||||
|
||||
// Call completion callbacks before complete()
|
||||
if (window.__completionCallbacks && window.__completionCallbacks.length > 0) {
|
||||
try {
|
||||
await Promise.all(window.__completionCallbacks.map(cb => cb(true)));
|
||||
} catch (e) {
|
||||
console.error('Completion callback error:', e);
|
||||
}
|
||||
}
|
||||
|
||||
await window.complete(null, returnValue);
|
||||
} catch (error) {
|
||||
|
||||
// Call completion callbacks before complete() (error path)
|
||||
if (window.__completionCallbacks && window.__completionCallbacks.length > 0) {
|
||||
try {
|
||||
await Promise.all(window.__completionCallbacks.map(cb => cb(false)));
|
||||
} catch (e) {
|
||||
console.error('Completion callback error:', e);
|
||||
}
|
||||
}
|
||||
|
||||
await window.complete({
|
||||
message: error?.message || String(error),
|
||||
stack: error?.stack || new Error().stack
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Generate runtime script from providers
|
||||
* @param sandboxId Unique sandbox ID
|
||||
* @param providers Runtime providers
|
||||
* @param isStandalone If true, skip runtime bridge and navigation interceptor (for standalone downloads)
|
||||
*/
|
||||
getRuntimeScript(sandboxId, providers = [], isStandalone = false) {
|
||||
// Collect all data from providers
|
||||
const allData = {};
|
||||
for (const provider of providers) {
|
||||
Object.assign(allData, provider.getData());
|
||||
}
|
||||
// Generate bridge code (skip if standalone)
|
||||
const bridgeCode = isStandalone
|
||||
? ""
|
||||
: RuntimeMessageBridge.generateBridgeCode({
|
||||
context: "sandbox-iframe",
|
||||
sandboxId,
|
||||
});
|
||||
// Collect all runtime functions - pass sandboxId as string literal
|
||||
const runtimeFunctions = [];
|
||||
for (const provider of providers) {
|
||||
runtimeFunctions.push(`(${provider.getRuntime().toString()})(${JSON.stringify(sandboxId)});`);
|
||||
}
|
||||
// Build script with HTML escaping
|
||||
// Escape </script> to prevent premature tag closure in HTML parser
|
||||
const dataInjection = Object.entries(allData)
|
||||
.map(([key, value]) => {
|
||||
const jsonStr = JSON.stringify(value).replace(/<\/script/gi, "<\\/script");
|
||||
return `window.${key} = ${jsonStr};`;
|
||||
})
|
||||
.join("\n");
|
||||
// TODO the font-size is needed, as chrome seems to inject a stylesheet into iframes
|
||||
// found in an extension context like sidepanel, settin body { font-size: 75% }. It's
|
||||
// definitely not our code doing that.
|
||||
// See https://stackoverflow.com/questions/71480433/chrome-is-injecting-some-stylesheet-in-popup-ui-which-reduces-the-font-size-to-7
|
||||
// Navigation interceptor (only if NOT standalone)
|
||||
const navigationInterceptor = isStandalone
|
||||
? ""
|
||||
: `
|
||||
// Navigation interceptor: prevent all navigation and open externally
|
||||
(function() {
|
||||
// Intercept link clicks
|
||||
document.addEventListener('click', function(e) {
|
||||
const link = e.target.closest('a');
|
||||
if (link && link.href) {
|
||||
// Check if it's an external link (not javascript: or #hash)
|
||||
if (link.href.startsWith('http://') || link.href.startsWith('https://')) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
window.parent.postMessage({ type: 'open-external-url', url: link.href }, '*');
|
||||
}
|
||||
}
|
||||
}, true);
|
||||
|
||||
// Intercept form submissions
|
||||
document.addEventListener('submit', function(e) {
|
||||
const form = e.target;
|
||||
if (form && form.action) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
window.parent.postMessage({ type: 'open-external-url', url: form.action }, '*');
|
||||
}
|
||||
}, true);
|
||||
|
||||
// Prevent window.location changes (only if not already redefined)
|
||||
try {
|
||||
const originalLocation = window.location;
|
||||
Object.defineProperty(window, 'location', {
|
||||
get: function() { return originalLocation; },
|
||||
set: function(url) {
|
||||
window.parent.postMessage({ type: 'open-external-url', url: url.toString() }, '*');
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
// Already defined, skip
|
||||
}
|
||||
})();
|
||||
`;
|
||||
return `<style>
|
||||
html, body {
|
||||
font-size: initial;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
window.sandboxId = ${JSON.stringify(sandboxId)};
|
||||
${dataInjection}
|
||||
${bridgeCode}
|
||||
${runtimeFunctions.join("\n")}
|
||||
${navigationInterceptor}
|
||||
</script>`;
|
||||
}
|
||||
};
|
||||
__decorate([
|
||||
property({ attribute: false })
|
||||
], SandboxIframe.prototype, "sandboxUrlProvider", void 0);
|
||||
SandboxIframe = __decorate([
|
||||
customElement("sandbox-iframe")
|
||||
], SandboxIframe);
|
||||
export { SandboxIframe };
|
||||
//# sourceMappingURL=SandboxedIframe.js.map
|
||||
File diff suppressed because one or more lines are too long
18
apps/macos/Sources/Clawdis/Resources/WebChat/components/StreamingMessageContainer.d.ts
vendored
Normal file
18
apps/macos/Sources/Clawdis/Resources/WebChat/components/StreamingMessageContainer.d.ts
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
import type { AgentTool, Message, ToolResultMessage } from "@mariozechner/pi-ai";
|
||||
import { LitElement } from "lit";
|
||||
export declare class StreamingMessageContainer extends LitElement {
|
||||
tools: AgentTool[];
|
||||
isStreaming: boolean;
|
||||
pendingToolCalls?: Set<string>;
|
||||
toolResultsById?: Map<string, ToolResultMessage>;
|
||||
onCostClick?: () => void;
|
||||
private _message;
|
||||
private _pendingMessage;
|
||||
private _updateScheduled;
|
||||
private _immediateUpdate;
|
||||
protected createRenderRoot(): HTMLElement | DocumentFragment;
|
||||
connectedCallback(): void;
|
||||
setMessage(message: Message | null, immediate?: boolean): void;
|
||||
render(): import("lit-html").TemplateResult<1> | undefined;
|
||||
}
|
||||
//# sourceMappingURL=StreamingMessageContainer.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"StreamingMessageContainer.d.ts","sourceRoot":"","sources":["../../src/components/StreamingMessageContainer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACjF,OAAO,EAAQ,UAAU,EAAE,MAAM,KAAK,CAAC;AAGvC,qBAAa,yBAA0B,SAAQ,UAAU;IAC7B,KAAK,EAAE,SAAS,EAAE,CAAM;IACtB,WAAW,UAAS;IACrB,gBAAgB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC/B,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IAC7C,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IAEhD,OAAO,CAAC,QAAQ,CAAwB;IACjD,OAAO,CAAC,eAAe,CAAwB;IAC/C,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,gBAAgB,CAAS;cAEd,gBAAgB,IAAI,WAAW,GAAG,gBAAgB;IAI5D,iBAAiB,IAAI,IAAI;IAM3B,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,EAAE,SAAS,UAAQ;IAmCnD,MAAM;CAmCf"}
|
||||
@@ -0,0 +1,117 @@
|
||||
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 { property, state } from "lit/decorators.js";
|
||||
export class StreamingMessageContainer extends LitElement {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.tools = [];
|
||||
this.isStreaming = false;
|
||||
this._message = null;
|
||||
this._pendingMessage = null;
|
||||
this._updateScheduled = false;
|
||||
this._immediateUpdate = false;
|
||||
}
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.style.display = "block";
|
||||
}
|
||||
// Public method to update the message with batching for performance
|
||||
setMessage(message, immediate = false) {
|
||||
// Store the latest message
|
||||
this._pendingMessage = message;
|
||||
// If this is an immediate update (like clearing), apply it right away
|
||||
if (immediate || message === null) {
|
||||
this._immediateUpdate = true;
|
||||
this._message = message;
|
||||
this.requestUpdate();
|
||||
// Cancel any pending updates since we're clearing
|
||||
this._pendingMessage = null;
|
||||
this._updateScheduled = false;
|
||||
return;
|
||||
}
|
||||
// Otherwise batch updates for performance during streaming
|
||||
if (!this._updateScheduled) {
|
||||
this._updateScheduled = true;
|
||||
requestAnimationFrame(async () => {
|
||||
// Only apply the update if we haven't been cleared
|
||||
if (!this._immediateUpdate && this._pendingMessage !== null) {
|
||||
// Deep clone the message to ensure Lit detects changes in nested properties
|
||||
// (like toolCall.arguments being mutated during streaming)
|
||||
this._message = JSON.parse(JSON.stringify(this._pendingMessage));
|
||||
this.requestUpdate();
|
||||
}
|
||||
// Reset for next batch
|
||||
this._pendingMessage = null;
|
||||
this._updateScheduled = false;
|
||||
this._immediateUpdate = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
render() {
|
||||
// Show loading indicator if loading but no message yet
|
||||
if (!this._message) {
|
||||
if (this.isStreaming)
|
||||
return html `<div class="flex flex-col gap-3 mb-3">
|
||||
<span class="mx-4 inline-block w-2 h-4 bg-muted-foreground animate-pulse"></span>
|
||||
</div>`;
|
||||
return html ``; // Empty until a message is set
|
||||
}
|
||||
const msg = this._message;
|
||||
if (msg.role === "toolResult") {
|
||||
// Skip standalone tool result in streaming; the stable list will render paired tool-message
|
||||
return html ``;
|
||||
}
|
||||
else if (msg.role === "user") {
|
||||
// Skip standalone tool result in streaming; the stable list will render it immediiately
|
||||
return html ``;
|
||||
}
|
||||
else if (msg.role === "assistant") {
|
||||
// Assistant message - render inline tool messages during streaming
|
||||
return html `
|
||||
<div class="flex flex-col gap-3 mb-3">
|
||||
<assistant-message
|
||||
.message=${msg}
|
||||
.tools=${this.tools}
|
||||
.isStreaming=${this.isStreaming}
|
||||
.pendingToolCalls=${this.pendingToolCalls}
|
||||
.toolResultsById=${this.toolResultsById}
|
||||
.hideToolCalls=${false}
|
||||
.onCostClick=${this.onCostClick}
|
||||
></assistant-message>
|
||||
${this.isStreaming ? html `<span class="mx-4 inline-block w-2 h-4 bg-muted-foreground animate-pulse"></span>` : ""}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
}
|
||||
__decorate([
|
||||
property({ type: Array })
|
||||
], StreamingMessageContainer.prototype, "tools", void 0);
|
||||
__decorate([
|
||||
property({ type: Boolean })
|
||||
], StreamingMessageContainer.prototype, "isStreaming", void 0);
|
||||
__decorate([
|
||||
property({ type: Object })
|
||||
], StreamingMessageContainer.prototype, "pendingToolCalls", void 0);
|
||||
__decorate([
|
||||
property({ type: Object })
|
||||
], StreamingMessageContainer.prototype, "toolResultsById", void 0);
|
||||
__decorate([
|
||||
property({ attribute: false })
|
||||
], StreamingMessageContainer.prototype, "onCostClick", void 0);
|
||||
__decorate([
|
||||
state()
|
||||
], StreamingMessageContainer.prototype, "_message", void 0);
|
||||
// Register custom element
|
||||
if (!customElements.get("streaming-message-container")) {
|
||||
customElements.define("streaming-message-container", StreamingMessageContainer);
|
||||
}
|
||||
//# sourceMappingURL=StreamingMessageContainer.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"StreamingMessageContainer.js","sourceRoot":"","sources":["../../src/components/StreamingMessageContainer.ts"],"names":[],"mappings":"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"}
|
||||
11
apps/macos/Sources/Clawdis/Resources/WebChat/components/ThinkingBlock.d.ts
vendored
Normal file
11
apps/macos/Sources/Clawdis/Resources/WebChat/components/ThinkingBlock.d.ts
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
import { LitElement } from "lit";
|
||||
export declare class ThinkingBlock extends LitElement {
|
||||
content: string;
|
||||
isStreaming: boolean;
|
||||
private isExpanded;
|
||||
protected createRenderRoot(): HTMLElement | DocumentFragment;
|
||||
connectedCallback(): void;
|
||||
private toggleExpanded;
|
||||
render(): import("lit-html").TemplateResult<1>;
|
||||
}
|
||||
//# sourceMappingURL=ThinkingBlock.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ThinkingBlock.d.ts","sourceRoot":"","sources":["../../src/components/ThinkingBlock.ts"],"names":[],"mappings":"AACA,OAAO,EAAQ,UAAU,EAAE,MAAM,KAAK,CAAC;AAIvC,qBACa,aAAc,SAAQ,UAAU;IAChC,OAAO,EAAG,MAAM,CAAC;IACA,WAAW,UAAS;IACxC,OAAO,CAAC,UAAU,CAAS;cAEjB,gBAAgB,IAAI,WAAW,GAAG,gBAAgB;IAI5D,iBAAiB,IAAI,IAAI;IAKlC,OAAO,CAAC,cAAc;IAIb,MAAM;CAkBf"}
|
||||
@@ -0,0 +1,58 @@
|
||||
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 { icon } from "@mariozechner/mini-lit";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import { ChevronRight } from "lucide";
|
||||
let ThinkingBlock = class ThinkingBlock extends LitElement {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.isStreaming = false;
|
||||
this.isExpanded = false;
|
||||
}
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.style.display = "block";
|
||||
}
|
||||
toggleExpanded() {
|
||||
this.isExpanded = !this.isExpanded;
|
||||
}
|
||||
render() {
|
||||
const shimmerClasses = this.isStreaming
|
||||
? "animate-shimmer bg-gradient-to-r from-muted-foreground via-foreground to-muted-foreground bg-[length:200%_100%] bg-clip-text text-transparent"
|
||||
: "";
|
||||
return html `
|
||||
<div class="thinking-block">
|
||||
<div
|
||||
class="thinking-header cursor-pointer select-none flex items-center gap-2 py-1 text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
@click=${this.toggleExpanded}
|
||||
>
|
||||
<span class="transition-transform inline-block ${this.isExpanded ? "rotate-90" : ""}">${icon(ChevronRight, "sm")}</span>
|
||||
<span class="${shimmerClasses}">Thinking...</span>
|
||||
</div>
|
||||
${this.isExpanded ? html `<markdown-block .content=${this.content} .isThinking=${true}></markdown-block>` : ""}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
};
|
||||
__decorate([
|
||||
property()
|
||||
], ThinkingBlock.prototype, "content", void 0);
|
||||
__decorate([
|
||||
property({ type: Boolean })
|
||||
], ThinkingBlock.prototype, "isStreaming", void 0);
|
||||
__decorate([
|
||||
state()
|
||||
], ThinkingBlock.prototype, "isExpanded", void 0);
|
||||
ThinkingBlock = __decorate([
|
||||
customElement("thinking-block")
|
||||
], ThinkingBlock);
|
||||
export { ThinkingBlock };
|
||||
//# sourceMappingURL=ThinkingBlock.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ThinkingBlock.js","sourceRoot":"","sources":["../../src/components/ThinkingBlock.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAC9C,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,KAAK,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAG/B,IAAM,aAAa,GAAnB,MAAM,aAAc,SAAQ,UAAU;IAAtC;;QAEuB,gBAAW,GAAG,KAAK,CAAC;QAChC,eAAU,GAAG,KAAK,CAAC;IAiCrC,CAAC;IA/BmB,gBAAgB;QAClC,OAAO,IAAI,CAAC;IACb,CAAC;IAEQ,iBAAiB;QACzB,KAAK,CAAC,iBAAiB,EAAE,CAAC;QAC1B,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,OAAO,CAAC;IAC9B,CAAC;IAEO,cAAc;QACrB,IAAI,CAAC,UAAU,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC;IACpC,CAAC;IAEQ,MAAM;QACd,MAAM,cAAc,GAAG,IAAI,CAAC,WAAW;YACtC,CAAC,CAAC,+IAA+I;YACjJ,CAAC,CAAC,EAAE,CAAC;QAEN,OAAO,IAAI,CAAA;;;;cAIC,IAAI,CAAC,cAAc;;sDAEqB,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC;oBACjG,cAAc;;MAE5B,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAA,4BAA4B,IAAI,CAAC,OAAO,gBAAgB,IAAI,oBAAoB,CAAC,CAAC,CAAC,EAAE;;GAE9G,CAAC;IACH,CAAC;CACD,CAAA;AAnCY;IAAX,QAAQ,EAAE;8CAAkB;AACA;IAA5B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;kDAAqB;AAChC;IAAhB,KAAK,EAAE;iDAA4B;AAHxB,aAAa;IADzB,aAAa,CAAC,gBAAgB,CAAC;GACnB,aAAa,CAoCzB"}
|
||||
12
apps/macos/Sources/Clawdis/Resources/WebChat/components/message-renderer-registry.d.ts
vendored
Normal file
12
apps/macos/Sources/Clawdis/Resources/WebChat/components/message-renderer-registry.d.ts
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
import type { TemplateResult } from "lit";
|
||||
import type { AppMessage } from "./Messages.js";
|
||||
export type MessageRole = AppMessage["role"];
|
||||
export interface MessageRenderer<TMessage extends AppMessage = AppMessage> {
|
||||
render(message: TMessage): TemplateResult;
|
||||
}
|
||||
export declare function registerMessageRenderer<TRole extends MessageRole>(role: TRole, renderer: MessageRenderer<Extract<AppMessage, {
|
||||
role: TRole;
|
||||
}>>): void;
|
||||
export declare function getMessageRenderer(role: MessageRole): MessageRenderer | undefined;
|
||||
export declare function renderMessage(message: AppMessage): TemplateResult | undefined;
|
||||
//# sourceMappingURL=message-renderer-registry.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"message-renderer-registry.d.ts","sourceRoot":"","sources":["../../src/components/message-renderer-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,KAAK,CAAC;AAC1C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAGhD,MAAM,MAAM,WAAW,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;AAG7C,MAAM,WAAW,eAAe,CAAC,QAAQ,SAAS,UAAU,GAAG,UAAU;IACxE,MAAM,CAAC,OAAO,EAAE,QAAQ,GAAG,cAAc,CAAC;CAC1C;AAKD,wBAAgB,uBAAuB,CAAC,KAAK,SAAS,WAAW,EAChE,IAAI,EAAE,KAAK,EACX,QAAQ,EAAE,eAAe,CAAC,OAAO,CAAC,UAAU,EAAE;IAAE,IAAI,EAAE,KAAK,CAAA;CAAE,CAAC,CAAC,GAC7D,IAAI,CAEN;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,WAAW,GAAG,eAAe,GAAG,SAAS,CAEjF;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,UAAU,GAAG,cAAc,GAAG,SAAS,CAE7E"}
|
||||
@@ -0,0 +1,12 @@
|
||||
// Registry of custom message renderers by role
|
||||
const messageRenderers = new Map();
|
||||
export function registerMessageRenderer(role, renderer) {
|
||||
messageRenderers.set(role, renderer);
|
||||
}
|
||||
export function getMessageRenderer(role) {
|
||||
return messageRenderers.get(role);
|
||||
}
|
||||
export function renderMessage(message) {
|
||||
return messageRenderers.get(message.role)?.render(message);
|
||||
}
|
||||
//# sourceMappingURL=message-renderer-registry.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"message-renderer-registry.js","sourceRoot":"","sources":["../../src/components/message-renderer-registry.ts"],"names":[],"mappings":"AAWA,+CAA+C;AAC/C,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAAqC,CAAC;AAEtE,MAAM,UAAU,uBAAuB,CACtC,IAAW,EACX,QAA+D;IAE/D,gBAAgB,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AACtC,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,IAAiB;IACnD,OAAO,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,OAAmB;IAChD,OAAO,gBAAgB,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;AAC5D,CAAC"}
|
||||
35
apps/macos/Sources/Clawdis/Resources/WebChat/components/sandbox/ArtifactsRuntimeProvider.d.ts
vendored
Normal file
35
apps/macos/Sources/Clawdis/Resources/WebChat/components/sandbox/ArtifactsRuntimeProvider.d.ts
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
import type { SandboxRuntimeProvider } from "./SandboxRuntimeProvider.js";
|
||||
interface ArtifactsPanelLike {
|
||||
artifacts: Map<string, {
|
||||
content: string;
|
||||
}>;
|
||||
tool: {
|
||||
execute(toolCallId: string, args: {
|
||||
command: string;
|
||||
filename: string;
|
||||
content?: string;
|
||||
}): Promise<any>;
|
||||
};
|
||||
}
|
||||
interface AgentLike {
|
||||
appendMessage(message: any): void;
|
||||
}
|
||||
/**
|
||||
* Artifacts Runtime Provider
|
||||
*
|
||||
* Provides programmatic access to session artifacts from sandboxed code.
|
||||
* Allows code to create, read, update, and delete artifacts dynamically.
|
||||
* Supports both online (extension) and offline (downloaded HTML) modes.
|
||||
*/
|
||||
export declare class ArtifactsRuntimeProvider implements SandboxRuntimeProvider {
|
||||
private artifactsPanel;
|
||||
private agent?;
|
||||
private readWrite;
|
||||
constructor(artifactsPanel: ArtifactsPanelLike, agent?: AgentLike | undefined, readWrite?: boolean);
|
||||
getData(): Record<string, any>;
|
||||
getRuntime(): (sandboxId: string) => void;
|
||||
handleMessage(message: any, respond: (response: any) => void): Promise<void>;
|
||||
getDescription(): string;
|
||||
}
|
||||
export {};
|
||||
//# sourceMappingURL=ArtifactsRuntimeProvider.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ArtifactsRuntimeProvider.d.ts","sourceRoot":"","sources":["../../../src/components/sandbox/ArtifactsRuntimeProvider.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAG1E,UAAU,kBAAkB;IAC3B,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5C,IAAI,EAAE;QACL,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE;YAAE,OAAO,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAC;YAAC,OAAO,CAAC,EAAE,MAAM,CAAA;SAAE,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;KACzG,CAAC;CACF;AAED,UAAU,SAAS;IAClB,aAAa,CAAC,OAAO,EAAE,GAAG,GAAG,IAAI,CAAC;CAClC;AAED;;;;;;GAMG;AACH,qBAAa,wBAAyB,YAAW,sBAAsB;IAErE,OAAO,CAAC,cAAc;IACtB,OAAO,CAAC,KAAK,CAAC;IACd,OAAO,CAAC,SAAS;gBAFT,cAAc,EAAE,kBAAkB,EAClC,KAAK,CAAC,EAAE,SAAS,YAAA,EACjB,SAAS,GAAE,OAAc;IAGlC,OAAO,IAAI,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IAS9B,UAAU,IAAI,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI;IAgGnC,aAAa,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IA8ElF,cAAc,IAAI,MAAM;CAGxB"}
|
||||
@@ -0,0 +1,189 @@
|
||||
import { ARTIFACTS_RUNTIME_PROVIDER_DESCRIPTION_RO, ARTIFACTS_RUNTIME_PROVIDER_DESCRIPTION_RW, } from "../../prompts/prompts.js";
|
||||
/**
|
||||
* Artifacts Runtime Provider
|
||||
*
|
||||
* Provides programmatic access to session artifacts from sandboxed code.
|
||||
* Allows code to create, read, update, and delete artifacts dynamically.
|
||||
* Supports both online (extension) and offline (downloaded HTML) modes.
|
||||
*/
|
||||
export class ArtifactsRuntimeProvider {
|
||||
constructor(artifactsPanel, agent, readWrite = true) {
|
||||
this.artifactsPanel = artifactsPanel;
|
||||
this.agent = agent;
|
||||
this.readWrite = readWrite;
|
||||
}
|
||||
getData() {
|
||||
// Inject artifact snapshot for offline mode
|
||||
const snapshot = {};
|
||||
this.artifactsPanel.artifacts.forEach((artifact, filename) => {
|
||||
snapshot[filename] = artifact.content;
|
||||
});
|
||||
return { artifacts: snapshot };
|
||||
}
|
||||
getRuntime() {
|
||||
// This function will be stringified, so no external references!
|
||||
return (_sandboxId) => {
|
||||
// Auto-parse/stringify for .json files
|
||||
const isJsonFile = (filename) => filename.endsWith(".json");
|
||||
window.listArtifacts = async () => {
|
||||
// Online: ask extension
|
||||
if (window.sendRuntimeMessage) {
|
||||
const response = await window.sendRuntimeMessage({
|
||||
type: "artifact-operation",
|
||||
action: "list",
|
||||
});
|
||||
if (!response.success)
|
||||
throw new Error(response.error);
|
||||
return response.result;
|
||||
}
|
||||
// Offline: return snapshot keys
|
||||
else {
|
||||
return Object.keys(window.artifacts || {});
|
||||
}
|
||||
};
|
||||
window.getArtifact = async (filename) => {
|
||||
let content;
|
||||
// Online: ask extension
|
||||
if (window.sendRuntimeMessage) {
|
||||
const response = await window.sendRuntimeMessage({
|
||||
type: "artifact-operation",
|
||||
action: "get",
|
||||
filename,
|
||||
});
|
||||
if (!response.success)
|
||||
throw new Error(response.error);
|
||||
content = response.result;
|
||||
}
|
||||
// Offline: read snapshot
|
||||
else {
|
||||
if (!window.artifacts?.[filename]) {
|
||||
throw new Error(`Artifact not found (offline mode): ${filename}`);
|
||||
}
|
||||
content = window.artifacts[filename];
|
||||
}
|
||||
// Auto-parse .json files
|
||||
if (isJsonFile(filename)) {
|
||||
try {
|
||||
return JSON.parse(content);
|
||||
}
|
||||
catch (e) {
|
||||
throw new Error(`Failed to parse JSON from ${filename}: ${e}`);
|
||||
}
|
||||
}
|
||||
return content;
|
||||
};
|
||||
window.createOrUpdateArtifact = async (filename, content, mimeType) => {
|
||||
if (!window.sendRuntimeMessage) {
|
||||
throw new Error("Cannot create/update artifacts in offline mode (read-only)");
|
||||
}
|
||||
let finalContent = content;
|
||||
// Auto-stringify .json files
|
||||
if (isJsonFile(filename) && typeof content !== "string") {
|
||||
finalContent = JSON.stringify(content, null, 2);
|
||||
}
|
||||
else if (typeof content !== "string") {
|
||||
finalContent = JSON.stringify(content, null, 2);
|
||||
}
|
||||
const response = await window.sendRuntimeMessage({
|
||||
type: "artifact-operation",
|
||||
action: "createOrUpdate",
|
||||
filename,
|
||||
content: finalContent,
|
||||
mimeType,
|
||||
});
|
||||
if (!response.success)
|
||||
throw new Error(response.error);
|
||||
};
|
||||
window.deleteArtifact = async (filename) => {
|
||||
if (!window.sendRuntimeMessage) {
|
||||
throw new Error("Cannot delete artifacts in offline mode (read-only)");
|
||||
}
|
||||
const response = await window.sendRuntimeMessage({
|
||||
type: "artifact-operation",
|
||||
action: "delete",
|
||||
filename,
|
||||
});
|
||||
if (!response.success)
|
||||
throw new Error(response.error);
|
||||
};
|
||||
};
|
||||
}
|
||||
async handleMessage(message, respond) {
|
||||
if (message.type !== "artifact-operation") {
|
||||
return;
|
||||
}
|
||||
const { action, filename, content, mimeType } = message;
|
||||
try {
|
||||
switch (action) {
|
||||
case "list": {
|
||||
const filenames = Array.from(this.artifactsPanel.artifacts.keys());
|
||||
respond({ success: true, result: filenames });
|
||||
break;
|
||||
}
|
||||
case "get": {
|
||||
const artifact = this.artifactsPanel.artifacts.get(filename);
|
||||
if (!artifact) {
|
||||
respond({ success: false, error: `Artifact not found: ${filename}` });
|
||||
}
|
||||
else {
|
||||
respond({ success: true, result: artifact.content });
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "createOrUpdate": {
|
||||
try {
|
||||
const exists = this.artifactsPanel.artifacts.has(filename);
|
||||
const command = exists ? "rewrite" : "create";
|
||||
const action = exists ? "update" : "create";
|
||||
await this.artifactsPanel.tool.execute("", {
|
||||
command,
|
||||
filename,
|
||||
content,
|
||||
});
|
||||
this.agent?.appendMessage({
|
||||
role: "artifact",
|
||||
action,
|
||||
filename,
|
||||
content,
|
||||
...(action === "create" && { title: filename }),
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
respond({ success: true });
|
||||
}
|
||||
catch (err) {
|
||||
respond({ success: false, error: err.message });
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "delete": {
|
||||
try {
|
||||
await this.artifactsPanel.tool.execute("", {
|
||||
command: "delete",
|
||||
filename,
|
||||
});
|
||||
this.agent?.appendMessage({
|
||||
role: "artifact",
|
||||
action: "delete",
|
||||
filename,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
respond({ success: true });
|
||||
}
|
||||
catch (err) {
|
||||
respond({ success: false, error: err.message });
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
respond({ success: false, error: `Unknown artifact action: ${action}` });
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
respond({ success: false, error: error.message });
|
||||
}
|
||||
}
|
||||
getDescription() {
|
||||
return this.readWrite ? ARTIFACTS_RUNTIME_PROVIDER_DESCRIPTION_RW : ARTIFACTS_RUNTIME_PROVIDER_DESCRIPTION_RO;
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=ArtifactsRuntimeProvider.js.map
|
||||
File diff suppressed because one or more lines are too long
17
apps/macos/Sources/Clawdis/Resources/WebChat/components/sandbox/AttachmentsRuntimeProvider.d.ts
vendored
Normal file
17
apps/macos/Sources/Clawdis/Resources/WebChat/components/sandbox/AttachmentsRuntimeProvider.d.ts
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
import type { Attachment } from "../../utils/attachment-utils.js";
|
||||
import type { SandboxRuntimeProvider } from "./SandboxRuntimeProvider.js";
|
||||
/**
|
||||
* Attachments Runtime Provider
|
||||
*
|
||||
* OPTIONAL provider that provides file access APIs to sandboxed code.
|
||||
* Only needed when attachments are present.
|
||||
* Attachments are read-only snapshot data - no messaging needed.
|
||||
*/
|
||||
export declare class AttachmentsRuntimeProvider implements SandboxRuntimeProvider {
|
||||
private attachments;
|
||||
constructor(attachments: Attachment[]);
|
||||
getData(): Record<string, any>;
|
||||
getRuntime(): (sandboxId: string) => void;
|
||||
getDescription(): string;
|
||||
}
|
||||
//# sourceMappingURL=AttachmentsRuntimeProvider.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"AttachmentsRuntimeProvider.d.ts","sourceRoot":"","sources":["../../../src/components/sandbox/AttachmentsRuntimeProvider.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAE1E;;;;;;GAMG;AACH,qBAAa,0BAA2B,YAAW,sBAAsB;IAC5D,OAAO,CAAC,WAAW;gBAAX,WAAW,EAAE,UAAU,EAAE;IAE7C,OAAO,IAAI,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IAa9B,UAAU,IAAI,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI;IAmCzC,cAAc,IAAI,MAAM;CAGxB"}
|
||||
@@ -0,0 +1,64 @@
|
||||
import { ATTACHMENTS_RUNTIME_DESCRIPTION } from "../../prompts/prompts.js";
|
||||
/**
|
||||
* Attachments Runtime Provider
|
||||
*
|
||||
* OPTIONAL provider that provides file access APIs to sandboxed code.
|
||||
* Only needed when attachments are present.
|
||||
* Attachments are read-only snapshot data - no messaging needed.
|
||||
*/
|
||||
export class AttachmentsRuntimeProvider {
|
||||
constructor(attachments) {
|
||||
this.attachments = attachments;
|
||||
}
|
||||
getData() {
|
||||
const attachmentsData = this.attachments.map((a) => ({
|
||||
id: a.id,
|
||||
fileName: a.fileName,
|
||||
mimeType: a.mimeType,
|
||||
size: a.size,
|
||||
content: a.content,
|
||||
extractedText: a.extractedText,
|
||||
}));
|
||||
return { attachments: attachmentsData };
|
||||
}
|
||||
getRuntime() {
|
||||
// This function will be stringified, so no external references!
|
||||
// These functions read directly from window.attachments
|
||||
// Works both online AND offline (no messaging needed!)
|
||||
return (_sandboxId) => {
|
||||
window.listAttachments = () => (window.attachments || []).map((a) => ({
|
||||
id: a.id,
|
||||
fileName: a.fileName,
|
||||
mimeType: a.mimeType,
|
||||
size: a.size,
|
||||
}));
|
||||
window.readTextAttachment = (attachmentId) => {
|
||||
const a = (window.attachments || []).find((x) => x.id === attachmentId);
|
||||
if (!a)
|
||||
throw new Error("Attachment not found: " + attachmentId);
|
||||
if (a.extractedText)
|
||||
return a.extractedText;
|
||||
try {
|
||||
return atob(a.content);
|
||||
}
|
||||
catch {
|
||||
throw new Error("Failed to decode text content for: " + attachmentId);
|
||||
}
|
||||
};
|
||||
window.readBinaryAttachment = (attachmentId) => {
|
||||
const a = (window.attachments || []).find((x) => x.id === attachmentId);
|
||||
if (!a)
|
||||
throw new Error("Attachment not found: " + attachmentId);
|
||||
const bin = atob(a.content);
|
||||
const bytes = new Uint8Array(bin.length);
|
||||
for (let i = 0; i < bin.length; i++)
|
||||
bytes[i] = bin.charCodeAt(i);
|
||||
return bytes;
|
||||
};
|
||||
};
|
||||
}
|
||||
getDescription() {
|
||||
return ATTACHMENTS_RUNTIME_DESCRIPTION;
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=AttachmentsRuntimeProvider.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"AttachmentsRuntimeProvider.js","sourceRoot":"","sources":["../../../src/components/sandbox/AttachmentsRuntimeProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,+BAA+B,EAAE,MAAM,0BAA0B,CAAC;AAI3E;;;;;;GAMG;AACH,MAAM,OAAO,0BAA0B;IACtC,YAAoB,WAAyB;QAAzB,gBAAW,GAAX,WAAW,CAAc;IAAG,CAAC;IAEjD,OAAO;QACN,MAAM,eAAe,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACpD,EAAE,EAAE,CAAC,CAAC,EAAE;YACR,QAAQ,EAAE,CAAC,CAAC,QAAQ;YACpB,QAAQ,EAAE,CAAC,CAAC,QAAQ;YACpB,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,OAAO,EAAE,CAAC,CAAC,OAAO;YAClB,aAAa,EAAE,CAAC,CAAC,aAAa;SAC9B,CAAC,CAAC,CAAC;QAEJ,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,CAAC;IACzC,CAAC;IAED,UAAU;QACT,gEAAgE;QAChE,wDAAwD;QACxD,uDAAuD;QACvD,OAAO,CAAC,UAAkB,EAAE,EAAE;YAC5B,MAAc,CAAC,eAAe,GAAG,GAAG,EAAE,CACtC,CAAE,MAAc,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC;gBACpD,EAAE,EAAE,CAAC,CAAC,EAAE;gBACR,QAAQ,EAAE,CAAC,CAAC,QAAQ;gBACpB,QAAQ,EAAE,CAAC,CAAC,QAAQ;gBACpB,IAAI,EAAE,CAAC,CAAC,IAAI;aACZ,CAAC,CAAC,CAAC;YAEJ,MAAc,CAAC,kBAAkB,GAAG,CAAC,YAAoB,EAAE,EAAE;gBAC7D,MAAM,CAAC,GAAG,CAAE,MAAc,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,YAAY,CAAC,CAAC;gBACtF,IAAI,CAAC,CAAC;oBAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,GAAG,YAAY,CAAC,CAAC;gBACjE,IAAI,CAAC,CAAC,aAAa;oBAAE,OAAO,CAAC,CAAC,aAAa,CAAC;gBAC5C,IAAI,CAAC;oBACJ,OAAO,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;gBACxB,CAAC;gBAAC,MAAM,CAAC;oBACR,MAAM,IAAI,KAAK,CAAC,qCAAqC,GAAG,YAAY,CAAC,CAAC;gBACvE,CAAC;YACF,CAAC,CAAC;YAED,MAAc,CAAC,oBAAoB,GAAG,CAAC,YAAoB,EAAE,EAAE;gBAC/D,MAAM,CAAC,GAAG,CAAE,MAAc,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,YAAY,CAAC,CAAC;gBACtF,IAAI,CAAC,CAAC;oBAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,GAAG,YAAY,CAAC,CAAC;gBACjE,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;gBAC5B,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;gBACzC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE;oBAAE,KAAK,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;gBAClE,OAAO,KAAK,CAAC;YACd,CAAC,CAAC;QACH,CAAC,CAAC;IACH,CAAC;IAED,cAAc;QACb,OAAO,+BAA+B,CAAC;IACxC,CAAC;CACD"}
|
||||
42
apps/macos/Sources/Clawdis/Resources/WebChat/components/sandbox/ConsoleRuntimeProvider.d.ts
vendored
Normal file
42
apps/macos/Sources/Clawdis/Resources/WebChat/components/sandbox/ConsoleRuntimeProvider.d.ts
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
import type { SandboxRuntimeProvider } from "./SandboxRuntimeProvider.js";
|
||||
export interface ConsoleLog {
|
||||
type: "log" | "warn" | "error" | "info";
|
||||
text: string;
|
||||
args?: unknown[];
|
||||
}
|
||||
/**
|
||||
* Console Runtime Provider
|
||||
*
|
||||
* REQUIRED provider that should always be included first.
|
||||
* Provides console capture, error handling, and execution lifecycle management.
|
||||
* Collects console output for retrieval by caller.
|
||||
*/
|
||||
export declare class ConsoleRuntimeProvider implements SandboxRuntimeProvider {
|
||||
private logs;
|
||||
private completionError;
|
||||
private completed;
|
||||
getData(): Record<string, any>;
|
||||
getDescription(): string;
|
||||
getRuntime(): (sandboxId: string) => void;
|
||||
handleMessage(message: any, respond: (response: any) => void): Promise<void>;
|
||||
/**
|
||||
* Get collected console logs
|
||||
*/
|
||||
getLogs(): ConsoleLog[];
|
||||
/**
|
||||
* Get completion status
|
||||
*/
|
||||
isCompleted(): boolean;
|
||||
/**
|
||||
* Get completion error if any
|
||||
*/
|
||||
getCompletionError(): {
|
||||
message: string;
|
||||
stack: string;
|
||||
} | null;
|
||||
/**
|
||||
* Reset state for reuse
|
||||
*/
|
||||
reset(): void;
|
||||
}
|
||||
//# sourceMappingURL=ConsoleRuntimeProvider.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ConsoleRuntimeProvider.d.ts","sourceRoot":"","sources":["../../../src/components/sandbox/ConsoleRuntimeProvider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAE1E,MAAM,WAAW,UAAU;IAC1B,IAAI,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,OAAO,EAAE,CAAC;CACjB;AAED;;;;;;GAMG;AACH,qBAAa,sBAAuB,YAAW,sBAAsB;IACpE,OAAO,CAAC,IAAI,CAAoB;IAChC,OAAO,CAAC,eAAe,CAAmD;IAC1E,OAAO,CAAC,SAAS,CAAS;IAE1B,OAAO,IAAI,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IAK9B,cAAc,IAAI,MAAM;IAIxB,UAAU,IAAI,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI;IA4GnC,aAAa,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBlF;;OAEG;IACH,OAAO,IAAI,UAAU,EAAE;IAIvB;;OAEG;IACH,WAAW,IAAI,OAAO;IAItB;;OAEG;IACH,kBAAkB,IAAI;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAI/D;;OAEG;IACH,KAAK,IAAI,IAAI;CAKb"}
|
||||
@@ -0,0 +1,161 @@
|
||||
/**
|
||||
* Console Runtime Provider
|
||||
*
|
||||
* REQUIRED provider that should always be included first.
|
||||
* Provides console capture, error handling, and execution lifecycle management.
|
||||
* Collects console output for retrieval by caller.
|
||||
*/
|
||||
export class ConsoleRuntimeProvider {
|
||||
constructor() {
|
||||
this.logs = [];
|
||||
this.completionError = null;
|
||||
this.completed = false;
|
||||
}
|
||||
getData() {
|
||||
// No data needed
|
||||
return {};
|
||||
}
|
||||
getDescription() {
|
||||
return "";
|
||||
}
|
||||
getRuntime() {
|
||||
return (_sandboxId) => {
|
||||
// Store truly original console methods on first wrap only
|
||||
// This prevents accumulation of wrapper functions across multiple executions
|
||||
if (!window.__originalConsole) {
|
||||
window.__originalConsole = {
|
||||
log: console.log.bind(console),
|
||||
error: console.error.bind(console),
|
||||
warn: console.warn.bind(console),
|
||||
info: console.info.bind(console),
|
||||
};
|
||||
}
|
||||
// Always use the truly original console, not the current (possibly wrapped) one
|
||||
const originalConsole = window.__originalConsole;
|
||||
// Track pending send promises to wait for them in onCompleted
|
||||
const pendingSends = [];
|
||||
["log", "error", "warn", "info"].forEach((method) => {
|
||||
console[method] = (...args) => {
|
||||
const text = args
|
||||
.map((arg) => {
|
||||
try {
|
||||
return typeof arg === "object" ? JSON.stringify(arg) : String(arg);
|
||||
}
|
||||
catch {
|
||||
return String(arg);
|
||||
}
|
||||
})
|
||||
.join(" ");
|
||||
// Always log locally too (using truly original console)
|
||||
originalConsole[method].apply(console, args);
|
||||
// Send immediately and track the promise (only in extension context)
|
||||
if (window.sendRuntimeMessage) {
|
||||
const sendPromise = window
|
||||
.sendRuntimeMessage({
|
||||
type: "console",
|
||||
method,
|
||||
text,
|
||||
args,
|
||||
})
|
||||
.catch(() => { });
|
||||
pendingSends.push(sendPromise);
|
||||
}
|
||||
};
|
||||
});
|
||||
// Register completion callback to wait for all pending sends
|
||||
if (window.onCompleted) {
|
||||
window.onCompleted(async (_success) => {
|
||||
// Wait for all pending console sends to complete
|
||||
if (pendingSends.length > 0) {
|
||||
await Promise.all(pendingSends);
|
||||
}
|
||||
});
|
||||
}
|
||||
// Track errors for HTML artifacts
|
||||
let lastError = null;
|
||||
// Error handlers - track errors but don't log them
|
||||
// (they'll be shown via execution-error message)
|
||||
window.addEventListener("error", (e) => {
|
||||
const text = (e.error?.stack || e.message || String(e)) + " at line " + (e.lineno || "?") + ":" + (e.colno || "?");
|
||||
lastError = {
|
||||
message: e.error?.message || e.message || String(e),
|
||||
stack: e.error?.stack || text,
|
||||
};
|
||||
});
|
||||
window.addEventListener("unhandledrejection", (e) => {
|
||||
const text = "Unhandled promise rejection: " + (e.reason?.message || e.reason || "Unknown error");
|
||||
lastError = {
|
||||
message: e.reason?.message || String(e.reason) || "Unhandled promise rejection",
|
||||
stack: e.reason?.stack || text,
|
||||
};
|
||||
});
|
||||
// Expose complete() method for user code to call
|
||||
let completionSent = false;
|
||||
window.complete = async (error, returnValue) => {
|
||||
if (completionSent)
|
||||
return;
|
||||
completionSent = true;
|
||||
const finalError = error || lastError;
|
||||
if (window.sendRuntimeMessage) {
|
||||
if (finalError) {
|
||||
await window.sendRuntimeMessage({
|
||||
type: "execution-error",
|
||||
error: finalError,
|
||||
});
|
||||
}
|
||||
else {
|
||||
await window.sendRuntimeMessage({
|
||||
type: "execution-complete",
|
||||
returnValue,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
async handleMessage(message, respond) {
|
||||
if (message.type === "console") {
|
||||
// Collect console output
|
||||
this.logs.push({
|
||||
type: message.method === "error"
|
||||
? "error"
|
||||
: message.method === "warn"
|
||||
? "warn"
|
||||
: message.method === "info"
|
||||
? "info"
|
||||
: "log",
|
||||
text: message.text,
|
||||
args: message.args,
|
||||
});
|
||||
// Acknowledge receipt
|
||||
respond({ success: true });
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get collected console logs
|
||||
*/
|
||||
getLogs() {
|
||||
return this.logs;
|
||||
}
|
||||
/**
|
||||
* Get completion status
|
||||
*/
|
||||
isCompleted() {
|
||||
return this.completed;
|
||||
}
|
||||
/**
|
||||
* Get completion error if any
|
||||
*/
|
||||
getCompletionError() {
|
||||
return this.completionError;
|
||||
}
|
||||
/**
|
||||
* Reset state for reuse
|
||||
*/
|
||||
reset() {
|
||||
this.logs = [];
|
||||
this.completionError = null;
|
||||
this.completed = false;
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=ConsoleRuntimeProvider.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ConsoleRuntimeProvider.js","sourceRoot":"","sources":["../../../src/components/sandbox/ConsoleRuntimeProvider.ts"],"names":[],"mappings":"AAQA;;;;;;GAMG;AACH,MAAM,OAAO,sBAAsB;IAAnC;QACS,SAAI,GAAiB,EAAE,CAAC;QACxB,oBAAe,GAA8C,IAAI,CAAC;QAClE,cAAS,GAAG,KAAK,CAAC;IAwK3B,CAAC;IAtKA,OAAO;QACN,iBAAiB;QACjB,OAAO,EAAE,CAAC;IACX,CAAC;IAED,cAAc;QACb,OAAO,EAAE,CAAC;IACX,CAAC;IAED,UAAU;QACT,OAAO,CAAC,UAAkB,EAAE,EAAE;YAC7B,0DAA0D;YAC1D,6EAA6E;YAC7E,IAAI,CAAE,MAAc,CAAC,iBAAiB,EAAE,CAAC;gBACvC,MAAc,CAAC,iBAAiB,GAAG;oBACnC,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC;oBAC9B,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC;oBAClC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC;oBAChC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC;iBAChC,CAAC;YACH,CAAC;YAED,gFAAgF;YAChF,MAAM,eAAe,GAAI,MAAc,CAAC,iBAAiB,CAAC;YAE1D,8DAA8D;YAC9D,MAAM,YAAY,GAAmB,EAAE,CAAC;YAExC,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE;gBAClD,OAAe,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,IAAW,EAAE,EAAE;oBAC7C,MAAM,IAAI,GAAG,IAAI;yBACf,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;wBACZ,IAAI,CAAC;4BACJ,OAAO,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;wBACpE,CAAC;wBAAC,MAAM,CAAC;4BACR,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;wBACpB,CAAC;oBACF,CAAC,CAAC;yBACD,IAAI,CAAC,GAAG,CAAC,CAAC;oBAEZ,wDAAwD;oBACvD,eAAuB,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;oBAEtD,qEAAqE;oBACrE,IAAK,MAAc,CAAC,kBAAkB,EAAE,CAAC;wBACxC,MAAM,WAAW,GAAI,MAAc;6BACjC,kBAAkB,CAAC;4BACnB,IAAI,EAAE,SAAS;4BACf,MAAM;4BACN,IAAI;4BACJ,IAAI;yBACJ,CAAC;6BACD,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;wBAClB,YAAY,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;oBAChC,CAAC;gBACF,CAAC,CAAC;YACH,CAAC,CAAC,CAAC;YAEH,6DAA6D;YAC7D,IAAK,MAAc,CAAC,WAAW,EAAE,CAAC;gBAChC,MAAc,CAAC,WAAW,CAAC,KAAK,EAAE,QAAiB,EAAE,EAAE;oBACvD,iDAAiD;oBACjD,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;wBAC7B,MAAM,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;oBACjC,CAAC;gBACF,CAAC,CAAC,CAAC;YACJ,CAAC;YAED,kCAAkC;YAClC,IAAI,SAAS,GAA8C,IAAI,CAAC;YAEhE,mDAAmD;YACnD,iDAAiD;YACjD,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE;gBACtC,MAAM,IAAI,GACT,CAAC,CAAC,CAAC,KAAK,EAAE,KAAK,IAAI,CAAC,CAAC,OAAO,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,WAAW,GAAG,CAAC,CAAC,CAAC,MAAM,IAAI,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,CAAC,CAAC;gBAEvG,SAAS,GAAG;oBACX,OAAO,EAAE,CAAC,CAAC,KAAK,EAAE,OAAO,IAAI,CAAC,CAAC,OAAO,IAAI,MAAM,CAAC,CAAC,CAAC;oBACnD,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,KAAK,IAAI,IAAI;iBAC7B,CAAC;YACH,CAAC,CAAC,CAAC;YAEH,MAAM,CAAC,gBAAgB,CAAC,oBAAoB,EAAE,CAAC,CAAC,EAAE,EAAE;gBACnD,MAAM,IAAI,GAAG,+BAA+B,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,OAAO,IAAI,CAAC,CAAC,MAAM,IAAI,eAAe,CAAC,CAAC;gBAElG,SAAS,GAAG;oBACX,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,OAAO,IAAI,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,6BAA6B;oBAC/E,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,KAAK,IAAI,IAAI;iBAC9B,CAAC;YACH,CAAC,CAAC,CAAC;YAEH,iDAAiD;YACjD,IAAI,cAAc,GAAG,KAAK,CAAC;YAC1B,MAAc,CAAC,QAAQ,GAAG,KAAK,EAAE,KAA0C,EAAE,WAAiB,EAAE,EAAE;gBAClG,IAAI,cAAc;oBAAE,OAAO;gBAC3B,cAAc,GAAG,IAAI,CAAC;gBAEtB,MAAM,UAAU,GAAG,KAAK,IAAI,SAAS,CAAC;gBAEtC,IAAK,MAAc,CAAC,kBAAkB,EAAE,CAAC;oBACxC,IAAI,UAAU,EAAE,CAAC;wBAChB,MAAO,MAAc,CAAC,kBAAkB,CAAC;4BACxC,IAAI,EAAE,iBAAiB;4BACvB,KAAK,EAAE,UAAU;yBACjB,CAAC,CAAC;oBACJ,CAAC;yBAAM,CAAC;wBACP,MAAO,MAAc,CAAC,kBAAkB,CAAC;4BACxC,IAAI,EAAE,oBAAoB;4BAC1B,WAAW;yBACX,CAAC,CAAC;oBACJ,CAAC;gBACF,CAAC;YACF,CAAC,CAAC;QACH,CAAC,CAAC;IACH,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,OAAY,EAAE,OAAgC;QACjE,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAChC,yBAAyB;YACzB,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;gBACd,IAAI,EACH,OAAO,CAAC,MAAM,KAAK,OAAO;oBACzB,CAAC,CAAC,OAAO;oBACT,CAAC,CAAC,OAAO,CAAC,MAAM,KAAK,MAAM;wBAC1B,CAAC,CAAC,MAAM;wBACR,CAAC,CAAC,OAAO,CAAC,MAAM,KAAK,MAAM;4BAC1B,CAAC,CAAC,MAAM;4BACR,CAAC,CAAC,KAAK;gBACX,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,IAAI,EAAE,OAAO,CAAC,IAAI;aAClB,CAAC,CAAC;YACH,sBAAsB;YACtB,OAAO,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5B,CAAC;IACF,CAAC;IAED;;OAEG;IACH,OAAO;QACN,OAAO,IAAI,CAAC,IAAI,CAAC;IAClB,CAAC;IAED;;OAEG;IACH,WAAW;QACV,OAAO,IAAI,CAAC,SAAS,CAAC;IACvB,CAAC;IAED;;OAEG;IACH,kBAAkB;QACjB,OAAO,IAAI,CAAC,eAAe,CAAC;IAC7B,CAAC;IAED;;OAEG;IACH,KAAK;QACJ,IAAI,CAAC,IAAI,GAAG,EAAE,CAAC;QACf,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC;QAC5B,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;IACxB,CAAC;CACD"}
|
||||
30
apps/macos/Sources/Clawdis/Resources/WebChat/components/sandbox/FileDownloadRuntimeProvider.d.ts
vendored
Normal file
30
apps/macos/Sources/Clawdis/Resources/WebChat/components/sandbox/FileDownloadRuntimeProvider.d.ts
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
import type { SandboxRuntimeProvider } from "./SandboxRuntimeProvider.js";
|
||||
export interface DownloadableFile {
|
||||
fileName: string;
|
||||
content: string | Uint8Array;
|
||||
mimeType: string;
|
||||
}
|
||||
/**
|
||||
* File Download Runtime Provider
|
||||
*
|
||||
* Provides returnDownloadableFile() for creating user downloads.
|
||||
* Files returned this way are NOT accessible to the LLM later (one-time download).
|
||||
* Works both online (sends to extension) and offline (triggers browser download directly).
|
||||
* Collects files for retrieval by caller.
|
||||
*/
|
||||
export declare class FileDownloadRuntimeProvider implements SandboxRuntimeProvider {
|
||||
private files;
|
||||
getData(): Record<string, any>;
|
||||
getRuntime(): (sandboxId: string) => void;
|
||||
handleMessage(message: any, respond: (response: any) => void): Promise<void>;
|
||||
/**
|
||||
* Get collected files
|
||||
*/
|
||||
getFiles(): DownloadableFile[];
|
||||
/**
|
||||
* Reset state for reuse
|
||||
*/
|
||||
reset(): void;
|
||||
getDescription(): string;
|
||||
}
|
||||
//# sourceMappingURL=FileDownloadRuntimeProvider.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"FileDownloadRuntimeProvider.d.ts","sourceRoot":"","sources":["../../../src/components/sandbox/FileDownloadRuntimeProvider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAE1E,MAAM,WAAW,gBAAgB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;GAOG;AACH,qBAAa,2BAA4B,YAAW,sBAAsB;IACzE,OAAO,CAAC,KAAK,CAA0B;IAEvC,OAAO,IAAI,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IAK9B,UAAU,IAAI,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI;IAuDnC,aAAa,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAalF;;OAEG;IACH,QAAQ,IAAI,gBAAgB,EAAE;IAI9B;;OAEG;IACH,KAAK,IAAI,IAAI;IAIb,cAAc,IAAI,MAAM;CAGxB"}
|
||||
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* File Download Runtime Provider
|
||||
*
|
||||
* Provides returnDownloadableFile() for creating user downloads.
|
||||
* Files returned this way are NOT accessible to the LLM later (one-time download).
|
||||
* Works both online (sends to extension) and offline (triggers browser download directly).
|
||||
* Collects files for retrieval by caller.
|
||||
*/
|
||||
export class FileDownloadRuntimeProvider {
|
||||
constructor() {
|
||||
this.files = [];
|
||||
}
|
||||
getData() {
|
||||
// No data needed
|
||||
return {};
|
||||
}
|
||||
getRuntime() {
|
||||
return (_sandboxId) => {
|
||||
window.returnDownloadableFile = async (fileName, content, mimeType) => {
|
||||
let finalContent, finalMimeType;
|
||||
if (content instanceof Blob) {
|
||||
const arrayBuffer = await content.arrayBuffer();
|
||||
finalContent = new Uint8Array(arrayBuffer);
|
||||
finalMimeType = mimeType || content.type || "application/octet-stream";
|
||||
if (!mimeType && !content.type) {
|
||||
throw new Error("returnDownloadableFile: MIME type is required for Blob content. Please provide a mimeType parameter (e.g., 'image/png').");
|
||||
}
|
||||
}
|
||||
else if (content instanceof Uint8Array) {
|
||||
finalContent = content;
|
||||
if (!mimeType) {
|
||||
throw new Error("returnDownloadableFile: MIME type is required for Uint8Array content. Please provide a mimeType parameter (e.g., 'image/png').");
|
||||
}
|
||||
finalMimeType = mimeType;
|
||||
}
|
||||
else if (typeof content === "string") {
|
||||
finalContent = content;
|
||||
finalMimeType = mimeType || "text/plain";
|
||||
}
|
||||
else {
|
||||
finalContent = JSON.stringify(content, null, 2);
|
||||
finalMimeType = mimeType || "application/json";
|
||||
}
|
||||
// Send to extension if in extension context (online mode)
|
||||
if (window.sendRuntimeMessage) {
|
||||
const response = await window.sendRuntimeMessage({
|
||||
type: "file-returned",
|
||||
fileName,
|
||||
content: finalContent,
|
||||
mimeType: finalMimeType,
|
||||
});
|
||||
if (response.error)
|
||||
throw new Error(response.error);
|
||||
}
|
||||
else {
|
||||
// Offline mode: trigger browser download directly
|
||||
const blob = new Blob([finalContent instanceof Uint8Array ? finalContent : finalContent], {
|
||||
type: finalMimeType,
|
||||
});
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = fileName;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
async handleMessage(message, respond) {
|
||||
if (message.type === "file-returned") {
|
||||
// Collect file for caller
|
||||
this.files.push({
|
||||
fileName: message.fileName,
|
||||
content: message.content,
|
||||
mimeType: message.mimeType,
|
||||
});
|
||||
respond({ success: true });
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get collected files
|
||||
*/
|
||||
getFiles() {
|
||||
return this.files;
|
||||
}
|
||||
/**
|
||||
* Reset state for reuse
|
||||
*/
|
||||
reset() {
|
||||
this.files = [];
|
||||
}
|
||||
getDescription() {
|
||||
return "returnDownloadableFile(filename, content, mimeType?) - Create downloadable file for user (one-time download, not accessible later)";
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=FileDownloadRuntimeProvider.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"FileDownloadRuntimeProvider.js","sourceRoot":"","sources":["../../../src/components/sandbox/FileDownloadRuntimeProvider.ts"],"names":[],"mappings":"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"}
|
||||
19
apps/macos/Sources/Clawdis/Resources/WebChat/components/sandbox/RuntimeMessageBridge.d.ts
vendored
Normal file
19
apps/macos/Sources/Clawdis/Resources/WebChat/components/sandbox/RuntimeMessageBridge.d.ts
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Generates sendRuntimeMessage() function for injection into execution contexts.
|
||||
* Provides unified messaging API that works in both sandbox iframe and user script contexts.
|
||||
*/
|
||||
export type MessageType = "request-response" | "fire-and-forget";
|
||||
export interface RuntimeMessageBridgeOptions {
|
||||
context: "sandbox-iframe" | "user-script";
|
||||
sandboxId: string;
|
||||
}
|
||||
export declare class RuntimeMessageBridge {
|
||||
/**
|
||||
* Generate sendRuntimeMessage() function as injectable string.
|
||||
* Returns the function source code to be injected into target context.
|
||||
*/
|
||||
static generateBridgeCode(options: RuntimeMessageBridgeOptions): string;
|
||||
private static generateSandboxBridge;
|
||||
private static generateUserScriptBridge;
|
||||
}
|
||||
//# sourceMappingURL=RuntimeMessageBridge.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"RuntimeMessageBridge.d.ts","sourceRoot":"","sources":["../../../src/components/sandbox/RuntimeMessageBridge.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,WAAW,GAAG,kBAAkB,GAAG,iBAAiB,CAAC;AAEjE,MAAM,WAAW,2BAA2B;IAC3C,OAAO,EAAE,gBAAgB,GAAG,aAAa,CAAC;IAC1C,SAAS,EAAE,MAAM,CAAC;CAClB;AAGD,qBAAa,oBAAoB;IAChC;;;OAGG;IACH,MAAM,CAAC,kBAAkB,CAAC,OAAO,EAAE,2BAA2B,GAAG,MAAM;IAQvE,OAAO,CAAC,MAAM,CAAC,qBAAqB;IAwCpC,OAAO,CAAC,MAAM,CAAC,wBAAwB;CAevC"}
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Generates sendRuntimeMessage() function for injection into execution contexts.
|
||||
* Provides unified messaging API that works in both sandbox iframe and user script contexts.
|
||||
*/
|
||||
// biome-ignore lint/complexity/noStaticOnlyClass: fine
|
||||
export class RuntimeMessageBridge {
|
||||
/**
|
||||
* Generate sendRuntimeMessage() function as injectable string.
|
||||
* Returns the function source code to be injected into target context.
|
||||
*/
|
||||
static generateBridgeCode(options) {
|
||||
if (options.context === "sandbox-iframe") {
|
||||
return RuntimeMessageBridge.generateSandboxBridge(options.sandboxId);
|
||||
}
|
||||
else {
|
||||
return RuntimeMessageBridge.generateUserScriptBridge(options.sandboxId);
|
||||
}
|
||||
}
|
||||
static generateSandboxBridge(sandboxId) {
|
||||
// Returns stringified function that uses window.parent.postMessage
|
||||
return `
|
||||
window.__completionCallbacks = [];
|
||||
window.sendRuntimeMessage = async (message) => {
|
||||
const messageId = 'msg_' + Date.now() + '_' + Math.random().toString(36).substring(2, 9);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const handler = (e) => {
|
||||
if (e.data.type === 'runtime-response' && e.data.messageId === messageId) {
|
||||
window.removeEventListener('message', handler);
|
||||
if (e.data.success) {
|
||||
resolve(e.data);
|
||||
} else {
|
||||
reject(new Error(e.data.error || 'Operation failed'));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('message', handler);
|
||||
|
||||
window.parent.postMessage({
|
||||
...message,
|
||||
sandboxId: ${JSON.stringify(sandboxId)},
|
||||
messageId: messageId
|
||||
}, '*');
|
||||
|
||||
// Timeout after 30s
|
||||
setTimeout(() => {
|
||||
window.removeEventListener('message', handler);
|
||||
reject(new Error('Runtime message timeout'));
|
||||
}, 30000);
|
||||
});
|
||||
};
|
||||
window.onCompleted = (callback) => {
|
||||
window.__completionCallbacks.push(callback);
|
||||
};
|
||||
`.trim();
|
||||
}
|
||||
static generateUserScriptBridge(sandboxId) {
|
||||
// Returns stringified function that uses chrome.runtime.sendMessage
|
||||
return `
|
||||
window.__completionCallbacks = [];
|
||||
window.sendRuntimeMessage = async (message) => {
|
||||
return await chrome.runtime.sendMessage({
|
||||
...message,
|
||||
sandboxId: ${JSON.stringify(sandboxId)}
|
||||
});
|
||||
};
|
||||
window.onCompleted = (callback) => {
|
||||
window.__completionCallbacks.push(callback);
|
||||
};
|
||||
`.trim();
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=RuntimeMessageBridge.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"RuntimeMessageBridge.js","sourceRoot":"","sources":["../../../src/components/sandbox/RuntimeMessageBridge.ts"],"names":[],"mappings":"AAAA;;;GAGG;AASH,uDAAuD;AACvD,MAAM,OAAO,oBAAoB;IAChC;;;OAGG;IACH,MAAM,CAAC,kBAAkB,CAAC,OAAoC;QAC7D,IAAI,OAAO,CAAC,OAAO,KAAK,gBAAgB,EAAE,CAAC;YAC1C,OAAO,oBAAoB,CAAC,qBAAqB,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QACtE,CAAC;aAAM,CAAC;YACP,OAAO,oBAAoB,CAAC,wBAAwB,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QACzE,CAAC;IACF,CAAC;IAEO,MAAM,CAAC,qBAAqB,CAAC,SAAiB;QACrD,mEAAmE;QACnE,OAAO;;;;;;;;;;;;;;;;;;;;;yBAqBgB,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC;;;;;;;;;;;;;;CAcjD,CAAC,IAAI,EAAE,CAAC;IACR,CAAC;IAEO,MAAM,CAAC,wBAAwB,CAAC,SAAiB;QACxD,oEAAoE;QACpE,OAAO;;;;;qBAKY,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC;;;;;;CAM7C,CAAC,IAAI,EAAE,CAAC;IACR,CAAC;CACD"}
|
||||
65
apps/macos/Sources/Clawdis/Resources/WebChat/components/sandbox/RuntimeMessageRouter.d.ts
vendored
Normal file
65
apps/macos/Sources/Clawdis/Resources/WebChat/components/sandbox/RuntimeMessageRouter.d.ts
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
import type { SandboxRuntimeProvider } from "./SandboxRuntimeProvider.js";
|
||||
/**
|
||||
* Message consumer interface - components that want to receive messages from sandboxes
|
||||
*/
|
||||
export interface MessageConsumer {
|
||||
/**
|
||||
* Handle a message from a sandbox.
|
||||
* All consumers receive all messages - decide internally what to handle.
|
||||
*/
|
||||
handleMessage(message: any): Promise<void>;
|
||||
}
|
||||
/**
|
||||
* Centralized message router for all runtime communication.
|
||||
*
|
||||
* This singleton replaces all individual window.addEventListener("message") calls
|
||||
* with a single global listener that routes messages to the appropriate handlers.
|
||||
* Also handles user script messages from chrome.runtime.onUserScriptMessage.
|
||||
*
|
||||
* Benefits:
|
||||
* - Single global listener instead of multiple independent listeners
|
||||
* - Automatic cleanup when sandboxes are destroyed
|
||||
* - Support for bidirectional communication (providers) and broadcasting (consumers)
|
||||
* - Works with both sandbox iframes and user scripts
|
||||
* - Clear lifecycle management
|
||||
*/
|
||||
export declare class RuntimeMessageRouter {
|
||||
private sandboxes;
|
||||
private messageListener;
|
||||
private userScriptMessageListener;
|
||||
/**
|
||||
* Register a new sandbox with its runtime providers.
|
||||
* Call this BEFORE creating the iframe (for sandbox contexts) or executing user script.
|
||||
*/
|
||||
registerSandbox(sandboxId: string, providers: SandboxRuntimeProvider[], consumers: MessageConsumer[]): void;
|
||||
/**
|
||||
* Update the iframe reference for a sandbox.
|
||||
* Call this AFTER creating the iframe.
|
||||
* This is needed so providers can send responses back to the sandbox.
|
||||
*/
|
||||
setSandboxIframe(sandboxId: string, iframe: HTMLIFrameElement): void;
|
||||
/**
|
||||
* Unregister a sandbox and remove all its consumers.
|
||||
* Call this when the sandbox is destroyed.
|
||||
*/
|
||||
unregisterSandbox(sandboxId: string): void;
|
||||
/**
|
||||
* Add a message consumer for a sandbox.
|
||||
* Consumers receive broadcast messages (console, execution-complete, etc.)
|
||||
*/
|
||||
addConsumer(sandboxId: string, consumer: MessageConsumer): void;
|
||||
/**
|
||||
* Remove a message consumer from a sandbox.
|
||||
*/
|
||||
removeConsumer(sandboxId: string, consumer: MessageConsumer): void;
|
||||
/**
|
||||
* Setup the global message listeners (called automatically)
|
||||
*/
|
||||
private setupListener;
|
||||
}
|
||||
/**
|
||||
* Global singleton instance.
|
||||
* Import this from wherever you need to interact with the message router.
|
||||
*/
|
||||
export declare const RUNTIME_MESSAGE_ROUTER: RuntimeMessageRouter;
|
||||
//# sourceMappingURL=RuntimeMessageRouter.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"RuntimeMessageRouter.d.ts","sourceRoot":"","sources":["../../../src/components/sandbox/RuntimeMessageRouter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAK1E;;GAEG;AACH,MAAM,WAAW,eAAe;IAC/B;;;OAGG;IACH,aAAa,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3C;AAYD;;;;;;;;;;;;;GAaG;AACH,qBAAa,oBAAoB;IAChC,OAAO,CAAC,SAAS,CAAqC;IACtD,OAAO,CAAC,eAAe,CAA4C;IACnE,OAAO,CAAC,yBAAyB,CAElB;IAEf;;;OAGG;IACH,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,sBAAsB,EAAE,EAAE,SAAS,EAAE,eAAe,EAAE,GAAG,IAAI;IAY3G;;;;OAIG;IACH,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,GAAG,IAAI;IAOpE;;;OAGG;IACH,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAmB1C;;;OAGG;IACH,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,GAAG,IAAI;IAO/D;;OAEG;IACH,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,GAAG,IAAI;IAOlE;;OAEG;IACH,OAAO,CAAC,aAAa;CAuFrB;AAED;;;GAGG;AACH,eAAO,MAAM,sBAAsB,sBAA6B,CAAC"}
|
||||
@@ -0,0 +1,168 @@
|
||||
/**
|
||||
* Centralized message router for all runtime communication.
|
||||
*
|
||||
* This singleton replaces all individual window.addEventListener("message") calls
|
||||
* with a single global listener that routes messages to the appropriate handlers.
|
||||
* Also handles user script messages from chrome.runtime.onUserScriptMessage.
|
||||
*
|
||||
* Benefits:
|
||||
* - Single global listener instead of multiple independent listeners
|
||||
* - Automatic cleanup when sandboxes are destroyed
|
||||
* - Support for bidirectional communication (providers) and broadcasting (consumers)
|
||||
* - Works with both sandbox iframes and user scripts
|
||||
* - Clear lifecycle management
|
||||
*/
|
||||
export class RuntimeMessageRouter {
|
||||
constructor() {
|
||||
this.sandboxes = new Map();
|
||||
this.messageListener = null;
|
||||
this.userScriptMessageListener = null;
|
||||
}
|
||||
/**
|
||||
* Register a new sandbox with its runtime providers.
|
||||
* Call this BEFORE creating the iframe (for sandbox contexts) or executing user script.
|
||||
*/
|
||||
registerSandbox(sandboxId, providers, consumers) {
|
||||
this.sandboxes.set(sandboxId, {
|
||||
sandboxId,
|
||||
iframe: null, // Will be set via setSandboxIframe() for sandbox contexts
|
||||
providers,
|
||||
consumers: new Set(consumers),
|
||||
});
|
||||
// Setup global listener if not already done
|
||||
this.setupListener();
|
||||
}
|
||||
/**
|
||||
* Update the iframe reference for a sandbox.
|
||||
* Call this AFTER creating the iframe.
|
||||
* This is needed so providers can send responses back to the sandbox.
|
||||
*/
|
||||
setSandboxIframe(sandboxId, iframe) {
|
||||
const context = this.sandboxes.get(sandboxId);
|
||||
if (context) {
|
||||
context.iframe = iframe;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Unregister a sandbox and remove all its consumers.
|
||||
* Call this when the sandbox is destroyed.
|
||||
*/
|
||||
unregisterSandbox(sandboxId) {
|
||||
this.sandboxes.delete(sandboxId);
|
||||
// If no more sandboxes, remove global listeners
|
||||
if (this.sandboxes.size === 0) {
|
||||
// Remove iframe listener
|
||||
if (this.messageListener) {
|
||||
window.removeEventListener("message", this.messageListener);
|
||||
this.messageListener = null;
|
||||
}
|
||||
// Remove user script listener
|
||||
if (this.userScriptMessageListener && typeof chrome !== "undefined" && chrome.runtime?.onUserScriptMessage) {
|
||||
chrome.runtime.onUserScriptMessage.removeListener(this.userScriptMessageListener);
|
||||
this.userScriptMessageListener = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Add a message consumer for a sandbox.
|
||||
* Consumers receive broadcast messages (console, execution-complete, etc.)
|
||||
*/
|
||||
addConsumer(sandboxId, consumer) {
|
||||
const context = this.sandboxes.get(sandboxId);
|
||||
if (context) {
|
||||
context.consumers.add(consumer);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Remove a message consumer from a sandbox.
|
||||
*/
|
||||
removeConsumer(sandboxId, consumer) {
|
||||
const context = this.sandboxes.get(sandboxId);
|
||||
if (context) {
|
||||
context.consumers.delete(consumer);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Setup the global message listeners (called automatically)
|
||||
*/
|
||||
setupListener() {
|
||||
// Setup sandbox iframe listener
|
||||
if (!this.messageListener) {
|
||||
this.messageListener = async (e) => {
|
||||
const { sandboxId, messageId } = e.data;
|
||||
if (!sandboxId)
|
||||
return;
|
||||
const context = this.sandboxes.get(sandboxId);
|
||||
if (!context) {
|
||||
return;
|
||||
}
|
||||
// Create respond() function for bidirectional communication
|
||||
const respond = (response) => {
|
||||
context.iframe?.contentWindow?.postMessage({
|
||||
type: "runtime-response",
|
||||
messageId,
|
||||
sandboxId,
|
||||
...response,
|
||||
}, "*");
|
||||
};
|
||||
// 1. Try provider handlers first (for bidirectional comm)
|
||||
for (const provider of context.providers) {
|
||||
if (provider.handleMessage) {
|
||||
await provider.handleMessage(e.data, respond);
|
||||
// Don't stop - let consumers also handle the message
|
||||
}
|
||||
}
|
||||
// 2. Broadcast to consumers (one-way messages or lifecycle events)
|
||||
for (const consumer of context.consumers) {
|
||||
await consumer.handleMessage(e.data);
|
||||
// Don't stop - let all consumers see the message
|
||||
}
|
||||
};
|
||||
window.addEventListener("message", this.messageListener);
|
||||
}
|
||||
// Setup user script message listener
|
||||
if (!this.userScriptMessageListener) {
|
||||
// Guard: check if we're in extension context
|
||||
if (typeof chrome === "undefined" || !chrome.runtime?.onUserScriptMessage) {
|
||||
return;
|
||||
}
|
||||
this.userScriptMessageListener = (message, _sender, sendResponse) => {
|
||||
const { sandboxId } = message;
|
||||
if (!sandboxId)
|
||||
return false;
|
||||
const context = this.sandboxes.get(sandboxId);
|
||||
if (!context)
|
||||
return false;
|
||||
const respond = (response) => {
|
||||
sendResponse({
|
||||
...response,
|
||||
sandboxId,
|
||||
});
|
||||
};
|
||||
// Route to providers (async)
|
||||
(async () => {
|
||||
// 1. Try provider handlers first (for bidirectional comm)
|
||||
for (const provider of context.providers) {
|
||||
if (provider.handleMessage) {
|
||||
await provider.handleMessage(message, respond);
|
||||
// Don't stop - let consumers also handle the message
|
||||
}
|
||||
}
|
||||
// 2. Broadcast to consumers (one-way messages or lifecycle events)
|
||||
for (const consumer of context.consumers) {
|
||||
await consumer.handleMessage(message);
|
||||
// Don't stop - let all consumers see the message
|
||||
}
|
||||
})();
|
||||
return true; // Indicates async response
|
||||
};
|
||||
chrome.runtime.onUserScriptMessage.addListener(this.userScriptMessageListener);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Global singleton instance.
|
||||
* Import this from wherever you need to interact with the message router.
|
||||
*/
|
||||
export const RUNTIME_MESSAGE_ROUTER = new RuntimeMessageRouter();
|
||||
//# sourceMappingURL=RuntimeMessageRouter.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"RuntimeMessageRouter.js","sourceRoot":"","sources":["../../../src/components/sandbox/RuntimeMessageRouter.ts"],"names":[],"mappings":"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"}
|
||||
48
apps/macos/Sources/Clawdis/Resources/WebChat/components/sandbox/SandboxRuntimeProvider.d.ts
vendored
Normal file
48
apps/macos/Sources/Clawdis/Resources/WebChat/components/sandbox/SandboxRuntimeProvider.d.ts
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Interface for providing runtime capabilities to sandboxed iframes.
|
||||
* Each provider injects data and runtime functions into the sandbox context.
|
||||
*/
|
||||
export interface SandboxRuntimeProvider {
|
||||
/**
|
||||
* Returns data to inject into window scope.
|
||||
* Keys become window properties (e.g., { attachments: [...] } -> window.attachments)
|
||||
*/
|
||||
getData(): Record<string, any>;
|
||||
/**
|
||||
* Returns a runtime function that will be stringified and executed in the sandbox.
|
||||
* The function receives sandboxId and has access to data from getData() via window.
|
||||
*
|
||||
* IMPORTANT: This function will be converted to string via .toString() and injected
|
||||
* into the sandbox, so it cannot reference external variables or imports.
|
||||
*/
|
||||
getRuntime(): (sandboxId: string) => void;
|
||||
/**
|
||||
* Optional message handler for bidirectional communication.
|
||||
* All providers receive all messages - decide internally what to handle.
|
||||
*
|
||||
* @param message - The message from the sandbox
|
||||
* @param respond - Function to send a response back to the sandbox
|
||||
*/
|
||||
handleMessage?(message: any, respond: (response: any) => void): Promise<void>;
|
||||
/**
|
||||
* Optional documentation describing what globals/functions this provider injects.
|
||||
* This will be appended to tool descriptions dynamically so the LLM knows what's available.
|
||||
*/
|
||||
getDescription(): string;
|
||||
/**
|
||||
* Optional lifecycle callback invoked when sandbox execution starts.
|
||||
* Providers can use this to track abort signals for cancellation of async operations.
|
||||
*
|
||||
* @param sandboxId - The unique identifier for this sandbox execution
|
||||
* @param signal - Optional AbortSignal that will be triggered if execution is cancelled
|
||||
*/
|
||||
onExecutionStart?(sandboxId: string, signal?: AbortSignal): void;
|
||||
/**
|
||||
* Optional lifecycle callback invoked when sandbox execution ends (success, error, or abort).
|
||||
* Providers can use this to clean up any resources associated with the sandbox.
|
||||
*
|
||||
* @param sandboxId - The unique identifier for this sandbox execution
|
||||
*/
|
||||
onExecutionEnd?(sandboxId: string): void;
|
||||
}
|
||||
//# sourceMappingURL=SandboxRuntimeProvider.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"SandboxRuntimeProvider.d.ts","sourceRoot":"","sources":["../../../src/components/sandbox/SandboxRuntimeProvider.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,sBAAsB;IACtC;;;OAGG;IACH,OAAO,IAAI,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAE/B;;;;;;OAMG;IACH,UAAU,IAAI,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAE1C;;;;;;OAMG;IACH,aAAa,CAAC,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE9E;;;OAGG;IACH,cAAc,IAAI,MAAM,CAAC;IAEzB;;;;;;OAMG;IACH,gBAAgB,CAAC,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAEjE;;;;;OAKG;IACH,cAAc,CAAC,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CACzC"}
|
||||
@@ -0,0 +1,2 @@
|
||||
export {};
|
||||
//# sourceMappingURL=SandboxRuntimeProvider.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"SandboxRuntimeProvider.js","sourceRoot":"","sources":["../../../src/components/sandbox/SandboxRuntimeProvider.ts"],"names":[],"mappings":""}
|
||||
Reference in New Issue
Block a user