mac: bundle web chat assets
This commit is contained in:
15
apps/macos/Sources/Clawdis/Resources/WebChat/dialogs/ApiKeyPromptDialog.d.ts
vendored
Normal file
15
apps/macos/Sources/Clawdis/Resources/WebChat/dialogs/ApiKeyPromptDialog.d.ts
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
import "../components/ProviderKeyInput.js";
|
||||
import { DialogBase } from "@mariozechner/mini-lit/dist/DialogBase.js";
|
||||
export declare class ApiKeyPromptDialog extends DialogBase {
|
||||
private provider;
|
||||
private resolvePromise?;
|
||||
private unsubscribe?;
|
||||
protected modalWidth: string;
|
||||
protected modalHeight: string;
|
||||
static prompt(provider: string): Promise<boolean>;
|
||||
connectedCallback(): Promise<void>;
|
||||
disconnectedCallback(): void;
|
||||
close(): void;
|
||||
protected renderContent(): import("lit-html").TemplateResult<1>;
|
||||
}
|
||||
//# sourceMappingURL=ApiKeyPromptDialog.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ApiKeyPromptDialog.d.ts","sourceRoot":"","sources":["../../src/dialogs/ApiKeyPromptDialog.ts"],"names":[],"mappings":"AACA,OAAO,mCAAmC,CAAC;AAE3C,OAAO,EAAE,UAAU,EAAE,MAAM,2CAA2C,CAAC;AAKvE,qBACa,kBAAmB,SAAQ,UAAU;IACxC,OAAO,CAAC,QAAQ,CAAM;IAE/B,OAAO,CAAC,cAAc,CAAC,CAA6B;IACpD,OAAO,CAAC,WAAW,CAAC,CAAa;IAEjC,SAAS,CAAC,UAAU,SAAsB;IAC1C,SAAS,CAAC,WAAW,SAAU;WAElB,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAUxC,iBAAiB;IAmBvB,oBAAoB;IAQpB,KAAK;cAOK,aAAa;CAYhC"}
|
||||
@@ -0,0 +1,79 @@
|
||||
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;
|
||||
};
|
||||
var ApiKeyPromptDialog_1;
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
import "../components/ProviderKeyInput.js";
|
||||
import { DialogContent, DialogHeader } from "@mariozechner/mini-lit/dist/Dialog.js";
|
||||
import { DialogBase } from "@mariozechner/mini-lit/dist/DialogBase.js";
|
||||
import { html } from "lit";
|
||||
import { getAppStorage } from "../storage/app-storage.js";
|
||||
import { i18n } from "../utils/i18n.js";
|
||||
let ApiKeyPromptDialog = ApiKeyPromptDialog_1 = class ApiKeyPromptDialog extends DialogBase {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.provider = "";
|
||||
this.modalWidth = "min(500px, 90vw)";
|
||||
this.modalHeight = "auto";
|
||||
}
|
||||
static async prompt(provider) {
|
||||
const dialog = new ApiKeyPromptDialog_1();
|
||||
dialog.provider = provider;
|
||||
dialog.open();
|
||||
return new Promise((resolve) => {
|
||||
dialog.resolvePromise = resolve;
|
||||
});
|
||||
}
|
||||
async connectedCallback() {
|
||||
super.connectedCallback();
|
||||
// Poll for key existence - when key is added, resolve and close
|
||||
const checkInterval = setInterval(async () => {
|
||||
const hasKey = !!(await getAppStorage().providerKeys.get(this.provider));
|
||||
if (hasKey) {
|
||||
clearInterval(checkInterval);
|
||||
if (this.resolvePromise) {
|
||||
this.resolvePromise(true);
|
||||
this.resolvePromise = undefined;
|
||||
}
|
||||
this.close();
|
||||
}
|
||||
}, 500);
|
||||
this.unsubscribe = () => clearInterval(checkInterval);
|
||||
}
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
if (this.unsubscribe) {
|
||||
this.unsubscribe();
|
||||
this.unsubscribe = undefined;
|
||||
}
|
||||
}
|
||||
close() {
|
||||
super.close();
|
||||
if (this.resolvePromise) {
|
||||
this.resolvePromise(false);
|
||||
}
|
||||
}
|
||||
renderContent() {
|
||||
return html `
|
||||
${DialogContent({
|
||||
children: html `
|
||||
${DialogHeader({
|
||||
title: i18n("API Key Required"),
|
||||
})}
|
||||
<provider-key-input .provider=${this.provider}></provider-key-input>
|
||||
`,
|
||||
})}
|
||||
`;
|
||||
}
|
||||
};
|
||||
__decorate([
|
||||
state()
|
||||
], ApiKeyPromptDialog.prototype, "provider", void 0);
|
||||
ApiKeyPromptDialog = ApiKeyPromptDialog_1 = __decorate([
|
||||
customElement("api-key-prompt-dialog")
|
||||
], ApiKeyPromptDialog);
|
||||
export { ApiKeyPromptDialog };
|
||||
//# sourceMappingURL=ApiKeyPromptDialog.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ApiKeyPromptDialog.js","sourceRoot":"","sources":["../../src/dialogs/ApiKeyPromptDialog.ts"],"names":[],"mappings":";;;;;;;AAAA,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,mCAAmC,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAC;AACpF,OAAO,EAAE,UAAU,EAAE,MAAM,2CAA2C,CAAC;AACvE,OAAO,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAC3B,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAGjC,IAAM,kBAAkB,0BAAxB,MAAM,kBAAmB,SAAQ,UAAU;IAA3C;;QACW,aAAQ,GAAG,EAAE,CAAC;QAKrB,eAAU,GAAG,kBAAkB,CAAC;QAChC,gBAAW,GAAG,MAAM,CAAC;IA0DhC,CAAC;IAxDA,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,QAAgB;QACnC,MAAM,MAAM,GAAG,IAAI,oBAAkB,EAAE,CAAC;QACxC,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAC;QAC3B,MAAM,CAAC,IAAI,EAAE,CAAC;QAEd,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YAC9B,MAAM,CAAC,cAAc,GAAG,OAAO,CAAC;QACjC,CAAC,CAAC,CAAC;IACJ,CAAC;IAEQ,KAAK,CAAC,iBAAiB;QAC/B,KAAK,CAAC,iBAAiB,EAAE,CAAC;QAE1B,gEAAgE;QAChE,MAAM,aAAa,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;YAC5C,MAAM,MAAM,GAAG,CAAC,CAAC,CAAC,MAAM,aAAa,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;YACzE,IAAI,MAAM,EAAE,CAAC;gBACZ,aAAa,CAAC,aAAa,CAAC,CAAC;gBAC7B,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;oBACzB,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;oBAC1B,IAAI,CAAC,cAAc,GAAG,SAAS,CAAC;gBACjC,CAAC;gBACD,IAAI,CAAC,KAAK,EAAE,CAAC;YACd,CAAC;QACF,CAAC,EAAE,GAAG,CAAC,CAAC;QAER,IAAI,CAAC,WAAW,GAAG,GAAG,EAAE,CAAC,aAAa,CAAC,aAAa,CAAC,CAAC;IACvD,CAAC;IAEQ,oBAAoB;QAC5B,KAAK,CAAC,oBAAoB,EAAE,CAAC;QAC7B,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACtB,IAAI,CAAC,WAAW,EAAE,CAAC;YACnB,IAAI,CAAC,WAAW,GAAG,SAAS,CAAC;QAC9B,CAAC;IACF,CAAC;IAEQ,KAAK;QACb,KAAK,CAAC,KAAK,EAAE,CAAC;QACd,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACzB,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;QAC5B,CAAC;IACF,CAAC;IAEkB,aAAa;QAC/B,OAAO,IAAI,CAAA;KACR,aAAa,CAAC;YACf,QAAQ,EAAE,IAAI,CAAA;OACX,YAAY,CAAC;gBACd,KAAK,EAAE,IAAI,CAAC,kBAAkB,CAAC;aAC/B,CAAC;qCAC8B,IAAI,CAAC,QAAQ;KAC7C;SACD,CAAC;GACF,CAAC;IACH,CAAC;CACD,CAAA;AAhEiB;IAAhB,KAAK,EAAE;oDAAuB;AADnB,kBAAkB;IAD9B,aAAa,CAAC,uBAAuB,CAAC;GAC1B,kBAAkB,CAiE9B"}
|
||||
32
apps/macos/Sources/Clawdis/Resources/WebChat/dialogs/AttachmentOverlay.d.ts
vendored
Normal file
32
apps/macos/Sources/Clawdis/Resources/WebChat/dialogs/AttachmentOverlay.d.ts
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
import "@mariozechner/mini-lit/dist/ModeToggle.js";
|
||||
import { LitElement } from "lit";
|
||||
import type { Attachment } from "../utils/attachment-utils.js";
|
||||
export declare class AttachmentOverlay extends LitElement {
|
||||
private attachment?;
|
||||
private showExtractedText;
|
||||
private error;
|
||||
private currentLoadingTask;
|
||||
private onCloseCallback?;
|
||||
private boundHandleKeyDown?;
|
||||
protected createRenderRoot(): HTMLElement | DocumentFragment;
|
||||
static open(attachment: Attachment, onClose?: () => void): void;
|
||||
private setupEventListeners;
|
||||
private close;
|
||||
private getFileType;
|
||||
private getFileTypeLabel;
|
||||
private handleBackdropClick;
|
||||
private handleDownload;
|
||||
private cleanup;
|
||||
render(): import("lit-html").TemplateResult<1>;
|
||||
private renderToggle;
|
||||
private renderContent;
|
||||
private renderFileContent;
|
||||
updated(changedProperties: Map<string, any>): Promise<void>;
|
||||
private renderPdf;
|
||||
private renderDocx;
|
||||
private renderExcel;
|
||||
private renderExcelSheet;
|
||||
private base64ToArrayBuffer;
|
||||
private renderExtractedText;
|
||||
}
|
||||
//# sourceMappingURL=AttachmentOverlay.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"AttachmentOverlay.d.ts","sourceRoot":"","sources":["../../src/dialogs/AttachmentOverlay.ts"],"names":[],"mappings":"AAAA,OAAO,2CAA2C,CAAC;AAInD,OAAO,EAAQ,UAAU,EAAE,MAAM,KAAK,CAAC;AAKvC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAK/D,qBAAa,iBAAkB,SAAQ,UAAU;IACvC,OAAO,CAAC,UAAU,CAAC,CAAa;IAChC,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,KAAK,CAAuB;IAG7C,OAAO,CAAC,kBAAkB,CAAa;IACvC,OAAO,CAAC,eAAe,CAAC,CAAa;IACrC,OAAO,CAAC,kBAAkB,CAAC,CAA6B;cAErC,gBAAgB,IAAI,WAAW,GAAG,gBAAgB;IAIrE,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,MAAM,IAAI;IAQxD,OAAO,CAAC,mBAAmB;IAS3B,OAAO,CAAC,KAAK;IASb,OAAO,CAAC,WAAW;IAsBnB,OAAO,CAAC,gBAAgB;IAgBxB,OAAO,CAAC,mBAAmB,CAEzB;IAEF,OAAO,CAAC,cAAc,CAqBpB;IAEF,OAAO,CAAC,OAAO;IAUN,MAAM;IAwCf,OAAO,CAAC,YAAY;IAwBpB,OAAO,CAAC,aAAa;IAiBrB,OAAO,CAAC,iBAAiB;IA+DV,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC;YA6B5C,SAAS;YAgFT,UAAU;YAqGV,WAAW;IA2EzB,OAAO,CAAC,gBAAgB;IAyCxB,OAAO,CAAC,mBAAmB;YASb,mBAAmB;CAsBjC"}
|
||||
@@ -0,0 +1,576 @@
|
||||
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 "@mariozechner/mini-lit/dist/ModeToggle.js";
|
||||
import { icon } from "@mariozechner/mini-lit";
|
||||
import { Button } from "@mariozechner/mini-lit/dist/Button.js";
|
||||
import { renderAsync } from "docx-preview";
|
||||
import { html, LitElement } from "lit";
|
||||
import { state } from "lit/decorators.js";
|
||||
import { Download, X } from "lucide";
|
||||
import * as pdfjsLib from "pdfjs-dist";
|
||||
import * as XLSX from "xlsx";
|
||||
import { i18n } from "../utils/i18n.js";
|
||||
export class AttachmentOverlay extends LitElement {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.showExtractedText = false;
|
||||
this.error = null;
|
||||
// Track current loading task to cancel if needed
|
||||
this.currentLoadingTask = null;
|
||||
this.handleBackdropClick = () => {
|
||||
this.close();
|
||||
};
|
||||
this.handleDownload = () => {
|
||||
if (!this.attachment)
|
||||
return;
|
||||
// Create a blob from the base64 content
|
||||
const byteCharacters = atob(this.attachment.content);
|
||||
const byteNumbers = new Array(byteCharacters.length);
|
||||
for (let i = 0; i < byteCharacters.length; i++) {
|
||||
byteNumbers[i] = byteCharacters.charCodeAt(i);
|
||||
}
|
||||
const byteArray = new Uint8Array(byteNumbers);
|
||||
const blob = new Blob([byteArray], { type: this.attachment.mimeType });
|
||||
// Create download link
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = this.attachment.fileName;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
}
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
static open(attachment, onClose) {
|
||||
const overlay = new AttachmentOverlay();
|
||||
overlay.attachment = attachment;
|
||||
overlay.onCloseCallback = onClose;
|
||||
document.body.appendChild(overlay);
|
||||
overlay.setupEventListeners();
|
||||
}
|
||||
setupEventListeners() {
|
||||
this.boundHandleKeyDown = (e) => {
|
||||
if (e.key === "Escape") {
|
||||
this.close();
|
||||
}
|
||||
};
|
||||
window.addEventListener("keydown", this.boundHandleKeyDown);
|
||||
}
|
||||
close() {
|
||||
this.cleanup();
|
||||
if (this.boundHandleKeyDown) {
|
||||
window.removeEventListener("keydown", this.boundHandleKeyDown);
|
||||
}
|
||||
this.onCloseCallback?.();
|
||||
this.remove();
|
||||
}
|
||||
getFileType() {
|
||||
if (!this.attachment)
|
||||
return "text";
|
||||
if (this.attachment.type === "image")
|
||||
return "image";
|
||||
if (this.attachment.mimeType === "application/pdf")
|
||||
return "pdf";
|
||||
if (this.attachment.mimeType?.includes("wordprocessingml"))
|
||||
return "docx";
|
||||
if (this.attachment.mimeType?.includes("presentationml") ||
|
||||
this.attachment.fileName.toLowerCase().endsWith(".pptx"))
|
||||
return "pptx";
|
||||
if (this.attachment.mimeType?.includes("spreadsheetml") ||
|
||||
this.attachment.mimeType?.includes("ms-excel") ||
|
||||
this.attachment.fileName.toLowerCase().endsWith(".xlsx") ||
|
||||
this.attachment.fileName.toLowerCase().endsWith(".xls"))
|
||||
return "excel";
|
||||
return "text";
|
||||
}
|
||||
getFileTypeLabel() {
|
||||
const type = this.getFileType();
|
||||
switch (type) {
|
||||
case "pdf":
|
||||
return i18n("PDF");
|
||||
case "docx":
|
||||
return i18n("Document");
|
||||
case "pptx":
|
||||
return i18n("Presentation");
|
||||
case "excel":
|
||||
return i18n("Spreadsheet");
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
cleanup() {
|
||||
this.showExtractedText = false;
|
||||
this.error = null;
|
||||
// Cancel any loading PDF task when closing
|
||||
if (this.currentLoadingTask) {
|
||||
this.currentLoadingTask.destroy();
|
||||
this.currentLoadingTask = null;
|
||||
}
|
||||
}
|
||||
render() {
|
||||
if (!this.attachment)
|
||||
return html ``;
|
||||
return html `
|
||||
<!-- Full screen overlay -->
|
||||
<div class="fixed inset-0 bg-black/90 z-50 flex flex-col" @click=${this.handleBackdropClick}>
|
||||
<!-- Compact header bar -->
|
||||
<div class="bg-background/95 backdrop-blur border-b border-border" @click=${(e) => e.stopPropagation()}>
|
||||
<div class="px-4 py-2 flex items-center justify-between">
|
||||
<div class="flex items-center gap-3 min-w-0">
|
||||
<span class="text-sm font-medium text-foreground truncate">${this.attachment.fileName}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
${this.renderToggle()}
|
||||
${Button({
|
||||
variant: "ghost",
|
||||
size: "icon",
|
||||
onClick: this.handleDownload,
|
||||
children: icon(Download, "sm"),
|
||||
className: "h-8 w-8",
|
||||
})}
|
||||
${Button({
|
||||
variant: "ghost",
|
||||
size: "icon",
|
||||
onClick: () => this.close(),
|
||||
children: icon(X, "sm"),
|
||||
className: "h-8 w-8",
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content container -->
|
||||
<div class="flex-1 flex items-center justify-center overflow-auto" @click=${(e) => e.stopPropagation()}>
|
||||
${this.renderContent()}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
renderToggle() {
|
||||
if (!this.attachment)
|
||||
return html ``;
|
||||
const fileType = this.getFileType();
|
||||
const hasExtractedText = !!this.attachment.extractedText;
|
||||
const showToggle = fileType !== "image" && fileType !== "text" && fileType !== "pptx" && hasExtractedText;
|
||||
if (!showToggle)
|
||||
return html ``;
|
||||
const fileTypeLabel = this.getFileTypeLabel();
|
||||
return html `
|
||||
<mode-toggle
|
||||
.modes=${[fileTypeLabel, i18n("Text")]}
|
||||
.selectedIndex=${this.showExtractedText ? 1 : 0}
|
||||
@mode-change=${(e) => {
|
||||
e.stopPropagation();
|
||||
this.showExtractedText = e.detail.index === 1;
|
||||
this.error = null;
|
||||
}}
|
||||
></mode-toggle>
|
||||
`;
|
||||
}
|
||||
renderContent() {
|
||||
if (!this.attachment)
|
||||
return html ``;
|
||||
// Error state
|
||||
if (this.error) {
|
||||
return html `
|
||||
<div class="bg-destructive/10 border border-destructive text-destructive p-4 rounded-lg max-w-2xl">
|
||||
<div class="font-medium mb-1">${i18n("Error loading file")}</div>
|
||||
<div class="text-sm opacity-90">${this.error}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
// Content based on file type
|
||||
return this.renderFileContent();
|
||||
}
|
||||
renderFileContent() {
|
||||
if (!this.attachment)
|
||||
return html ``;
|
||||
const fileType = this.getFileType();
|
||||
// Show extracted text if toggled
|
||||
if (this.showExtractedText && fileType !== "image") {
|
||||
return html `
|
||||
<div class="bg-card border border-border text-foreground p-6 w-full h-full max-w-4xl overflow-auto">
|
||||
<pre class="whitespace-pre-wrap font-mono text-xs leading-relaxed">${this.attachment.extractedText || i18n("No text content available")}</pre>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
// Render based on file type
|
||||
switch (fileType) {
|
||||
case "image": {
|
||||
const imageUrl = `data:${this.attachment.mimeType};base64,${this.attachment.content}`;
|
||||
return html `
|
||||
<img src="${imageUrl}" class="max-w-full max-h-full object-contain rounded-lg shadow-lg" alt="${this.attachment.fileName}" />
|
||||
`;
|
||||
}
|
||||
case "pdf":
|
||||
return html `
|
||||
<div
|
||||
id="pdf-container"
|
||||
class="bg-card text-foreground overflow-auto shadow-lg border border-border w-full h-full max-w-[1000px]"
|
||||
></div>
|
||||
`;
|
||||
case "docx":
|
||||
return html `
|
||||
<div
|
||||
id="docx-container"
|
||||
class="bg-card text-foreground overflow-auto shadow-lg border border-border w-full h-full max-w-[1000px]"
|
||||
></div>
|
||||
`;
|
||||
case "excel":
|
||||
return html ` <div id="excel-container" class="bg-card text-foreground overflow-auto w-full h-full"></div> `;
|
||||
case "pptx":
|
||||
return html `
|
||||
<div
|
||||
id="pptx-container"
|
||||
class="bg-card text-foreground overflow-auto shadow-lg border border-border w-full h-full max-w-[1000px]"
|
||||
></div>
|
||||
`;
|
||||
default:
|
||||
return html `
|
||||
<div class="bg-card border border-border text-foreground p-6 w-full h-full max-w-4xl overflow-auto">
|
||||
<pre class="whitespace-pre-wrap font-mono text-sm">${this.attachment.extractedText || i18n("No content available")}</pre>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
async updated(changedProperties) {
|
||||
super.updated(changedProperties);
|
||||
// Only process if we need to render the actual file (not extracted text)
|
||||
if ((changedProperties.has("attachment") || changedProperties.has("showExtractedText")) &&
|
||||
this.attachment &&
|
||||
!this.showExtractedText &&
|
||||
!this.error) {
|
||||
const fileType = this.getFileType();
|
||||
switch (fileType) {
|
||||
case "pdf":
|
||||
await this.renderPdf();
|
||||
break;
|
||||
case "docx":
|
||||
await this.renderDocx();
|
||||
break;
|
||||
case "excel":
|
||||
await this.renderExcel();
|
||||
break;
|
||||
case "pptx":
|
||||
await this.renderExtractedText();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
async renderPdf() {
|
||||
const container = this.querySelector("#pdf-container");
|
||||
if (!container || !this.attachment)
|
||||
return;
|
||||
let pdf = null;
|
||||
try {
|
||||
// Convert base64 to ArrayBuffer
|
||||
const arrayBuffer = this.base64ToArrayBuffer(this.attachment.content);
|
||||
// Cancel any existing loading task
|
||||
if (this.currentLoadingTask) {
|
||||
this.currentLoadingTask.destroy();
|
||||
}
|
||||
// Load the PDF
|
||||
this.currentLoadingTask = pdfjsLib.getDocument({ data: arrayBuffer });
|
||||
pdf = await this.currentLoadingTask.promise;
|
||||
this.currentLoadingTask = null;
|
||||
// Clear container and add wrapper
|
||||
container.innerHTML = "";
|
||||
const wrapper = document.createElement("div");
|
||||
wrapper.className = "";
|
||||
container.appendChild(wrapper);
|
||||
// Render all pages
|
||||
for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) {
|
||||
const page = await pdf.getPage(pageNum);
|
||||
// Create a container for each page
|
||||
const pageContainer = document.createElement("div");
|
||||
pageContainer.className = "mb-4 last:mb-0";
|
||||
// Create canvas for this page
|
||||
const canvas = document.createElement("canvas");
|
||||
const context = canvas.getContext("2d");
|
||||
// Set scale for reasonable resolution
|
||||
const viewport = page.getViewport({ scale: 1.5 });
|
||||
canvas.height = viewport.height;
|
||||
canvas.width = viewport.width;
|
||||
// Style the canvas
|
||||
canvas.className = "w-full max-w-full h-auto block mx-auto bg-white rounded shadow-sm border border-border";
|
||||
// Fill white background for proper PDF rendering
|
||||
if (context) {
|
||||
context.fillStyle = "white";
|
||||
context.fillRect(0, 0, canvas.width, canvas.height);
|
||||
}
|
||||
// Render page
|
||||
await page.render({
|
||||
canvasContext: context,
|
||||
viewport: viewport,
|
||||
canvas: canvas,
|
||||
}).promise;
|
||||
pageContainer.appendChild(canvas);
|
||||
// Add page separator for multi-page documents
|
||||
if (pageNum < pdf.numPages) {
|
||||
const separator = document.createElement("div");
|
||||
separator.className = "h-px bg-border my-4";
|
||||
pageContainer.appendChild(separator);
|
||||
}
|
||||
wrapper.appendChild(pageContainer);
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Error rendering PDF:", error);
|
||||
this.error = error?.message || i18n("Failed to load PDF");
|
||||
}
|
||||
finally {
|
||||
if (pdf) {
|
||||
pdf.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
async renderDocx() {
|
||||
const container = this.querySelector("#docx-container");
|
||||
if (!container || !this.attachment)
|
||||
return;
|
||||
try {
|
||||
// Convert base64 to ArrayBuffer
|
||||
const arrayBuffer = this.base64ToArrayBuffer(this.attachment.content);
|
||||
// Clear container first
|
||||
container.innerHTML = "";
|
||||
// Create a wrapper div for the document
|
||||
const wrapper = document.createElement("div");
|
||||
wrapper.className = "docx-wrapper-custom";
|
||||
container.appendChild(wrapper);
|
||||
// Render the DOCX file into the wrapper
|
||||
await renderAsync(arrayBuffer, wrapper, undefined, {
|
||||
className: "docx",
|
||||
inWrapper: true,
|
||||
ignoreWidth: true, // Let it be responsive
|
||||
ignoreHeight: false,
|
||||
ignoreFonts: false,
|
||||
breakPages: true,
|
||||
ignoreLastRenderedPageBreak: true,
|
||||
experimental: false,
|
||||
trimXmlDeclaration: true,
|
||||
useBase64URL: false,
|
||||
renderHeaders: true,
|
||||
renderFooters: true,
|
||||
renderFootnotes: true,
|
||||
renderEndnotes: true,
|
||||
});
|
||||
// Apply custom styles to match theme and fix sizing
|
||||
const style = document.createElement("style");
|
||||
style.textContent = `
|
||||
#docx-container {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#docx-container .docx-wrapper-custom {
|
||||
max-width: 100%;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
#docx-container .docx-wrapper {
|
||||
max-width: 100% !important;
|
||||
margin: 0 !important;
|
||||
background: transparent !important;
|
||||
padding: 0em !important;
|
||||
}
|
||||
|
||||
#docx-container .docx-wrapper > section.docx {
|
||||
box-shadow: none !important;
|
||||
border: none !important;
|
||||
border-radius: 0 !important;
|
||||
margin: 0 !important;
|
||||
padding: 2em !important;
|
||||
background: white !important;
|
||||
color: black !important;
|
||||
max-width: 100% !important;
|
||||
width: 100% !important;
|
||||
min-width: 0 !important;
|
||||
overflow-x: auto !important;
|
||||
}
|
||||
|
||||
/* Fix tables and wide content */
|
||||
#docx-container table {
|
||||
max-width: 100% !important;
|
||||
width: auto !important;
|
||||
overflow-x: auto !important;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
#docx-container img {
|
||||
max-width: 100% !important;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
/* Fix paragraphs and text */
|
||||
#docx-container p,
|
||||
#docx-container span,
|
||||
#docx-container div {
|
||||
max-width: 100% !important;
|
||||
word-wrap: break-word !important;
|
||||
overflow-wrap: break-word !important;
|
||||
}
|
||||
|
||||
/* Hide page breaks in web view */
|
||||
#docx-container .docx-page-break {
|
||||
display: none !important;
|
||||
}
|
||||
`;
|
||||
container.appendChild(style);
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Error rendering DOCX:", error);
|
||||
this.error = error?.message || i18n("Failed to load document");
|
||||
}
|
||||
}
|
||||
async renderExcel() {
|
||||
const container = this.querySelector("#excel-container");
|
||||
if (!container || !this.attachment)
|
||||
return;
|
||||
try {
|
||||
// Convert base64 to ArrayBuffer
|
||||
const arrayBuffer = this.base64ToArrayBuffer(this.attachment.content);
|
||||
// Read the workbook
|
||||
const workbook = XLSX.read(arrayBuffer, { type: "array" });
|
||||
// Clear container
|
||||
container.innerHTML = "";
|
||||
const wrapper = document.createElement("div");
|
||||
wrapper.className = "overflow-auto h-full flex flex-col";
|
||||
container.appendChild(wrapper);
|
||||
// Create tabs for multiple sheets
|
||||
if (workbook.SheetNames.length > 1) {
|
||||
const tabContainer = document.createElement("div");
|
||||
tabContainer.className = "flex gap-2 mb-4 border-b border-border sticky top-0 bg-card z-10";
|
||||
const sheetContents = [];
|
||||
workbook.SheetNames.forEach((sheetName, index) => {
|
||||
// Create tab button
|
||||
const tab = document.createElement("button");
|
||||
tab.textContent = sheetName;
|
||||
tab.className =
|
||||
index === 0
|
||||
? "px-4 py-2 text-sm font-medium border-b-2 border-primary text-primary"
|
||||
: "px-4 py-2 text-sm font-medium text-muted-foreground hover:text-foreground hover:border-b-2 hover:border-border transition-colors";
|
||||
// Create sheet content
|
||||
const sheetDiv = document.createElement("div");
|
||||
sheetDiv.style.display = index === 0 ? "flex" : "none";
|
||||
sheetDiv.className = "flex-1 overflow-auto";
|
||||
sheetDiv.appendChild(this.renderExcelSheet(workbook.Sheets[sheetName], sheetName));
|
||||
sheetContents.push(sheetDiv);
|
||||
// Tab click handler
|
||||
tab.onclick = () => {
|
||||
// Update tab styles
|
||||
tabContainer.querySelectorAll("button").forEach((btn, btnIndex) => {
|
||||
if (btnIndex === index) {
|
||||
btn.className = "px-4 py-2 text-sm font-medium border-b-2 border-primary text-primary";
|
||||
}
|
||||
else {
|
||||
btn.className =
|
||||
"px-4 py-2 text-sm font-medium text-muted-foreground hover:text-foreground hover:border-b-2 hover:border-border transition-colors";
|
||||
}
|
||||
});
|
||||
// Show/hide sheets
|
||||
sheetContents.forEach((content, contentIndex) => {
|
||||
content.style.display = contentIndex === index ? "flex" : "none";
|
||||
});
|
||||
};
|
||||
tabContainer.appendChild(tab);
|
||||
});
|
||||
wrapper.appendChild(tabContainer);
|
||||
sheetContents.forEach((content) => {
|
||||
wrapper.appendChild(content);
|
||||
});
|
||||
}
|
||||
else {
|
||||
// Single sheet
|
||||
const sheetName = workbook.SheetNames[0];
|
||||
wrapper.appendChild(this.renderExcelSheet(workbook.Sheets[sheetName], sheetName));
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Error rendering Excel:", error);
|
||||
this.error = error?.message || i18n("Failed to load spreadsheet");
|
||||
}
|
||||
}
|
||||
renderExcelSheet(worksheet, sheetName) {
|
||||
const sheetDiv = document.createElement("div");
|
||||
// Generate HTML table
|
||||
const htmlTable = XLSX.utils.sheet_to_html(worksheet, { id: `sheet-${sheetName}` });
|
||||
const tempDiv = document.createElement("div");
|
||||
tempDiv.innerHTML = htmlTable;
|
||||
// Find and style the table
|
||||
const table = tempDiv.querySelector("table");
|
||||
if (table) {
|
||||
table.className = "w-full border-collapse text-foreground";
|
||||
// Style all cells
|
||||
table.querySelectorAll("td, th").forEach((cell) => {
|
||||
const cellEl = cell;
|
||||
cellEl.className = "border border-border px-3 py-2 text-sm text-left";
|
||||
});
|
||||
// Style header row
|
||||
const headerCells = table.querySelectorAll("thead th, tr:first-child td");
|
||||
if (headerCells.length > 0) {
|
||||
headerCells.forEach((th) => {
|
||||
const thEl = th;
|
||||
thEl.className =
|
||||
"border border-border px-3 py-2 text-sm font-semibold bg-muted text-foreground sticky top-0";
|
||||
});
|
||||
}
|
||||
// Alternate row colors
|
||||
table.querySelectorAll("tbody tr:nth-child(even)").forEach((row) => {
|
||||
const rowEl = row;
|
||||
rowEl.className = "bg-muted/30";
|
||||
});
|
||||
sheetDiv.appendChild(table);
|
||||
}
|
||||
return sheetDiv;
|
||||
}
|
||||
base64ToArrayBuffer(base64) {
|
||||
const binaryString = atob(base64);
|
||||
const bytes = new Uint8Array(binaryString.length);
|
||||
for (let i = 0; i < binaryString.length; i++) {
|
||||
bytes[i] = binaryString.charCodeAt(i);
|
||||
}
|
||||
return bytes.buffer;
|
||||
}
|
||||
async renderExtractedText() {
|
||||
const container = this.querySelector("#pptx-container");
|
||||
if (!container || !this.attachment)
|
||||
return;
|
||||
try {
|
||||
// Display the extracted text content
|
||||
container.innerHTML = "";
|
||||
const wrapper = document.createElement("div");
|
||||
wrapper.className = "p-6 overflow-auto";
|
||||
// Create a pre element to preserve formatting
|
||||
const pre = document.createElement("pre");
|
||||
pre.className = "whitespace-pre-wrap text-sm text-foreground font-mono";
|
||||
pre.textContent = this.attachment.extractedText || i18n("No text content available");
|
||||
wrapper.appendChild(pre);
|
||||
container.appendChild(wrapper);
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Error rendering extracted text:", error);
|
||||
this.error = error?.message || i18n("Failed to display text content");
|
||||
}
|
||||
}
|
||||
}
|
||||
__decorate([
|
||||
state()
|
||||
], AttachmentOverlay.prototype, "attachment", void 0);
|
||||
__decorate([
|
||||
state()
|
||||
], AttachmentOverlay.prototype, "showExtractedText", void 0);
|
||||
__decorate([
|
||||
state()
|
||||
], AttachmentOverlay.prototype, "error", void 0);
|
||||
// Register the custom element only once
|
||||
if (!customElements.get("attachment-overlay")) {
|
||||
customElements.define("attachment-overlay", AttachmentOverlay);
|
||||
}
|
||||
//# sourceMappingURL=AttachmentOverlay.js.map
|
||||
File diff suppressed because one or more lines are too long
25
apps/macos/Sources/Clawdis/Resources/WebChat/dialogs/CustomProviderDialog.d.ts
vendored
Normal file
25
apps/macos/Sources/Clawdis/Resources/WebChat/dialogs/CustomProviderDialog.d.ts
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
import { DialogBase } from "@mariozechner/mini-lit/dist/DialogBase.js";
|
||||
import { type TemplateResult } from "lit";
|
||||
import type { CustomProvider, CustomProviderType } from "../storage/stores/custom-providers-store.js";
|
||||
export declare class CustomProviderDialog extends DialogBase {
|
||||
private provider?;
|
||||
private initialType?;
|
||||
private onSaveCallback?;
|
||||
private name;
|
||||
private type;
|
||||
private baseUrl;
|
||||
private apiKey;
|
||||
private testing;
|
||||
private testError;
|
||||
private discoveredModels;
|
||||
protected modalWidth: string;
|
||||
protected modalHeight: string;
|
||||
static open(provider: CustomProvider | undefined, initialType: CustomProviderType | undefined, onSave?: () => void): Promise<void>;
|
||||
private initializeFromProvider;
|
||||
private updateDefaultBaseUrl;
|
||||
private isAutoDiscoveryType;
|
||||
private testConnection;
|
||||
private save;
|
||||
protected renderContent(): TemplateResult;
|
||||
}
|
||||
//# sourceMappingURL=CustomProviderDialog.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"CustomProviderDialog.d.ts","sourceRoot":"","sources":["../../src/dialogs/CustomProviderDialog.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,2CAA2C,CAAC;AAKvE,OAAO,EAAQ,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAGhD,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,6CAA6C,CAAC;AAGtG,qBAAa,oBAAqB,SAAQ,UAAU;IACnD,OAAO,CAAC,QAAQ,CAAC,CAAiB;IAClC,OAAO,CAAC,WAAW,CAAC,CAAqB;IACzC,OAAO,CAAC,cAAc,CAAC,CAAa;IAE3B,OAAO,CAAC,IAAI,CAAM;IAClB,OAAO,CAAC,IAAI,CAA4C;IACxD,OAAO,CAAC,OAAO,CAAM;IACrB,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAM;IACvB,OAAO,CAAC,gBAAgB,CAAoB;IAErD,SAAS,CAAC,UAAU,SAAsB;IAC1C,SAAS,CAAC,WAAW,SAAsB;WAE9B,IAAI,CAChB,QAAQ,EAAE,cAAc,GAAG,SAAS,EACpC,WAAW,EAAE,kBAAkB,GAAG,SAAS,EAC3C,MAAM,CAAC,EAAE,MAAM,IAAI;IAYpB,OAAO,CAAC,sBAAsB;IAmB9B,OAAO,CAAC,oBAAoB;IAgB5B,OAAO,CAAC,mBAAmB;YAIb,cAAc;YA6Bd,IAAI;cA8BC,aAAa,IAAI,cAAc;CAiIlD"}
|
||||
@@ -0,0 +1,270 @@
|
||||
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 { DialogBase } from "@mariozechner/mini-lit/dist/DialogBase.js";
|
||||
import { Input } from "@mariozechner/mini-lit/dist/Input.js";
|
||||
import { Label } from "@mariozechner/mini-lit/dist/Label.js";
|
||||
import { Select } from "@mariozechner/mini-lit/dist/Select.js";
|
||||
import { html } from "lit";
|
||||
import { state } from "lit/decorators.js";
|
||||
import { getAppStorage } from "../storage/app-storage.js";
|
||||
import { discoverModels } from "../utils/model-discovery.js";
|
||||
export class CustomProviderDialog extends DialogBase {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.name = "";
|
||||
this.type = "openai-completions";
|
||||
this.baseUrl = "";
|
||||
this.apiKey = "";
|
||||
this.testing = false;
|
||||
this.testError = "";
|
||||
this.discoveredModels = [];
|
||||
this.modalWidth = "min(800px, 90vw)";
|
||||
this.modalHeight = "min(700px, 90vh)";
|
||||
}
|
||||
static async open(provider, initialType, onSave) {
|
||||
const dialog = new CustomProviderDialog();
|
||||
dialog.provider = provider;
|
||||
dialog.initialType = initialType;
|
||||
dialog.onSaveCallback = onSave;
|
||||
document.body.appendChild(dialog);
|
||||
dialog.initializeFromProvider();
|
||||
dialog.open();
|
||||
dialog.requestUpdate();
|
||||
}
|
||||
initializeFromProvider() {
|
||||
if (this.provider) {
|
||||
this.name = this.provider.name;
|
||||
this.type = this.provider.type;
|
||||
this.baseUrl = this.provider.baseUrl;
|
||||
this.apiKey = this.provider.apiKey || "";
|
||||
this.discoveredModels = this.provider.models || [];
|
||||
}
|
||||
else {
|
||||
this.name = "";
|
||||
this.type = this.initialType || "openai-completions";
|
||||
this.baseUrl = "";
|
||||
this.updateDefaultBaseUrl();
|
||||
this.apiKey = "";
|
||||
this.discoveredModels = [];
|
||||
}
|
||||
this.testError = "";
|
||||
this.testing = false;
|
||||
}
|
||||
updateDefaultBaseUrl() {
|
||||
if (this.baseUrl)
|
||||
return;
|
||||
const defaults = {
|
||||
ollama: "http://localhost:11434",
|
||||
"llama.cpp": "http://localhost:8080",
|
||||
vllm: "http://localhost:8000",
|
||||
lmstudio: "http://localhost:1234",
|
||||
"openai-completions": "",
|
||||
"openai-responses": "",
|
||||
"anthropic-messages": "",
|
||||
};
|
||||
this.baseUrl = defaults[this.type] || "";
|
||||
}
|
||||
isAutoDiscoveryType() {
|
||||
return this.type === "ollama" || this.type === "llama.cpp" || this.type === "vllm" || this.type === "lmstudio";
|
||||
}
|
||||
async testConnection() {
|
||||
if (!this.isAutoDiscoveryType())
|
||||
return;
|
||||
this.testing = true;
|
||||
this.testError = "";
|
||||
this.discoveredModels = [];
|
||||
try {
|
||||
const models = await discoverModels(this.type, this.baseUrl, this.apiKey || undefined);
|
||||
this.discoveredModels = models.map((model) => ({
|
||||
...model,
|
||||
provider: this.name || this.type,
|
||||
}));
|
||||
this.testError = "";
|
||||
}
|
||||
catch (error) {
|
||||
this.testError = error instanceof Error ? error.message : String(error);
|
||||
this.discoveredModels = [];
|
||||
}
|
||||
finally {
|
||||
this.testing = false;
|
||||
this.requestUpdate();
|
||||
}
|
||||
}
|
||||
async save() {
|
||||
if (!this.name || !this.baseUrl) {
|
||||
alert(i18n("Please fill in all required fields"));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const storage = getAppStorage();
|
||||
const provider = {
|
||||
id: this.provider?.id || crypto.randomUUID(),
|
||||
name: this.name,
|
||||
type: this.type,
|
||||
baseUrl: this.baseUrl,
|
||||
apiKey: this.apiKey || undefined,
|
||||
models: this.isAutoDiscoveryType() ? undefined : this.provider?.models || [],
|
||||
};
|
||||
await storage.customProviders.set(provider);
|
||||
if (this.onSaveCallback) {
|
||||
this.onSaveCallback();
|
||||
}
|
||||
this.close();
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Failed to save provider:", error);
|
||||
alert(i18n("Failed to save provider"));
|
||||
}
|
||||
}
|
||||
renderContent() {
|
||||
const providerTypes = [
|
||||
{ value: "ollama", label: "Ollama (auto-discovery)" },
|
||||
{ value: "llama.cpp", label: "llama.cpp (auto-discovery)" },
|
||||
{ value: "vllm", label: "vLLM (auto-discovery)" },
|
||||
{ value: "lmstudio", label: "LM Studio (auto-discovery)" },
|
||||
{ value: "openai-completions", label: "OpenAI Completions Compatible" },
|
||||
{ value: "openai-responses", label: "OpenAI Responses Compatible" },
|
||||
{ value: "anthropic-messages", label: "Anthropic Messages Compatible" },
|
||||
];
|
||||
return html `
|
||||
<div class="flex flex-col h-full overflow-hidden">
|
||||
<div class="p-6 flex-shrink-0 border-b border-border">
|
||||
<h2 class="text-lg font-semibold text-foreground">
|
||||
${this.provider ? i18n("Edit Provider") : i18n("Add Provider")}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 overflow-y-auto p-6">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex flex-col gap-2">
|
||||
${Label({ htmlFor: "provider-name", children: i18n("Provider Name") })}
|
||||
${Input({
|
||||
value: this.name,
|
||||
placeholder: i18n("e.g., My Ollama Server"),
|
||||
onInput: (e) => {
|
||||
this.name = e.target.value;
|
||||
this.requestUpdate();
|
||||
},
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
${Label({ htmlFor: "provider-type", children: i18n("Provider Type") })}
|
||||
${Select({
|
||||
value: this.type,
|
||||
options: providerTypes.map((pt) => ({
|
||||
value: pt.value,
|
||||
label: pt.label,
|
||||
})),
|
||||
onChange: (value) => {
|
||||
this.type = value;
|
||||
this.baseUrl = "";
|
||||
this.updateDefaultBaseUrl();
|
||||
this.requestUpdate();
|
||||
},
|
||||
width: "100%",
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
${Label({ htmlFor: "base-url", children: i18n("Base URL") })}
|
||||
${Input({
|
||||
value: this.baseUrl,
|
||||
placeholder: i18n("e.g., http://localhost:11434"),
|
||||
onInput: (e) => {
|
||||
this.baseUrl = e.target.value;
|
||||
this.requestUpdate();
|
||||
},
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
${Label({ htmlFor: "api-key", children: i18n("API Key (Optional)") })}
|
||||
${Input({
|
||||
type: "password",
|
||||
value: this.apiKey,
|
||||
placeholder: i18n("Leave empty if not required"),
|
||||
onInput: (e) => {
|
||||
this.apiKey = e.target.value;
|
||||
this.requestUpdate();
|
||||
},
|
||||
})}
|
||||
</div>
|
||||
|
||||
${this.isAutoDiscoveryType()
|
||||
? html `
|
||||
<div class="flex flex-col gap-2">
|
||||
${Button({
|
||||
onClick: () => this.testConnection(),
|
||||
variant: "outline",
|
||||
disabled: this.testing || !this.baseUrl,
|
||||
children: this.testing ? i18n("Testing...") : i18n("Test Connection"),
|
||||
})}
|
||||
${this.testError ? html ` <div class="text-sm text-destructive">${this.testError}</div> ` : ""}
|
||||
${this.discoveredModels.length > 0
|
||||
? html `
|
||||
<div class="text-sm text-muted-foreground">
|
||||
${i18n("Discovered")} ${this.discoveredModels.length} ${i18n("models")}:
|
||||
<ul class="list-disc list-inside mt-2">
|
||||
${this.discoveredModels.slice(0, 5).map((model) => html `<li>${model.name}</li>`)}
|
||||
${this.discoveredModels.length > 5
|
||||
? html `<li>...${i18n("and")} ${this.discoveredModels.length - 5} ${i18n("more")}</li>`
|
||||
: ""}
|
||||
</ul>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`
|
||||
: html ` <div class="text-sm text-muted-foreground">
|
||||
${i18n("For manual provider types, add models after saving the provider.")}
|
||||
</div>`}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-6 flex-shrink-0 border-t border-border flex justify-end gap-2">
|
||||
${Button({
|
||||
onClick: () => this.close(),
|
||||
variant: "ghost",
|
||||
children: i18n("Cancel"),
|
||||
})}
|
||||
${Button({
|
||||
onClick: () => this.save(),
|
||||
variant: "default",
|
||||
disabled: !this.name || !this.baseUrl,
|
||||
children: i18n("Save"),
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
__decorate([
|
||||
state()
|
||||
], CustomProviderDialog.prototype, "name", void 0);
|
||||
__decorate([
|
||||
state()
|
||||
], CustomProviderDialog.prototype, "type", void 0);
|
||||
__decorate([
|
||||
state()
|
||||
], CustomProviderDialog.prototype, "baseUrl", void 0);
|
||||
__decorate([
|
||||
state()
|
||||
], CustomProviderDialog.prototype, "apiKey", void 0);
|
||||
__decorate([
|
||||
state()
|
||||
], CustomProviderDialog.prototype, "testing", void 0);
|
||||
__decorate([
|
||||
state()
|
||||
], CustomProviderDialog.prototype, "testError", void 0);
|
||||
__decorate([
|
||||
state()
|
||||
], CustomProviderDialog.prototype, "discoveredModels", void 0);
|
||||
customElements.define("custom-provider-dialog", CustomProviderDialog);
|
||||
//# sourceMappingURL=CustomProviderDialog.js.map
|
||||
File diff suppressed because one or more lines are too long
27
apps/macos/Sources/Clawdis/Resources/WebChat/dialogs/ModelSelector.d.ts
vendored
Normal file
27
apps/macos/Sources/Clawdis/Resources/WebChat/dialogs/ModelSelector.d.ts
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
import { DialogBase } from "@mariozechner/mini-lit/dist/DialogBase.js";
|
||||
import { type Model } from "@mariozechner/pi-ai";
|
||||
import { type PropertyValues, type TemplateResult } from "lit";
|
||||
export declare class ModelSelector extends DialogBase {
|
||||
currentModel: Model<any> | null;
|
||||
searchQuery: string;
|
||||
filterThinking: boolean;
|
||||
filterVision: boolean;
|
||||
customProvidersLoading: boolean;
|
||||
selectedIndex: number;
|
||||
private navigationMode;
|
||||
private customProviderModels;
|
||||
private onSelectCallback?;
|
||||
private scrollContainerRef;
|
||||
private searchInputRef;
|
||||
private lastMousePosition;
|
||||
protected modalWidth: string;
|
||||
static open(currentModel: Model<any> | null, onSelect: (model: Model<any>) => void): Promise<void>;
|
||||
firstUpdated(changedProperties: PropertyValues): Promise<void>;
|
||||
private loadCustomProviders;
|
||||
private formatTokens;
|
||||
private handleSelect;
|
||||
private getFilteredModels;
|
||||
private scrollToSelected;
|
||||
protected renderContent(): TemplateResult;
|
||||
}
|
||||
//# sourceMappingURL=ModelSelector.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ModelSelector.d.ts","sourceRoot":"","sources":["../../src/dialogs/ModelSelector.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,UAAU,EAAE,MAAM,2CAA2C,CAAC;AACvE,OAAO,EAA2B,KAAK,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAC1E,OAAO,EAAQ,KAAK,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAWrE,qBACa,aAAc,SAAQ,UAAU;IACnC,YAAY,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,CAAQ;IACvC,WAAW,SAAM;IACjB,cAAc,UAAS;IACvB,YAAY,UAAS;IACrB,sBAAsB,UAAS;IAC/B,aAAa,SAAK;IAClB,OAAO,CAAC,cAAc,CAAiC;IACvD,OAAO,CAAC,oBAAoB,CAAoB;IAEzD,OAAO,CAAC,gBAAgB,CAAC,CAA8B;IACvD,OAAO,CAAC,kBAAkB,CAA+B;IACzD,OAAO,CAAC,cAAc,CAAiC;IACvD,OAAO,CAAC,iBAAiB,CAAkB;IAE3C,UAAmB,UAAU,SAAsB;WAEtC,IAAI,CAAC,YAAY,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,IAAI;IAQzE,YAAY,CAAC,iBAAiB,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;YAuD/D,mBAAmB;IA+CjC,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,iBAAiB;IAiDzB,OAAO,CAAC,gBAAgB;cAYL,aAAa,IAAI,cAAc;CA0FlD"}
|
||||
@@ -0,0 +1,320 @@
|
||||
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;
|
||||
};
|
||||
var ModelSelector_1;
|
||||
import { icon } from "@mariozechner/mini-lit";
|
||||
import { Badge } from "@mariozechner/mini-lit/dist/Badge.js";
|
||||
import { Button } from "@mariozechner/mini-lit/dist/Button.js";
|
||||
import { DialogHeader } from "@mariozechner/mini-lit/dist/Dialog.js";
|
||||
import { DialogBase } from "@mariozechner/mini-lit/dist/DialogBase.js";
|
||||
import { getModels, getProviders } from "@mariozechner/pi-ai";
|
||||
import { html } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
import { createRef, ref } from "lit/directives/ref.js";
|
||||
import { Brain, Image as ImageIcon } from "lucide";
|
||||
import { Input } from "../components/Input.js";
|
||||
import { getAppStorage } from "../storage/app-storage.js";
|
||||
import { formatModelCost } from "../utils/format.js";
|
||||
import { i18n } from "../utils/i18n.js";
|
||||
import { discoverModels } from "../utils/model-discovery.js";
|
||||
let ModelSelector = ModelSelector_1 = class ModelSelector extends DialogBase {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.currentModel = null;
|
||||
this.searchQuery = "";
|
||||
this.filterThinking = false;
|
||||
this.filterVision = false;
|
||||
this.customProvidersLoading = false;
|
||||
this.selectedIndex = 0;
|
||||
this.navigationMode = "mouse";
|
||||
this.customProviderModels = [];
|
||||
this.scrollContainerRef = createRef();
|
||||
this.searchInputRef = createRef();
|
||||
this.lastMousePosition = { x: 0, y: 0 };
|
||||
this.modalWidth = "min(400px, 90vw)";
|
||||
}
|
||||
static async open(currentModel, onSelect) {
|
||||
const selector = new ModelSelector_1();
|
||||
selector.currentModel = currentModel;
|
||||
selector.onSelectCallback = onSelect;
|
||||
selector.open();
|
||||
selector.loadCustomProviders();
|
||||
}
|
||||
async firstUpdated(changedProperties) {
|
||||
super.firstUpdated(changedProperties);
|
||||
// Wait for dialog to be fully rendered
|
||||
await this.updateComplete;
|
||||
// Focus the search input when dialog opens
|
||||
this.searchInputRef.value?.focus();
|
||||
// Track actual mouse movement
|
||||
this.addEventListener("mousemove", (e) => {
|
||||
// Check if mouse actually moved
|
||||
if (e.clientX !== this.lastMousePosition.x || e.clientY !== this.lastMousePosition.y) {
|
||||
this.lastMousePosition = { x: e.clientX, y: e.clientY };
|
||||
// Only switch to mouse mode on actual mouse movement
|
||||
if (this.navigationMode === "keyboard") {
|
||||
this.navigationMode = "mouse";
|
||||
// Update selection to the item under the mouse
|
||||
const target = e.target;
|
||||
const modelItem = target.closest("[data-model-item]");
|
||||
if (modelItem) {
|
||||
const allItems = this.scrollContainerRef.value?.querySelectorAll("[data-model-item]");
|
||||
if (allItems) {
|
||||
const index = Array.from(allItems).indexOf(modelItem);
|
||||
if (index !== -1) {
|
||||
this.selectedIndex = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
// Add global keyboard handler for the dialog
|
||||
this.addEventListener("keydown", (e) => {
|
||||
// Get filtered models to know the bounds
|
||||
const filteredModels = this.getFilteredModels();
|
||||
if (e.key === "ArrowDown") {
|
||||
e.preventDefault();
|
||||
this.navigationMode = "keyboard";
|
||||
this.selectedIndex = Math.min(this.selectedIndex + 1, filteredModels.length - 1);
|
||||
this.scrollToSelected();
|
||||
}
|
||||
else if (e.key === "ArrowUp") {
|
||||
e.preventDefault();
|
||||
this.navigationMode = "keyboard";
|
||||
this.selectedIndex = Math.max(this.selectedIndex - 1, 0);
|
||||
this.scrollToSelected();
|
||||
}
|
||||
else if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
if (filteredModels[this.selectedIndex]) {
|
||||
this.handleSelect(filteredModels[this.selectedIndex].model);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
async loadCustomProviders() {
|
||||
this.customProvidersLoading = true;
|
||||
const allCustomModels = [];
|
||||
try {
|
||||
const storage = getAppStorage();
|
||||
const customProviders = await storage.customProviders.getAll();
|
||||
// Load models from custom providers
|
||||
for (const provider of customProviders) {
|
||||
const isAutoDiscovery = provider.type === "ollama" ||
|
||||
provider.type === "llama.cpp" ||
|
||||
provider.type === "vllm" ||
|
||||
provider.type === "lmstudio";
|
||||
if (isAutoDiscovery) {
|
||||
try {
|
||||
const models = await discoverModels(provider.type, provider.baseUrl, provider.apiKey);
|
||||
const modelsWithProvider = models.map((model) => ({
|
||||
...model,
|
||||
provider: provider.name,
|
||||
}));
|
||||
allCustomModels.push(...modelsWithProvider);
|
||||
}
|
||||
catch (error) {
|
||||
console.debug(`Failed to load models from ${provider.name}:`, error);
|
||||
}
|
||||
}
|
||||
else if (provider.models) {
|
||||
// Manual provider - models already defined
|
||||
allCustomModels.push(...provider.models);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Failed to load custom providers:", error);
|
||||
}
|
||||
finally {
|
||||
this.customProviderModels = allCustomModels;
|
||||
this.customProvidersLoading = false;
|
||||
this.requestUpdate();
|
||||
}
|
||||
}
|
||||
formatTokens(tokens) {
|
||||
if (tokens >= 1000000)
|
||||
return `${(tokens / 1000000).toFixed(0)}M`;
|
||||
if (tokens >= 1000)
|
||||
return `${(tokens / 1000).toFixed(0)}`;
|
||||
return String(tokens);
|
||||
}
|
||||
handleSelect(model) {
|
||||
if (model) {
|
||||
this.onSelectCallback?.(model);
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
getFilteredModels() {
|
||||
// Collect all models from known providers
|
||||
const allModels = [];
|
||||
const knownProviders = getProviders();
|
||||
for (const provider of knownProviders) {
|
||||
const models = getModels(provider);
|
||||
for (const model of models) {
|
||||
allModels.push({ provider, id: model.id, model });
|
||||
}
|
||||
}
|
||||
// Add custom provider models
|
||||
for (const model of this.customProviderModels) {
|
||||
allModels.push({ provider: model.provider, id: model.id, model });
|
||||
}
|
||||
// Filter models based on search and capability filters
|
||||
let filteredModels = allModels;
|
||||
// Apply search filter
|
||||
if (this.searchQuery) {
|
||||
filteredModels = filteredModels.filter(({ provider, id, model }) => {
|
||||
const searchTokens = this.searchQuery.split(/\s+/).filter((t) => t);
|
||||
const searchText = `${provider} ${id} ${model.name}`.toLowerCase();
|
||||
return searchTokens.every((token) => searchText.includes(token));
|
||||
});
|
||||
}
|
||||
// Apply capability filters
|
||||
if (this.filterThinking) {
|
||||
filteredModels = filteredModels.filter(({ model }) => model.reasoning);
|
||||
}
|
||||
if (this.filterVision) {
|
||||
filteredModels = filteredModels.filter(({ model }) => model.input.includes("image"));
|
||||
}
|
||||
// Sort: current model first, then by provider
|
||||
filteredModels.sort((a, b) => {
|
||||
const aIsCurrent = this.currentModel?.id === a.model.id;
|
||||
const bIsCurrent = this.currentModel?.id === b.model.id;
|
||||
if (aIsCurrent && !bIsCurrent)
|
||||
return -1;
|
||||
if (!aIsCurrent && bIsCurrent)
|
||||
return 1;
|
||||
return a.provider.localeCompare(b.provider);
|
||||
});
|
||||
return filteredModels;
|
||||
}
|
||||
scrollToSelected() {
|
||||
requestAnimationFrame(() => {
|
||||
const scrollContainer = this.scrollContainerRef.value;
|
||||
const selectedElement = scrollContainer?.querySelectorAll("[data-model-item]")[this.selectedIndex];
|
||||
if (selectedElement) {
|
||||
selectedElement.scrollIntoView({ block: "nearest", behavior: "smooth" });
|
||||
}
|
||||
});
|
||||
}
|
||||
renderContent() {
|
||||
const filteredModels = this.getFilteredModels();
|
||||
return html `
|
||||
<!-- Header and Search -->
|
||||
<div class="p-6 pb-4 flex flex-col gap-4 border-b border-border flex-shrink-0">
|
||||
${DialogHeader({ title: i18n("Select Model") })}
|
||||
${Input({
|
||||
placeholder: i18n("Search models..."),
|
||||
value: this.searchQuery,
|
||||
inputRef: this.searchInputRef,
|
||||
onInput: (e) => {
|
||||
this.searchQuery = e.target.value;
|
||||
this.selectedIndex = 0;
|
||||
// Reset scroll position when search changes
|
||||
if (this.scrollContainerRef.value) {
|
||||
this.scrollContainerRef.value.scrollTop = 0;
|
||||
}
|
||||
},
|
||||
})}
|
||||
<div class="flex gap-2">
|
||||
${Button({
|
||||
variant: this.filterThinking ? "default" : "secondary",
|
||||
size: "sm",
|
||||
onClick: () => {
|
||||
this.filterThinking = !this.filterThinking;
|
||||
this.selectedIndex = 0;
|
||||
if (this.scrollContainerRef.value) {
|
||||
this.scrollContainerRef.value.scrollTop = 0;
|
||||
}
|
||||
},
|
||||
className: "rounded-full",
|
||||
children: html `<span class="inline-flex items-center gap-1">${icon(Brain, "sm")} ${i18n("Thinking")}</span>`,
|
||||
})}
|
||||
${Button({
|
||||
variant: this.filterVision ? "default" : "secondary",
|
||||
size: "sm",
|
||||
onClick: () => {
|
||||
this.filterVision = !this.filterVision;
|
||||
this.selectedIndex = 0;
|
||||
if (this.scrollContainerRef.value) {
|
||||
this.scrollContainerRef.value.scrollTop = 0;
|
||||
}
|
||||
},
|
||||
className: "rounded-full",
|
||||
children: html `<span class="inline-flex items-center gap-1">${icon(ImageIcon, "sm")} ${i18n("Vision")}</span>`,
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scrollable model list -->
|
||||
<div class="flex-1 overflow-y-auto" ${ref(this.scrollContainerRef)}>
|
||||
${filteredModels.map(({ provider, id, model }, index) => {
|
||||
const isCurrent = this.currentModel?.id === model.id && this.currentModel?.provider === model.provider;
|
||||
const isSelected = index === this.selectedIndex;
|
||||
return html `
|
||||
<div
|
||||
data-model-item
|
||||
class="px-4 py-3 ${this.navigationMode === "mouse" ? "hover:bg-muted" : ""} cursor-pointer border-b border-border ${isSelected ? "bg-accent" : ""}"
|
||||
@click=${() => this.handleSelect(model)}
|
||||
@mouseenter=${() => {
|
||||
// Only update selection in mouse mode
|
||||
if (this.navigationMode === "mouse") {
|
||||
this.selectedIndex = index;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class="flex items-center justify-between gap-2 mb-1">
|
||||
<div class="flex items-center gap-2 flex-1 min-w-0">
|
||||
<span class="text-sm font-medium text-foreground truncate">${id}</span>
|
||||
${isCurrent ? html `<span class="text-green-500">✓</span>` : ""}
|
||||
</div>
|
||||
${Badge(provider, "outline")}
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-xs text-muted-foreground">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="${model.reasoning ? "" : "opacity-30"}">${icon(Brain, "sm")}</span>
|
||||
<span class="${model.input.includes("image") ? "" : "opacity-30"}">${icon(ImageIcon, "sm")}</span>
|
||||
<span>${this.formatTokens(model.contextWindow)}K/${this.formatTokens(model.maxTokens)}K</span>
|
||||
</div>
|
||||
<span>${formatModelCost(model.cost)}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
})}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
};
|
||||
__decorate([
|
||||
state()
|
||||
], ModelSelector.prototype, "currentModel", void 0);
|
||||
__decorate([
|
||||
state()
|
||||
], ModelSelector.prototype, "searchQuery", void 0);
|
||||
__decorate([
|
||||
state()
|
||||
], ModelSelector.prototype, "filterThinking", void 0);
|
||||
__decorate([
|
||||
state()
|
||||
], ModelSelector.prototype, "filterVision", void 0);
|
||||
__decorate([
|
||||
state()
|
||||
], ModelSelector.prototype, "customProvidersLoading", void 0);
|
||||
__decorate([
|
||||
state()
|
||||
], ModelSelector.prototype, "selectedIndex", void 0);
|
||||
__decorate([
|
||||
state()
|
||||
], ModelSelector.prototype, "navigationMode", void 0);
|
||||
__decorate([
|
||||
state()
|
||||
], ModelSelector.prototype, "customProviderModels", void 0);
|
||||
ModelSelector = ModelSelector_1 = __decorate([
|
||||
customElement("agent-model-selector")
|
||||
], ModelSelector);
|
||||
export { ModelSelector };
|
||||
//# sourceMappingURL=ModelSelector.js.map
|
||||
File diff suppressed because one or more lines are too long
17
apps/macos/Sources/Clawdis/Resources/WebChat/dialogs/PersistentStorageDialog.d.ts
vendored
Normal file
17
apps/macos/Sources/Clawdis/Resources/WebChat/dialogs/PersistentStorageDialog.d.ts
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
import { DialogBase } from "@mariozechner/mini-lit/dist/DialogBase.js";
|
||||
export declare class PersistentStorageDialog extends DialogBase {
|
||||
private requesting;
|
||||
private resolvePromise?;
|
||||
protected modalWidth: string;
|
||||
protected modalHeight: string;
|
||||
/**
|
||||
* Request persistent storage permission.
|
||||
* Returns true if browser granted persistent storage, false otherwise.
|
||||
*/
|
||||
static request(): Promise<boolean>;
|
||||
private handleGrant;
|
||||
private handleDeny;
|
||||
close(): void;
|
||||
protected renderContent(): import("lit-html").TemplateResult<1>;
|
||||
}
|
||||
//# sourceMappingURL=PersistentStorageDialog.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"PersistentStorageDialog.d.ts","sourceRoot":"","sources":["../../src/dialogs/PersistentStorageDialog.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,2CAA2C,CAAC;AAKvE,qBACa,uBAAwB,SAAQ,UAAU;IAC7C,OAAO,CAAC,UAAU,CAAS;IAEpC,OAAO,CAAC,cAAc,CAAC,CAAkC;IAEzD,SAAS,CAAC,UAAU,SAAsB;IAC1C,SAAS,CAAC,WAAW,SAAU;IAE/B;;;OAGG;WACU,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;IA2CxC,OAAO,CAAC,WAAW;IAQnB,OAAO,CAAC,UAAU;IAQT,KAAK;cAOK,aAAa;CAyDhC"}
|
||||
@@ -0,0 +1,147 @@
|
||||
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;
|
||||
};
|
||||
var PersistentStorageDialog_1;
|
||||
import { Button } from "@mariozechner/mini-lit/dist/Button.js";
|
||||
import { DialogContent, DialogHeader } from "@mariozechner/mini-lit/dist/Dialog.js";
|
||||
import { DialogBase } from "@mariozechner/mini-lit/dist/DialogBase.js";
|
||||
import { html } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
import { i18n } from "../utils/i18n.js";
|
||||
let PersistentStorageDialog = PersistentStorageDialog_1 = class PersistentStorageDialog extends DialogBase {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.requesting = false;
|
||||
this.modalWidth = "min(500px, 90vw)";
|
||||
this.modalHeight = "auto";
|
||||
}
|
||||
/**
|
||||
* Request persistent storage permission.
|
||||
* Returns true if browser granted persistent storage, false otherwise.
|
||||
*/
|
||||
static async request() {
|
||||
// Check if already persisted
|
||||
if (navigator.storage?.persisted) {
|
||||
const alreadyPersisted = await navigator.storage.persisted();
|
||||
if (alreadyPersisted) {
|
||||
console.log("✓ Persistent storage already granted");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Show dialog and wait for user response
|
||||
const dialog = new PersistentStorageDialog_1();
|
||||
dialog.open();
|
||||
const userApproved = await new Promise((resolve) => {
|
||||
dialog.resolvePromise = resolve;
|
||||
});
|
||||
if (!userApproved) {
|
||||
console.warn("⚠ User declined persistent storage - sessions may be lost");
|
||||
return false;
|
||||
}
|
||||
// User approved, request from browser
|
||||
if (!navigator.storage?.persist) {
|
||||
console.warn("⚠ Persistent storage API not available");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
const granted = await navigator.storage.persist();
|
||||
if (granted) {
|
||||
console.log("✓ Persistent storage granted - sessions will be preserved");
|
||||
}
|
||||
else {
|
||||
console.warn("⚠ Browser denied persistent storage - sessions may be lost under storage pressure");
|
||||
}
|
||||
return granted;
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Failed to request persistent storage:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
handleGrant() {
|
||||
if (this.resolvePromise) {
|
||||
this.resolvePromise(true);
|
||||
this.resolvePromise = undefined;
|
||||
}
|
||||
this.close();
|
||||
}
|
||||
handleDeny() {
|
||||
if (this.resolvePromise) {
|
||||
this.resolvePromise(false);
|
||||
this.resolvePromise = undefined;
|
||||
}
|
||||
this.close();
|
||||
}
|
||||
close() {
|
||||
super.close();
|
||||
if (this.resolvePromise) {
|
||||
this.resolvePromise(false);
|
||||
}
|
||||
}
|
||||
renderContent() {
|
||||
return html `
|
||||
${DialogContent({
|
||||
children: html `
|
||||
${DialogHeader({
|
||||
title: i18n("Storage Permission Required"),
|
||||
description: i18n("This app needs persistent storage to save your conversations"),
|
||||
})}
|
||||
|
||||
<div class="mt-4 flex flex-col gap-4">
|
||||
<div class="flex gap-3 p-4 bg-warning/10 border border-warning/20 rounded-lg">
|
||||
<div class="flex-shrink-0 text-warning">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path>
|
||||
<line x1="12" y1="9" x2="12" y2="13"></line>
|
||||
<line x1="12" y1="17" x2="12.01" y2="17"></line>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="text-sm">
|
||||
<p class="font-medium text-foreground mb-1">${i18n("Why is this needed?")}</p>
|
||||
<p class="text-muted-foreground">
|
||||
${i18n("Without persistent storage, your browser may delete saved conversations when it needs disk space. Granting this permission ensures your chat history is preserved.")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-sm text-muted-foreground">
|
||||
<p class="mb-2">${i18n("What this means:")}</p>
|
||||
<ul class="list-disc list-inside space-y-1 ml-2">
|
||||
<li>${i18n("Your conversations will be saved locally in your browser")}</li>
|
||||
<li>${i18n("Data will not be deleted automatically to free up space")}</li>
|
||||
<li>${i18n("You can still manually clear data at any time")}</li>
|
||||
<li>${i18n("No data is sent to external servers")}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex gap-3 justify-end">
|
||||
${Button({
|
||||
variant: "outline",
|
||||
onClick: () => this.handleDeny(),
|
||||
disabled: this.requesting,
|
||||
children: i18n("Continue Anyway"),
|
||||
})}
|
||||
${Button({
|
||||
variant: "default",
|
||||
onClick: () => this.handleGrant(),
|
||||
disabled: this.requesting,
|
||||
children: this.requesting ? i18n("Requesting...") : i18n("Grant Permission"),
|
||||
})}
|
||||
</div>
|
||||
`,
|
||||
})}
|
||||
`;
|
||||
}
|
||||
};
|
||||
__decorate([
|
||||
state()
|
||||
], PersistentStorageDialog.prototype, "requesting", void 0);
|
||||
PersistentStorageDialog = PersistentStorageDialog_1 = __decorate([
|
||||
customElement("persistent-storage-dialog")
|
||||
], PersistentStorageDialog);
|
||||
export { PersistentStorageDialog };
|
||||
//# sourceMappingURL=PersistentStorageDialog.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"PersistentStorageDialog.js","sourceRoot":"","sources":["../../src/dialogs/PersistentStorageDialog.ts"],"names":[],"mappings":"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"}
|
||||
20
apps/macos/Sources/Clawdis/Resources/WebChat/dialogs/ProvidersModelsTab.d.ts
vendored
Normal file
20
apps/macos/Sources/Clawdis/Resources/WebChat/dialogs/ProvidersModelsTab.d.ts
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
import { type TemplateResult } from "lit";
|
||||
import "../components/CustomProviderCard.js";
|
||||
import "../components/ProviderKeyInput.js";
|
||||
import { SettingsTab } from "./SettingsDialog.js";
|
||||
export declare class ProvidersModelsTab extends SettingsTab {
|
||||
private customProviders;
|
||||
private providerStatus;
|
||||
connectedCallback(): Promise<void>;
|
||||
private loadCustomProviders;
|
||||
getTabName(): string;
|
||||
private checkProviderStatus;
|
||||
private renderKnownProviders;
|
||||
private renderCustomProviders;
|
||||
private addCustomProvider;
|
||||
private editProvider;
|
||||
private refreshProvider;
|
||||
private deleteProvider;
|
||||
render(): TemplateResult;
|
||||
}
|
||||
//# sourceMappingURL=ProvidersModelsTab.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ProvidersModelsTab.d.ts","sourceRoot":"","sources":["../../src/dialogs/ProvidersModelsTab.ts"],"names":[],"mappings":"AAGA,OAAO,EAAQ,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAEhD,OAAO,qCAAqC,CAAC;AAC7C,OAAO,mCAAmC,CAAC;AAS3C,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAElD,qBACa,kBAAmB,SAAQ,WAAW;IACzC,OAAO,CAAC,eAAe,CAAwB;IAC/C,OAAO,CAAC,cAAc,CAGjB;IAEC,iBAAiB;YAKlB,mBAAmB;IAqBjC,UAAU,IAAI,MAAM;YAIN,mBAAmB;IAkBjC,OAAO,CAAC,oBAAoB;IAkB5B,OAAO,CAAC,qBAAqB;YA0Df,iBAAiB;YAOjB,YAAY;YAOZ,eAAe;YAwBf,cAAc;IAe5B,MAAM,IAAI,cAAc;CASxB"}
|
||||
@@ -0,0 +1,191 @@
|
||||
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 { Select } from "@mariozechner/mini-lit/dist/Select.js";
|
||||
import { getProviders } from "@mariozechner/pi-ai";
|
||||
import { html } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
import "../components/CustomProviderCard.js";
|
||||
import "../components/ProviderKeyInput.js";
|
||||
import { getAppStorage } from "../storage/app-storage.js";
|
||||
import { discoverModels } from "../utils/model-discovery.js";
|
||||
import { CustomProviderDialog } from "./CustomProviderDialog.js";
|
||||
import { SettingsTab } from "./SettingsDialog.js";
|
||||
let ProvidersModelsTab = class ProvidersModelsTab extends SettingsTab {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.customProviders = [];
|
||||
this.providerStatus = new Map();
|
||||
}
|
||||
async connectedCallback() {
|
||||
super.connectedCallback();
|
||||
await this.loadCustomProviders();
|
||||
}
|
||||
async loadCustomProviders() {
|
||||
try {
|
||||
const storage = getAppStorage();
|
||||
this.customProviders = await storage.customProviders.getAll();
|
||||
// Check status for auto-discovery providers
|
||||
for (const provider of this.customProviders) {
|
||||
const isAutoDiscovery = provider.type === "ollama" ||
|
||||
provider.type === "llama.cpp" ||
|
||||
provider.type === "vllm" ||
|
||||
provider.type === "lmstudio";
|
||||
if (isAutoDiscovery) {
|
||||
this.checkProviderStatus(provider);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Failed to load custom providers:", error);
|
||||
}
|
||||
}
|
||||
getTabName() {
|
||||
return "Providers & Models";
|
||||
}
|
||||
async checkProviderStatus(provider) {
|
||||
this.providerStatus.set(provider.id, { modelCount: 0, status: "checking" });
|
||||
this.requestUpdate();
|
||||
try {
|
||||
const models = await discoverModels(provider.type, provider.baseUrl, provider.apiKey);
|
||||
this.providerStatus.set(provider.id, { modelCount: models.length, status: "connected" });
|
||||
}
|
||||
catch (error) {
|
||||
this.providerStatus.set(provider.id, { modelCount: 0, status: "disconnected" });
|
||||
}
|
||||
this.requestUpdate();
|
||||
}
|
||||
renderKnownProviders() {
|
||||
const providers = getProviders();
|
||||
return html `
|
||||
<div class="flex flex-col gap-6">
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-foreground mb-2">Cloud Providers</h3>
|
||||
<p class="text-sm text-muted-foreground mb-4">
|
||||
Cloud LLM providers with predefined models. API keys are stored locally in your browser.
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-col gap-6">
|
||||
${providers.map((provider) => html ` <provider-key-input .provider=${provider}></provider-key-input> `)}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
renderCustomProviders() {
|
||||
const isAutoDiscovery = (type) => type === "ollama" || type === "llama.cpp" || type === "vllm" || type === "lmstudio";
|
||||
return html `
|
||||
<div class="flex flex-col gap-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-foreground mb-2">Custom Providers</h3>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
User-configured servers with auto-discovered or manually defined models.
|
||||
</p>
|
||||
</div>
|
||||
${Select({
|
||||
placeholder: i18n("Add Provider"),
|
||||
options: [
|
||||
{ value: "ollama", label: "Ollama" },
|
||||
{ value: "llama.cpp", label: "llama.cpp" },
|
||||
{ value: "vllm", label: "vLLM" },
|
||||
{ value: "lmstudio", label: "LM Studio" },
|
||||
{ value: "openai-completions", label: i18n("OpenAI Completions Compatible") },
|
||||
{ value: "openai-responses", label: i18n("OpenAI Responses Compatible") },
|
||||
{ value: "anthropic-messages", label: i18n("Anthropic Messages Compatible") },
|
||||
],
|
||||
onChange: (value) => this.addCustomProvider(value),
|
||||
variant: "outline",
|
||||
size: "sm",
|
||||
})}
|
||||
</div>
|
||||
|
||||
${this.customProviders.length === 0
|
||||
? html `
|
||||
<div class="text-sm text-muted-foreground text-center py-8">
|
||||
No custom providers configured. Click 'Add Provider' to get started.
|
||||
</div>
|
||||
`
|
||||
: html `
|
||||
<div class="flex flex-col gap-4">
|
||||
${this.customProviders.map((provider) => html `
|
||||
<custom-provider-card
|
||||
.provider=${provider}
|
||||
.isAutoDiscovery=${isAutoDiscovery(provider.type)}
|
||||
.status=${this.providerStatus.get(provider.id)}
|
||||
.onRefresh=${(p) => this.refreshProvider(p)}
|
||||
.onEdit=${(p) => this.editProvider(p)}
|
||||
.onDelete=${(p) => this.deleteProvider(p)}
|
||||
></custom-provider-card>
|
||||
`)}
|
||||
</div>
|
||||
`}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
async addCustomProvider(type) {
|
||||
await CustomProviderDialog.open(undefined, type, async () => {
|
||||
await this.loadCustomProviders();
|
||||
this.requestUpdate();
|
||||
});
|
||||
}
|
||||
async editProvider(provider) {
|
||||
await CustomProviderDialog.open(provider, undefined, async () => {
|
||||
await this.loadCustomProviders();
|
||||
this.requestUpdate();
|
||||
});
|
||||
}
|
||||
async refreshProvider(provider) {
|
||||
this.providerStatus.set(provider.id, { modelCount: 0, status: "checking" });
|
||||
this.requestUpdate();
|
||||
try {
|
||||
const models = await discoverModels(provider.type, provider.baseUrl, provider.apiKey);
|
||||
this.providerStatus.set(provider.id, { modelCount: models.length, status: "connected" });
|
||||
this.requestUpdate();
|
||||
console.log(`Refreshed ${models.length} models from ${provider.name}`);
|
||||
}
|
||||
catch (error) {
|
||||
this.providerStatus.set(provider.id, { modelCount: 0, status: "disconnected" });
|
||||
this.requestUpdate();
|
||||
console.error(`Failed to refresh provider ${provider.name}:`, error);
|
||||
alert(`Failed to refresh provider: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
}
|
||||
async deleteProvider(provider) {
|
||||
if (!confirm("Are you sure you want to delete this provider?")) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const storage = getAppStorage();
|
||||
await storage.customProviders.delete(provider.id);
|
||||
await this.loadCustomProviders();
|
||||
this.requestUpdate();
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Failed to delete provider:", error);
|
||||
}
|
||||
}
|
||||
render() {
|
||||
return html `
|
||||
<div class="flex flex-col gap-8">
|
||||
${this.renderKnownProviders()}
|
||||
<div class="border-t border-border"></div>
|
||||
${this.renderCustomProviders()}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
};
|
||||
__decorate([
|
||||
state()
|
||||
], ProvidersModelsTab.prototype, "customProviders", void 0);
|
||||
__decorate([
|
||||
state()
|
||||
], ProvidersModelsTab.prototype, "providerStatus", void 0);
|
||||
ProvidersModelsTab = __decorate([
|
||||
customElement("providers-models-tab")
|
||||
], ProvidersModelsTab);
|
||||
export { ProvidersModelsTab };
|
||||
//# sourceMappingURL=ProvidersModelsTab.js.map
|
||||
File diff suppressed because one or more lines are too long
19
apps/macos/Sources/Clawdis/Resources/WebChat/dialogs/SessionListDialog.d.ts
vendored
Normal file
19
apps/macos/Sources/Clawdis/Resources/WebChat/dialogs/SessionListDialog.d.ts
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
import { DialogBase } from "@mariozechner/mini-lit/dist/DialogBase.js";
|
||||
export declare class SessionListDialog extends DialogBase {
|
||||
private sessions;
|
||||
private loading;
|
||||
private onSelectCallback?;
|
||||
private onDeleteCallback?;
|
||||
private deletedSessions;
|
||||
private closedViaSelection;
|
||||
protected modalWidth: string;
|
||||
protected modalHeight: string;
|
||||
static open(onSelect: (sessionId: string) => void, onDelete?: (sessionId: string) => void): Promise<void>;
|
||||
private loadSessions;
|
||||
private handleDelete;
|
||||
close(): void;
|
||||
private handleSelect;
|
||||
private formatDate;
|
||||
protected renderContent(): import("lit-html").TemplateResult<1>;
|
||||
}
|
||||
//# sourceMappingURL=SessionListDialog.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"SessionListDialog.d.ts","sourceRoot":"","sources":["../../src/dialogs/SessionListDialog.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,2CAA2C,CAAC;AAQvE,qBACa,iBAAkB,SAAQ,UAAU;IACvC,OAAO,CAAC,QAAQ,CAAyB;IACzC,OAAO,CAAC,OAAO,CAAQ;IAEhC,OAAO,CAAC,gBAAgB,CAAC,CAA8B;IACvD,OAAO,CAAC,gBAAgB,CAAC,CAA8B;IACvD,OAAO,CAAC,eAAe,CAAqB;IAC5C,OAAO,CAAC,kBAAkB,CAAS;IAEnC,SAAS,CAAC,UAAU,SAAsB;IAC1C,SAAS,CAAC,WAAW,SAAsB;WAE9B,IAAI,CAAC,QAAQ,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,EAAE,QAAQ,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI;YAQjF,YAAY;YAaZ,YAAY;IAqBjB,KAAK;IAWd,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,UAAU;cAiBC,aAAa;CAiDhC"}
|
||||
@@ -0,0 +1,154 @@
|
||||
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;
|
||||
};
|
||||
var SessionListDialog_1;
|
||||
import { DialogContent, DialogHeader } from "@mariozechner/mini-lit/dist/Dialog.js";
|
||||
import { DialogBase } from "@mariozechner/mini-lit/dist/DialogBase.js";
|
||||
import { html } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
import { getAppStorage } from "../storage/app-storage.js";
|
||||
import { formatUsage } from "../utils/format.js";
|
||||
import { i18n } from "../utils/i18n.js";
|
||||
let SessionListDialog = SessionListDialog_1 = class SessionListDialog extends DialogBase {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.sessions = [];
|
||||
this.loading = true;
|
||||
this.deletedSessions = new Set();
|
||||
this.closedViaSelection = false;
|
||||
this.modalWidth = "min(600px, 90vw)";
|
||||
this.modalHeight = "min(700px, 90vh)";
|
||||
}
|
||||
static async open(onSelect, onDelete) {
|
||||
const dialog = new SessionListDialog_1();
|
||||
dialog.onSelectCallback = onSelect;
|
||||
dialog.onDeleteCallback = onDelete;
|
||||
dialog.open();
|
||||
await dialog.loadSessions();
|
||||
}
|
||||
async loadSessions() {
|
||||
this.loading = true;
|
||||
try {
|
||||
const storage = getAppStorage();
|
||||
this.sessions = await storage.sessions.getAllMetadata();
|
||||
}
|
||||
catch (err) {
|
||||
console.error("Failed to load sessions:", err);
|
||||
this.sessions = [];
|
||||
}
|
||||
finally {
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
async handleDelete(sessionId, event) {
|
||||
event.stopPropagation();
|
||||
if (!confirm(i18n("Delete this session?"))) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const storage = getAppStorage();
|
||||
if (!storage.sessions)
|
||||
return;
|
||||
await storage.sessions.deleteSession(sessionId);
|
||||
await this.loadSessions();
|
||||
// Track deleted session
|
||||
this.deletedSessions.add(sessionId);
|
||||
}
|
||||
catch (err) {
|
||||
console.error("Failed to delete session:", err);
|
||||
}
|
||||
}
|
||||
close() {
|
||||
super.close();
|
||||
// Only notify about deleted sessions if dialog wasn't closed via selection
|
||||
if (!this.closedViaSelection && this.onDeleteCallback && this.deletedSessions.size > 0) {
|
||||
for (const sessionId of this.deletedSessions) {
|
||||
this.onDeleteCallback(sessionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
handleSelect(sessionId) {
|
||||
this.closedViaSelection = true;
|
||||
if (this.onSelectCallback) {
|
||||
this.onSelectCallback(sessionId);
|
||||
}
|
||||
this.close();
|
||||
}
|
||||
formatDate(isoString) {
|
||||
const date = new Date(isoString);
|
||||
const now = new Date();
|
||||
const diff = now.getTime() - date.getTime();
|
||||
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
|
||||
if (days === 0) {
|
||||
return i18n("Today");
|
||||
}
|
||||
else if (days === 1) {
|
||||
return i18n("Yesterday");
|
||||
}
|
||||
else if (days < 7) {
|
||||
return i18n("{days} days ago").replace("{days}", days.toString());
|
||||
}
|
||||
else {
|
||||
return date.toLocaleDateString();
|
||||
}
|
||||
}
|
||||
renderContent() {
|
||||
return html `
|
||||
${DialogContent({
|
||||
className: "h-full flex flex-col",
|
||||
children: html `
|
||||
${DialogHeader({
|
||||
title: i18n("Sessions"),
|
||||
description: i18n("Load a previous conversation"),
|
||||
})}
|
||||
|
||||
<div class="flex-1 overflow-y-auto mt-4 space-y-2">
|
||||
${this.loading
|
||||
? html `<div class="text-center py-8 text-muted-foreground">${i18n("Loading...")}</div>`
|
||||
: this.sessions.length === 0
|
||||
? html `<div class="text-center py-8 text-muted-foreground">${i18n("No sessions yet")}</div>`
|
||||
: this.sessions.map((session) => html `
|
||||
<div
|
||||
class="group flex items-start gap-3 p-3 rounded-lg border border-border hover:bg-secondary/50 cursor-pointer transition-colors"
|
||||
@click=${() => this.handleSelect(session.id)}
|
||||
>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="font-medium text-sm text-foreground truncate">${session.title}</div>
|
||||
<div class="text-xs text-muted-foreground mt-1">${this.formatDate(session.lastModified)}</div>
|
||||
<div class="text-xs text-muted-foreground mt-1">
|
||||
${session.messageCount} ${i18n("messages")} · ${formatUsage(session.usage)}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="opacity-0 group-hover:opacity-100 p-1 rounded hover:bg-destructive/10 text-destructive transition-opacity"
|
||||
@click=${(e) => this.handleDelete(session.id, e)}
|
||||
title=${i18n("Delete")}
|
||||
>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M3 6h18"></path>
|
||||
<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"></path>
|
||||
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
`)}
|
||||
</div>
|
||||
`,
|
||||
})}
|
||||
`;
|
||||
}
|
||||
};
|
||||
__decorate([
|
||||
state()
|
||||
], SessionListDialog.prototype, "sessions", void 0);
|
||||
__decorate([
|
||||
state()
|
||||
], SessionListDialog.prototype, "loading", void 0);
|
||||
SessionListDialog = SessionListDialog_1 = __decorate([
|
||||
customElement("session-list-dialog")
|
||||
], SessionListDialog);
|
||||
export { SessionListDialog };
|
||||
//# sourceMappingURL=SessionListDialog.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"SessionListDialog.js","sourceRoot":"","sources":["../../src/dialogs/SessionListDialog.ts"],"names":[],"mappings":";;;;;;;AAAA,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAC;AACpF,OAAO,EAAE,UAAU,EAAE,MAAM,2CAA2C,CAAC;AACvE,OAAO,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAC3B,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE1D,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAGjC,IAAM,iBAAiB,yBAAvB,MAAM,iBAAkB,SAAQ,UAAU;IAA1C;;QACW,aAAQ,GAAsB,EAAE,CAAC;QACjC,YAAO,GAAG,IAAI,CAAC;QAIxB,oBAAe,GAAG,IAAI,GAAG,EAAU,CAAC;QACpC,uBAAkB,GAAG,KAAK,CAAC;QAEzB,eAAU,GAAG,kBAAkB,CAAC;QAChC,gBAAW,GAAG,kBAAkB,CAAC;IAiI5C,CAAC;IA/HA,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,QAAqC,EAAE,QAAsC;QAC9F,MAAM,MAAM,GAAG,IAAI,mBAAiB,EAAE,CAAC;QACvC,MAAM,CAAC,gBAAgB,GAAG,QAAQ,CAAC;QACnC,MAAM,CAAC,gBAAgB,GAAG,QAAQ,CAAC;QACnC,MAAM,CAAC,IAAI,EAAE,CAAC;QACd,MAAM,MAAM,CAAC,YAAY,EAAE,CAAC;IAC7B,CAAC;IAEO,KAAK,CAAC,YAAY;QACzB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,IAAI,CAAC;YACJ,MAAM,OAAO,GAAG,aAAa,EAAE,CAAC;YAChC,IAAI,CAAC,QAAQ,GAAG,MAAM,OAAO,CAAC,QAAQ,CAAC,cAAc,EAAE,CAAC;QACzD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,OAAO,CAAC,KAAK,CAAC,0BAA0B,EAAE,GAAG,CAAC,CAAC;YAC/C,IAAI,CAAC,QAAQ,GAAG,EAAE,CAAC;QACpB,CAAC;gBAAS,CAAC;YACV,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;QACtB,CAAC;IACF,CAAC;IAEO,KAAK,CAAC,YAAY,CAAC,SAAiB,EAAE,KAAY;QACzD,KAAK,CAAC,eAAe,EAAE,CAAC;QAExB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC,EAAE,CAAC;YAC5C,OAAO;QACR,CAAC;QAED,IAAI,CAAC;YACJ,MAAM,OAAO,GAAG,aAAa,EAAE,CAAC;YAChC,IAAI,CAAC,OAAO,CAAC,QAAQ;gBAAE,OAAO;YAE9B,MAAM,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC;YAChD,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC;YAE1B,wBAAwB;YACxB,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACrC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,OAAO,CAAC,KAAK,CAAC,2BAA2B,EAAE,GAAG,CAAC,CAAC;QACjD,CAAC;IACF,CAAC;IAEQ,KAAK;QACb,KAAK,CAAC,KAAK,EAAE,CAAC;QAEd,2EAA2E;QAC3E,IAAI,CAAC,IAAI,CAAC,kBAAkB,IAAI,IAAI,CAAC,gBAAgB,IAAI,IAAI,CAAC,eAAe,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;YACxF,KAAK,MAAM,SAAS,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;gBAC9C,IAAI,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC;YAClC,CAAC;QACF,CAAC;IACF,CAAC;IAEO,YAAY,CAAC,SAAiB;QACrC,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC;QAC/B,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC3B,IAAI,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC;QAClC,CAAC;QACD,IAAI,CAAC,KAAK,EAAE,CAAC;IACd,CAAC;IAEO,UAAU,CAAC,SAAiB;QACnC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC;QACjC,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;QAC5C,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,IAAI,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;QAEtD,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;YAChB,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC;QACtB,CAAC;aAAM,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;YACvB,OAAO,IAAI,CAAC,WAAW,CAAC,CAAC;QAC1B,CAAC;aAAM,IAAI,IAAI,GAAG,CAAC,EAAE,CAAC;YACrB,OAAO,IAAI,CAAC,iBAAiB,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;QACnE,CAAC;aAAM,CAAC;YACP,OAAO,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAClC,CAAC;IACF,CAAC;IAEkB,aAAa;QAC/B,OAAO,IAAI,CAAA;KACR,aAAa,CAAC;YACf,SAAS,EAAE,sBAAsB;YACjC,QAAQ,EAAE,IAAI,CAAA;OACX,YAAY,CAAC;gBACd,KAAK,EAAE,IAAI,CAAC,UAAU,CAAC;gBACvB,WAAW,EAAE,IAAI,CAAC,8BAA8B,CAAC;aACjD,CAAC;;;QAIA,IAAI,CAAC,OAAO;gBACX,CAAC,CAAC,IAAI,CAAA,uDAAuD,IAAI,CAAC,YAAY,CAAC,QAAQ;gBACvF,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC;oBAC3B,CAAC,CAAC,IAAI,CAAA,uDAAuD,IAAI,CAAC,iBAAiB,CAAC,QAAQ;oBAC5F,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CACjB,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,CAAA;;;qBAGP,GAAG,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,EAAE,CAAC;;;yEAGiB,OAAO,CAAC,KAAK;+DACvB,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,YAAY,CAAC;;gBAEpF,OAAO,CAAC,YAAY,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC;;;;;sBAKlE,CAAC,CAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;qBAC/C,IAAI,CAAC,QAAQ,CAAC;;;;;;;;;WASxB,CAEL;;KAED;SACD,CAAC;GACF,CAAC;IACH,CAAC;CACD,CAAA;AA1IiB;IAAhB,KAAK,EAAE;mDAA0C;AACjC;IAAhB,KAAK,EAAE;kDAAwB;AAFpB,iBAAiB;IAD7B,aAAa,CAAC,qBAAqB,CAAC;GACxB,iBAAiB,CA2I7B"}
|
||||
30
apps/macos/Sources/Clawdis/Resources/WebChat/dialogs/SettingsDialog.d.ts
vendored
Normal file
30
apps/macos/Sources/Clawdis/Resources/WebChat/dialogs/SettingsDialog.d.ts
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
import { LitElement, type TemplateResult } from "lit";
|
||||
import "../components/ProviderKeyInput.js";
|
||||
export declare abstract class SettingsTab extends LitElement {
|
||||
abstract getTabName(): string;
|
||||
protected createRenderRoot(): this;
|
||||
}
|
||||
export declare class ApiKeysTab extends SettingsTab {
|
||||
getTabName(): string;
|
||||
render(): TemplateResult;
|
||||
}
|
||||
export declare class ProxyTab extends SettingsTab {
|
||||
private proxyEnabled;
|
||||
private proxyUrl;
|
||||
connectedCallback(): Promise<void>;
|
||||
private saveProxySettings;
|
||||
getTabName(): string;
|
||||
render(): TemplateResult;
|
||||
}
|
||||
export declare class SettingsDialog extends LitElement {
|
||||
tabs: SettingsTab[];
|
||||
private isOpen;
|
||||
private activeTabIndex;
|
||||
protected createRenderRoot(): this;
|
||||
static open(tabs: SettingsTab[]): Promise<void>;
|
||||
private setActiveTab;
|
||||
private renderSidebarItem;
|
||||
private renderMobileTab;
|
||||
render(): TemplateResult;
|
||||
}
|
||||
//# sourceMappingURL=SettingsDialog.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"SettingsDialog.d.ts","sourceRoot":"","sources":["../../src/dialogs/SettingsDialog.ts"],"names":[],"mappings":"AAMA,OAAO,EAAQ,UAAU,EAAE,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAE5D,OAAO,mCAAmC,CAAC;AAI3C,8BAAsB,WAAY,SAAQ,UAAU;IACnD,QAAQ,CAAC,UAAU,IAAI,MAAM;IAE7B,SAAS,CAAC,gBAAgB;CAG1B;AAGD,qBACa,UAAW,SAAQ,WAAW;IAC1C,UAAU,IAAI,MAAM;IAIpB,MAAM,IAAI,cAAc;CAYxB;AAGD,qBACa,QAAS,SAAQ,WAAW;IAC/B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAA2B;IAErC,iBAAiB;YAelB,iBAAiB;IAU/B,UAAU,IAAI,MAAM;IAIpB,MAAM,IAAI,cAAc;CAoCxB;AAED,qBACa,cAAe,SAAQ,UAAU;IACA,IAAI,EAAE,WAAW,EAAE,CAAM;IAC7D,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,cAAc,CAAK;IAEpC,SAAS,CAAC,gBAAgB;WAIb,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE;IAOrC,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,iBAAiB;IAgBzB,OAAO,CAAC,eAAe;IAcvB,MAAM;CAgDN"}
|
||||
@@ -0,0 +1,229 @@
|
||||
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;
|
||||
};
|
||||
var SettingsDialog_1;
|
||||
import { i18n } from "@mariozechner/mini-lit";
|
||||
import { Dialog, DialogContent, DialogHeader } from "@mariozechner/mini-lit/dist/Dialog.js";
|
||||
import { Input } from "@mariozechner/mini-lit/dist/Input.js";
|
||||
import { Label } from "@mariozechner/mini-lit/dist/Label.js";
|
||||
import { Switch } from "@mariozechner/mini-lit/dist/Switch.js";
|
||||
import { getProviders } from "@mariozechner/pi-ai";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import "../components/ProviderKeyInput.js";
|
||||
import { getAppStorage } from "../storage/app-storage.js";
|
||||
// Base class for settings tabs
|
||||
export class SettingsTab extends LitElement {
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
// API Keys Tab
|
||||
let ApiKeysTab = class ApiKeysTab extends SettingsTab {
|
||||
getTabName() {
|
||||
return i18n("API Keys");
|
||||
}
|
||||
render() {
|
||||
const providers = getProviders();
|
||||
return html `
|
||||
<div class="flex flex-col gap-6">
|
||||
<p class="text-sm text-muted-foreground">
|
||||
${i18n("Configure API keys for LLM providers. Keys are stored locally in your browser.")}
|
||||
</p>
|
||||
${providers.map((provider) => html `<provider-key-input .provider=${provider}></provider-key-input>`)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
};
|
||||
ApiKeysTab = __decorate([
|
||||
customElement("api-keys-tab")
|
||||
], ApiKeysTab);
|
||||
export { ApiKeysTab };
|
||||
// Proxy Tab
|
||||
let ProxyTab = class ProxyTab extends SettingsTab {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.proxyEnabled = false;
|
||||
this.proxyUrl = "http://localhost:3001";
|
||||
}
|
||||
async connectedCallback() {
|
||||
super.connectedCallback();
|
||||
// Load proxy settings when tab is connected
|
||||
try {
|
||||
const storage = getAppStorage();
|
||||
const enabled = await storage.settings.get("proxy.enabled");
|
||||
const url = await storage.settings.get("proxy.url");
|
||||
if (enabled !== null)
|
||||
this.proxyEnabled = enabled;
|
||||
if (url !== null)
|
||||
this.proxyUrl = url;
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Failed to load proxy settings:", error);
|
||||
}
|
||||
}
|
||||
async saveProxySettings() {
|
||||
try {
|
||||
const storage = getAppStorage();
|
||||
await storage.settings.set("proxy.enabled", this.proxyEnabled);
|
||||
await storage.settings.set("proxy.url", this.proxyUrl);
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Failed to save proxy settings:", error);
|
||||
}
|
||||
}
|
||||
getTabName() {
|
||||
return i18n("Proxy");
|
||||
}
|
||||
render() {
|
||||
return html `
|
||||
<div class="flex flex-col gap-4">
|
||||
<p class="text-sm text-muted-foreground">
|
||||
${i18n("Allows browser-based apps to bypass CORS restrictions when calling LLM providers. Required for Z-AI and Anthropic with OAuth token.")}
|
||||
</p>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-sm font-medium text-foreground">${i18n("Use CORS Proxy")}</span>
|
||||
${Switch({
|
||||
checked: this.proxyEnabled,
|
||||
onChange: (checked) => {
|
||||
this.proxyEnabled = checked;
|
||||
this.saveProxySettings();
|
||||
},
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
${Label({ children: i18n("Proxy URL") })}
|
||||
${Input({
|
||||
type: "text",
|
||||
value: this.proxyUrl,
|
||||
disabled: !this.proxyEnabled,
|
||||
onInput: (e) => {
|
||||
this.proxyUrl = e.target.value;
|
||||
},
|
||||
onChange: () => this.saveProxySettings(),
|
||||
})}
|
||||
<p class="text-xs text-muted-foreground">
|
||||
${i18n("Format: The proxy must accept requests as <proxy-url>/?url=<target-url>")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
};
|
||||
__decorate([
|
||||
state()
|
||||
], ProxyTab.prototype, "proxyEnabled", void 0);
|
||||
__decorate([
|
||||
state()
|
||||
], ProxyTab.prototype, "proxyUrl", void 0);
|
||||
ProxyTab = __decorate([
|
||||
customElement("proxy-tab")
|
||||
], ProxyTab);
|
||||
export { ProxyTab };
|
||||
let SettingsDialog = SettingsDialog_1 = class SettingsDialog extends LitElement {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.tabs = [];
|
||||
this.isOpen = false;
|
||||
this.activeTabIndex = 0;
|
||||
}
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
static async open(tabs) {
|
||||
const dialog = new SettingsDialog_1();
|
||||
dialog.tabs = tabs;
|
||||
dialog.isOpen = true;
|
||||
document.body.appendChild(dialog);
|
||||
}
|
||||
setActiveTab(index) {
|
||||
this.activeTabIndex = index;
|
||||
}
|
||||
renderSidebarItem(tab, index) {
|
||||
const isActive = this.activeTabIndex === index;
|
||||
return html `
|
||||
<button
|
||||
class="w-full text-left px-4 py-3 rounded-md transition-colors ${isActive
|
||||
? "bg-secondary text-foreground font-medium"
|
||||
: "text-muted-foreground hover:bg-secondary/50 hover:text-foreground"}"
|
||||
@click=${() => this.setActiveTab(index)}
|
||||
>
|
||||
${tab.getTabName()}
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
renderMobileTab(tab, index) {
|
||||
const isActive = this.activeTabIndex === index;
|
||||
return html `
|
||||
<button
|
||||
class="px-3 py-2 text-sm font-medium transition-colors ${isActive ? "border-b-2 border-primary text-foreground" : "text-muted-foreground hover:text-foreground"}"
|
||||
@click=${() => this.setActiveTab(index)}
|
||||
>
|
||||
${tab.getTabName()}
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
render() {
|
||||
if (this.tabs.length === 0) {
|
||||
return html ``;
|
||||
}
|
||||
return Dialog({
|
||||
isOpen: this.isOpen,
|
||||
onClose: () => {
|
||||
this.isOpen = false;
|
||||
this.remove();
|
||||
},
|
||||
width: "min(1000px, 90vw)",
|
||||
height: "min(800px, 90vh)",
|
||||
backdropClassName: "bg-black/50 backdrop-blur-sm",
|
||||
children: html `
|
||||
${DialogContent({
|
||||
className: "h-full p-6",
|
||||
children: html `
|
||||
<div class="flex flex-col h-full overflow-hidden">
|
||||
<!-- Header -->
|
||||
<div class="pb-4 flex-shrink-0">${DialogHeader({ title: i18n("Settings") })}</div>
|
||||
|
||||
<!-- Mobile Tabs -->
|
||||
<div class="md:hidden flex flex-shrink-0 pb-4">
|
||||
${this.tabs.map((tab, index) => this.renderMobileTab(tab, index))}
|
||||
</div>
|
||||
|
||||
<!-- Layout -->
|
||||
<div class="flex flex-1 overflow-hidden">
|
||||
<!-- Sidebar (desktop only) -->
|
||||
<div class="hidden md:block w-64 flex-shrink-0 space-y-1">
|
||||
${this.tabs.map((tab, index) => this.renderSidebarItem(tab, index))}
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="flex-1 overflow-y-auto md:pl-6">
|
||||
${this.tabs.map((tab, index) => html `<div style="display: ${this.activeTabIndex === index ? "block" : "none"}">${tab}</div>`)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
})}
|
||||
`,
|
||||
});
|
||||
}
|
||||
};
|
||||
__decorate([
|
||||
property({ type: Array, attribute: false })
|
||||
], SettingsDialog.prototype, "tabs", void 0);
|
||||
__decorate([
|
||||
state()
|
||||
], SettingsDialog.prototype, "isOpen", void 0);
|
||||
__decorate([
|
||||
state()
|
||||
], SettingsDialog.prototype, "activeTabIndex", void 0);
|
||||
SettingsDialog = SettingsDialog_1 = __decorate([
|
||||
customElement("settings-dialog")
|
||||
], SettingsDialog);
|
||||
export { SettingsDialog };
|
||||
//# sourceMappingURL=SettingsDialog.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"SettingsDialog.js","sourceRoot":"","sources":["../../src/dialogs/SettingsDialog.ts"],"names":[],"mappings":"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"}
|
||||
Reference in New Issue
Block a user