mac: bundle web chat assets

This commit is contained in:
Peter Steinberger
2025-12-06 05:01:28 +01:00
parent 15cdeeddaf
commit 42d843297d
315 changed files with 16618 additions and 20 deletions

View File

@@ -0,0 +1,19 @@
export interface Attachment {
id: string;
type: "image" | "document";
fileName: string;
mimeType: string;
size: number;
content: string;
extractedText?: string;
preview?: string;
}
/**
* Load an attachment from various sources
* @param source - URL string, File, Blob, or ArrayBuffer
* @param fileName - Optional filename override
* @returns Promise<Attachment>
* @throws Error if loading fails
*/
export declare function loadAttachment(source: string | File | Blob | ArrayBuffer, fileName?: string): Promise<Attachment>;
//# sourceMappingURL=attachment-utils.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"attachment-utils.d.ts","sourceRoot":"","sources":["../../src/utils/attachment-utils.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,UAAU;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,OAAO,GAAG,UAAU,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CACnC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,WAAW,EAC1C,QAAQ,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,UAAU,CAAC,CAyKrB"}

View File

@@ -0,0 +1,415 @@
import { parseAsync } from "docx-preview";
import JSZip from "jszip";
import * as pdfjsLib from "pdfjs-dist";
import * as XLSX from "xlsx";
import { i18n } from "./i18n.js";
// Configure PDF.js worker - we'll need to bundle this
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL("pdfjs-dist/build/pdf.worker.min.mjs", import.meta.url).toString();
/**
* Load an attachment from various sources
* @param source - URL string, File, Blob, or ArrayBuffer
* @param fileName - Optional filename override
* @returns Promise<Attachment>
* @throws Error if loading fails
*/
export async function loadAttachment(source, fileName) {
let arrayBuffer;
let detectedFileName = fileName || "unnamed";
let mimeType = "application/octet-stream";
let size = 0;
// Convert source to ArrayBuffer
if (typeof source === "string") {
// It's a URL - fetch it
const response = await fetch(source);
if (!response.ok) {
throw new Error(i18n("Failed to fetch file"));
}
arrayBuffer = await response.arrayBuffer();
size = arrayBuffer.byteLength;
mimeType = response.headers.get("content-type") || mimeType;
if (!fileName) {
// Try to extract filename from URL
const urlParts = source.split("/");
detectedFileName = urlParts[urlParts.length - 1] || "document";
}
}
else if (source instanceof File) {
arrayBuffer = await source.arrayBuffer();
size = source.size;
mimeType = source.type || mimeType;
detectedFileName = fileName || source.name;
}
else if (source instanceof Blob) {
arrayBuffer = await source.arrayBuffer();
size = source.size;
mimeType = source.type || mimeType;
}
else if (source instanceof ArrayBuffer) {
arrayBuffer = source;
size = source.byteLength;
}
else {
throw new Error(i18n("Invalid source type"));
}
// Convert ArrayBuffer to base64 - handle large files properly
const uint8Array = new Uint8Array(arrayBuffer);
let binary = "";
const chunkSize = 0x8000; // Process in 32KB chunks to avoid stack overflow
for (let i = 0; i < uint8Array.length; i += chunkSize) {
const chunk = uint8Array.slice(i, i + chunkSize);
binary += String.fromCharCode(...chunk);
}
const base64Content = btoa(binary);
// Detect type and process accordingly
const id = `${detectedFileName}_${Date.now()}_${Math.random()}`;
// Check if it's a PDF
if (mimeType === "application/pdf" || detectedFileName.toLowerCase().endsWith(".pdf")) {
const { extractedText, preview } = await processPdf(arrayBuffer, detectedFileName);
return {
id,
type: "document",
fileName: detectedFileName,
mimeType: "application/pdf",
size,
content: base64Content,
extractedText,
preview,
};
}
// Check if it's a DOCX file
if (mimeType === "application/vnd.openxmlformats-officedocument.wordprocessingml.document" ||
detectedFileName.toLowerCase().endsWith(".docx")) {
const { extractedText } = await processDocx(arrayBuffer, detectedFileName);
return {
id,
type: "document",
fileName: detectedFileName,
mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
size,
content: base64Content,
extractedText,
};
}
// Check if it's a PPTX file
if (mimeType === "application/vnd.openxmlformats-officedocument.presentationml.presentation" ||
detectedFileName.toLowerCase().endsWith(".pptx")) {
const { extractedText } = await processPptx(arrayBuffer, detectedFileName);
return {
id,
type: "document",
fileName: detectedFileName,
mimeType: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
size,
content: base64Content,
extractedText,
};
}
// Check if it's an Excel file (XLSX/XLS)
const excelMimeTypes = [
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"application/vnd.ms-excel",
];
if (excelMimeTypes.includes(mimeType) ||
detectedFileName.toLowerCase().endsWith(".xlsx") ||
detectedFileName.toLowerCase().endsWith(".xls")) {
const { extractedText } = await processExcel(arrayBuffer, detectedFileName);
return {
id,
type: "document",
fileName: detectedFileName,
mimeType: mimeType.startsWith("application/vnd")
? mimeType
: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
size,
content: base64Content,
extractedText,
};
}
// Check if it's an image
if (mimeType.startsWith("image/")) {
return {
id,
type: "image",
fileName: detectedFileName,
mimeType,
size,
content: base64Content,
preview: base64Content, // For images, preview is the same as content
};
}
// Check if it's a text document
const textExtensions = [
".txt",
".md",
".json",
".xml",
".html",
".css",
".js",
".ts",
".jsx",
".tsx",
".yml",
".yaml",
];
const isTextFile = mimeType.startsWith("text/") || textExtensions.some((ext) => detectedFileName.toLowerCase().endsWith(ext));
if (isTextFile) {
const decoder = new TextDecoder();
const text = decoder.decode(arrayBuffer);
return {
id,
type: "document",
fileName: detectedFileName,
mimeType: mimeType.startsWith("text/") ? mimeType : "text/plain",
size,
content: base64Content,
extractedText: text,
};
}
throw new Error(`Unsupported file type: ${mimeType}`);
}
async function processPdf(arrayBuffer, fileName) {
let pdf = null;
try {
pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise;
// Extract text with page structure
let extractedText = `<pdf filename="${fileName}">`;
for (let i = 1; i <= pdf.numPages; i++) {
const page = await pdf.getPage(i);
const textContent = await page.getTextContent();
const pageText = textContent.items
.map((item) => item.str)
.filter((str) => str.trim())
.join(" ");
extractedText += `\n<page number="${i}">\n${pageText}\n</page>`;
}
extractedText += "\n</pdf>";
// Generate preview from first page
const preview = await generatePdfPreview(pdf);
return { extractedText, preview };
}
catch (error) {
console.error("Error processing PDF:", error);
throw new Error(`Failed to process PDF: ${String(error)}`);
}
finally {
// Clean up PDF resources
if (pdf) {
pdf.destroy();
}
}
}
async function generatePdfPreview(pdf) {
try {
const page = await pdf.getPage(1);
const viewport = page.getViewport({ scale: 1.0 });
// Create canvas with reasonable size for thumbnail (160x160 max)
const scale = Math.min(160 / viewport.width, 160 / viewport.height);
const scaledViewport = page.getViewport({ scale });
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
if (!context) {
return undefined;
}
canvas.height = scaledViewport.height;
canvas.width = scaledViewport.width;
const renderContext = {
canvasContext: context,
viewport: scaledViewport,
canvas: canvas,
};
await page.render(renderContext).promise;
// Return base64 without data URL prefix
return canvas.toDataURL("image/png").split(",")[1];
}
catch (error) {
console.error("Error generating PDF preview:", error);
return undefined;
}
}
async function processDocx(arrayBuffer, fileName) {
try {
// Parse document structure
const wordDoc = await parseAsync(arrayBuffer);
// Extract structured text from document body
let extractedText = `<docx filename="${fileName}">\n<page number="1">\n`;
const body = wordDoc.documentPart?.body;
if (body?.children) {
// Walk through document elements and extract text
const texts = [];
for (const element of body.children) {
const text = extractTextFromElement(element);
if (text) {
texts.push(text);
}
}
extractedText += texts.join("\n");
}
extractedText += `\n</page>\n</docx>`;
return { extractedText };
}
catch (error) {
console.error("Error processing DOCX:", error);
throw new Error(`Failed to process DOCX: ${String(error)}`);
}
}
function extractTextFromElement(element) {
let text = "";
// Check type with lowercase
const elementType = element.type?.toLowerCase() || "";
// Handle paragraphs
if (elementType === "paragraph" && element.children) {
for (const child of element.children) {
const childType = child.type?.toLowerCase() || "";
if (childType === "run" && child.children) {
for (const textChild of child.children) {
const textType = textChild.type?.toLowerCase() || "";
if (textType === "text") {
text += textChild.text || "";
}
}
}
else if (childType === "text") {
text += child.text || "";
}
}
}
// Handle tables
else if (elementType === "table") {
if (element.children) {
const tableTexts = [];
for (const row of element.children) {
const rowType = row.type?.toLowerCase() || "";
if (rowType === "tablerow" && row.children) {
const rowTexts = [];
for (const cell of row.children) {
const cellType = cell.type?.toLowerCase() || "";
if (cellType === "tablecell" && cell.children) {
const cellTexts = [];
for (const cellElement of cell.children) {
const cellText = extractTextFromElement(cellElement);
if (cellText)
cellTexts.push(cellText);
}
if (cellTexts.length > 0)
rowTexts.push(cellTexts.join(" "));
}
}
if (rowTexts.length > 0)
tableTexts.push(rowTexts.join(" | "));
}
}
if (tableTexts.length > 0) {
text = "\n[Table]\n" + tableTexts.join("\n") + "\n[/Table]\n";
}
}
}
// Recursively handle other container elements
else if (element.children && Array.isArray(element.children)) {
const childTexts = [];
for (const child of element.children) {
const childText = extractTextFromElement(child);
if (childText)
childTexts.push(childText);
}
text = childTexts.join(" ");
}
return text.trim();
}
async function processPptx(arrayBuffer, fileName) {
try {
// Load the PPTX file as a ZIP
const zip = await JSZip.loadAsync(arrayBuffer);
// PPTX slides are stored in ppt/slides/slide[n].xml
let extractedText = `<pptx filename="${fileName}">`;
// Get all slide files and sort them numerically
const slideFiles = Object.keys(zip.files)
.filter((name) => name.match(/ppt\/slides\/slide\d+\.xml$/))
.sort((a, b) => {
const numA = Number.parseInt(a.match(/slide(\d+)\.xml$/)?.[1] || "0", 10);
const numB = Number.parseInt(b.match(/slide(\d+)\.xml$/)?.[1] || "0", 10);
return numA - numB;
});
// Extract text from each slide
for (let i = 0; i < slideFiles.length; i++) {
const slideFile = zip.file(slideFiles[i]);
if (slideFile) {
const slideXml = await slideFile.async("text");
// Extract text from XML (simple regex approach)
// Looking for <a:t> tags which contain text in PPTX
const textMatches = slideXml.match(/<a:t[^>]*>([^<]+)<\/a:t>/g);
if (textMatches) {
extractedText += `\n<slide number="${i + 1}">`;
const slideTexts = textMatches
.map((match) => {
const textMatch = match.match(/<a:t[^>]*>([^<]+)<\/a:t>/);
return textMatch ? textMatch[1] : "";
})
.filter((t) => t.trim());
if (slideTexts.length > 0) {
extractedText += "\n" + slideTexts.join("\n");
}
extractedText += "\n</slide>";
}
}
}
// Also try to extract text from notes
const notesFiles = Object.keys(zip.files)
.filter((name) => name.match(/ppt\/notesSlides\/notesSlide\d+\.xml$/))
.sort((a, b) => {
const numA = Number.parseInt(a.match(/notesSlide(\d+)\.xml$/)?.[1] || "0", 10);
const numB = Number.parseInt(b.match(/notesSlide(\d+)\.xml$/)?.[1] || "0", 10);
return numA - numB;
});
if (notesFiles.length > 0) {
extractedText += "\n<notes>";
for (const noteFile of notesFiles) {
const file = zip.file(noteFile);
if (file) {
const noteXml = await file.async("text");
const textMatches = noteXml.match(/<a:t[^>]*>([^<]+)<\/a:t>/g);
if (textMatches) {
const noteTexts = textMatches
.map((match) => {
const textMatch = match.match(/<a:t[^>]*>([^<]+)<\/a:t>/);
return textMatch ? textMatch[1] : "";
})
.filter((t) => t.trim());
if (noteTexts.length > 0) {
const slideNum = noteFile.match(/notesSlide(\d+)\.xml$/)?.[1];
extractedText += `\n[Slide ${slideNum} notes]: ${noteTexts.join(" ")}`;
}
}
}
}
extractedText += "\n</notes>";
}
extractedText += "\n</pptx>";
return { extractedText };
}
catch (error) {
console.error("Error processing PPTX:", error);
throw new Error(`Failed to process PPTX: ${String(error)}`);
}
}
async function processExcel(arrayBuffer, fileName) {
try {
// Read the workbook
const workbook = XLSX.read(arrayBuffer, { type: "array" });
let extractedText = `<excel filename="${fileName}">`;
// Process each sheet
for (const [index, sheetName] of workbook.SheetNames.entries()) {
const worksheet = workbook.Sheets[sheetName];
// Extract text as CSV for the extractedText field
const csvText = XLSX.utils.sheet_to_csv(worksheet);
extractedText += `\n<sheet name="${sheetName}" index="${index + 1}">\n${csvText}\n</sheet>`;
}
extractedText += "\n</excel>";
return { extractedText };
}
catch (error) {
console.error("Error processing Excel:", error);
throw new Error(`Failed to process Excel: ${String(error)}`);
}
}
//# sourceMappingURL=attachment-utils.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,3 @@
export declare function getAuthToken(): Promise<string | undefined>;
export declare function clearAuthToken(): Promise<void>;
//# sourceMappingURL=auth-token.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"auth-token.d.ts","sourceRoot":"","sources":["../../src/utils/auth-token.ts"],"names":[],"mappings":"AAGA,wBAAsB,YAAY,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAchE;AAED,wBAAsB,cAAc,kBAEnC"}

View File

@@ -0,0 +1,19 @@
import PromptDialog from "@mariozechner/mini-lit/dist/PromptDialog.js";
import { i18n } from "./i18n.js";
export async function getAuthToken() {
let authToken = localStorage.getItem(`auth-token`) || "";
if (authToken)
return authToken;
while (true) {
authToken = (await PromptDialog.ask(i18n("Enter Auth Token"), i18n("Please enter your auth token."), "", true))?.trim();
if (authToken) {
localStorage.setItem(`auth-token`, authToken);
break;
}
}
return authToken?.trim() || undefined;
}
export async function clearAuthToken() {
localStorage.removeItem(`auth-token`);
}
//# sourceMappingURL=auth-token.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"auth-token.js","sourceRoot":"","sources":["../../src/utils/auth-token.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,6CAA6C,CAAC;AACvE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,CAAC,KAAK,UAAU,YAAY;IACjC,IAAI,SAAS,GAAuB,YAAY,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC;IAC7E,IAAI,SAAS;QAAE,OAAO,SAAS,CAAC;IAEhC,OAAO,IAAI,EAAE,CAAC;QACb,SAAS,GAAG,CACX,MAAM,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,kBAAkB,CAAC,EAAE,IAAI,CAAC,+BAA+B,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,CACjG,EAAE,IAAI,EAAE,CAAC;QACV,IAAI,SAAS,EAAE,CAAC;YACf,YAAY,CAAC,OAAO,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;YAC9C,MAAM;QACP,CAAC;IACF,CAAC;IACD,OAAO,SAAS,EAAE,IAAI,EAAE,IAAI,SAAS,CAAC;AACvC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc;IACnC,YAAY,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;AACvC,CAAC"}

View File

@@ -0,0 +1,6 @@
import type { Usage } from "@mariozechner/pi-ai";
export declare function formatCost(cost: number): string;
export declare function formatModelCost(cost: any): string;
export declare function formatUsage(usage: Usage): string;
export declare function formatTokenCount(count: number): string;
//# sourceMappingURL=format.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../src/utils/format.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAEjD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,GAAG,GAAG,MAAM,CAejD;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,UAWvC;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAItD"}

View File

@@ -0,0 +1,47 @@
import { i18n } from "@mariozechner/mini-lit";
export function formatCost(cost) {
return `$${cost.toFixed(4)}`;
}
export function formatModelCost(cost) {
if (!cost)
return i18n("Free");
const input = cost.input || 0;
const output = cost.output || 0;
if (input === 0 && output === 0)
return i18n("Free");
// Format numbers with appropriate precision
const formatNum = (num) => {
if (num >= 100)
return num.toFixed(0);
if (num >= 10)
return num.toFixed(1).replace(/\.0$/, "");
if (num >= 1)
return num.toFixed(2).replace(/\.?0+$/, "");
return num.toFixed(3).replace(/\.?0+$/, "");
};
return `$${formatNum(input)}/$${formatNum(output)}`;
}
export function formatUsage(usage) {
if (!usage)
return "";
const parts = [];
if (usage.input)
parts.push(`${formatTokenCount(usage.input)}`);
if (usage.output)
parts.push(`${formatTokenCount(usage.output)}`);
if (usage.cacheRead)
parts.push(`R${formatTokenCount(usage.cacheRead)}`);
if (usage.cacheWrite)
parts.push(`W${formatTokenCount(usage.cacheWrite)}`);
if (usage.cost?.total)
parts.push(formatCost(usage.cost.total));
return parts.join(" ");
}
export function formatTokenCount(count) {
if (count < 1000)
return count.toString();
if (count < 10000)
return (count / 1000).toFixed(1) + "k";
return Math.round(count / 1000) + "k";
}
//# sourceMappingURL=format.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/utils/format.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAG9C,MAAM,UAAU,UAAU,CAAC,IAAY;IACtC,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,IAAS;IACxC,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC;IAC/B,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC;IAC9B,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC;IAChC,IAAI,KAAK,KAAK,CAAC,IAAI,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC;IAErD,4CAA4C;IAC5C,MAAM,SAAS,GAAG,CAAC,GAAW,EAAU,EAAE;QACzC,IAAI,GAAG,IAAI,GAAG;YAAE,OAAO,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QACtC,IAAI,GAAG,IAAI,EAAE;YAAE,OAAO,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACzD,IAAI,GAAG,IAAI,CAAC;YAAE,OAAO,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QAC1D,OAAO,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAC7C,CAAC,CAAC;IAEF,OAAO,IAAI,SAAS,CAAC,KAAK,CAAC,KAAK,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;AACrD,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,KAAY;IACvC,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,CAAC;IAEtB,MAAM,KAAK,GAAG,EAAE,CAAC;IACjB,IAAI,KAAK,CAAC,KAAK;QAAE,KAAK,CAAC,IAAI,CAAC,IAAI,gBAAgB,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACjE,IAAI,KAAK,CAAC,MAAM;QAAE,KAAK,CAAC,IAAI,CAAC,IAAI,gBAAgB,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACnE,IAAI,KAAK,CAAC,SAAS;QAAE,KAAK,CAAC,IAAI,CAAC,IAAI,gBAAgB,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;IACzE,IAAI,KAAK,CAAC,UAAU;QAAE,KAAK,CAAC,IAAI,CAAC,IAAI,gBAAgB,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;IAC3E,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK;QAAE,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IAEhE,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACxB,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC7C,IAAI,KAAK,GAAG,IAAI;QAAE,OAAO,KAAK,CAAC,QAAQ,EAAE,CAAC;IAC1C,IAAI,KAAK,GAAG,KAAK;QAAE,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC;IAC1D,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,GAAG,CAAC;AACvC,CAAC"}

View File

@@ -0,0 +1,636 @@
import { type MiniLitRequiredMessages } from "@mariozechner/mini-lit";
declare module "@mariozechner/mini-lit" {
interface i18nMessages extends MiniLitRequiredMessages {
Free: string;
"Input Required": string;
Cancel: string;
Confirm: string;
"Select Model": string;
"Search models...": string;
Format: string;
Thinking: string;
Vision: string;
You: string;
Assistant: string;
"Thinking...": string;
"Type your message...": string;
"API Keys Configuration": string;
"Configure API keys for LLM providers. Keys are stored locally in your browser.": string;
Configured: string;
"Not configured": string;
"✓ Valid": string;
"✗ Invalid": string;
"Testing...": string;
Update: string;
Test: string;
Remove: string;
Save: string;
"Update API key": string;
"Enter API key": string;
"Type a message...": string;
"Failed to fetch file": string;
"Invalid source type": string;
PDF: string;
Document: string;
Presentation: string;
Spreadsheet: string;
Text: string;
"Error loading file": string;
"No text content available": string;
"Failed to load PDF": string;
"Failed to load document": string;
"Failed to load spreadsheet": string;
"Error loading PDF": string;
"Error loading document": string;
"Error loading spreadsheet": string;
"Preview not available for this file type.": string;
"Click the download button above to view it on your computer.": string;
"No content available": string;
"Failed to display text content": string;
"API keys are required to use AI models. Get your keys from the provider's website.": string;
console: string;
"Copy output": string;
"Copied!": string;
"Error:": string;
"Request aborted": string;
Call: string;
Result: string;
"(no result)": string;
"Waiting for tool result…": string;
"Call was aborted; no result.": string;
"No session available": string;
"No session set": string;
"Preparing tool parameters...": string;
"(no output)": string;
Input: string;
Output: string;
"Writing expression...": string;
"Waiting for expression...": string;
Calculating: string;
"Getting current time in": string;
"Getting current date and time": string;
"Waiting for command...": string;
"Writing command...": string;
"Running command...": string;
"Command failed:": string;
"Enter Auth Token": string;
"Please enter your auth token.": string;
"Auth token is required for proxy transport": string;
"Execution aborted": string;
"Code parameter is required": string;
"Unknown error": string;
"Code executed successfully (no output)": string;
"Execution failed": string;
"JavaScript REPL": string;
"JavaScript code to execute": string;
"Writing JavaScript code...": string;
"Executing JavaScript": string;
"Preparing JavaScript...": string;
"Preparing command...": string;
"Preparing calculation...": string;
"Preparing tool...": string;
"Getting time...": string;
"Processing artifact...": string;
"Preparing artifact...": string;
"Processing artifact": string;
"Processed artifact": string;
"Creating artifact": string;
"Created artifact": string;
"Updating artifact": string;
"Updated artifact": string;
"Rewriting artifact": string;
"Rewrote artifact": string;
"Getting artifact": string;
"Got artifact": string;
"Deleting artifact": string;
"Deleted artifact": string;
"Getting logs": string;
"Got logs": string;
"An error occurred": string;
"Copy logs": string;
"Autoscroll enabled": string;
"Autoscroll disabled": string;
Processing: string;
Create: string;
Rewrite: string;
Get: string;
Delete: string;
"Get logs": string;
"Show artifacts": string;
"Close artifacts": string;
Artifacts: string;
"Copy HTML": string;
"Download HTML": string;
"Reload HTML": string;
"Copy SVG": string;
"Download SVG": string;
"Copy Markdown": string;
"Download Markdown": string;
Download: string;
"No logs for {filename}": string;
"API Keys Settings": string;
Settings: string;
"API Keys": string;
Proxy: string;
"Use CORS Proxy": string;
"Proxy URL": string;
"Format: The proxy must accept requests as <proxy-url>/?url=<target-url>": string;
"Settings are stored locally in your browser": string;
Clear: string;
"API Key Required": string;
"Enter your API key for {provider}": string;
"Allows browser-based apps to bypass CORS restrictions when calling LLM providers. Required for Z-AI and Anthropic with OAuth token.": string;
Off: string;
Minimal: string;
Low: string;
Medium: string;
High: string;
"Storage Permission Required": string;
"This app needs persistent storage to save your conversations": string;
"Why is this needed?": string;
"Without persistent storage, your browser may delete saved conversations when it needs disk space. Granting this permission ensures your chat history is preserved.": string;
"What this means:": string;
"Your conversations will be saved locally in your browser": string;
"Data will not be deleted automatically to free up space": string;
"You can still manually clear data at any time": string;
"No data is sent to external servers": string;
"Continue Anyway": string;
"Requesting...": string;
"Grant Permission": string;
Sessions: string;
"Load a previous conversation": string;
"No sessions yet": string;
"Delete this session?": string;
Today: string;
Yesterday: string;
"{days} days ago": string;
messages: string;
tokens: string;
"Drop files here": string;
"Providers & Models": string;
"Cloud Providers": string;
"Cloud LLM providers with predefined models. API keys are stored locally in your browser.": string;
"Custom Providers": string;
"User-configured servers with auto-discovered or manually defined models.": string;
"Add Provider": string;
"No custom providers configured. Click 'Add Provider' to get started.": string;
Models: string;
"auto-discovered": string;
Refresh: string;
Edit: string;
"Are you sure you want to delete this provider?": string;
"Edit Provider": string;
"Provider Name": string;
"e.g., My Ollama Server": string;
"Provider Type": string;
"Base URL": string;
"e.g., http://localhost:11434": string;
"API Key (Optional)": string;
"Leave empty if not required": string;
"Test Connection": string;
Discovered: string;
models: string;
and: string;
more: string;
"For manual provider types, add models after saving the provider.": string;
"Please fill in all required fields": string;
"Failed to save provider": string;
"OpenAI Completions Compatible": string;
"OpenAI Responses Compatible": string;
"Anthropic Messages Compatible": string;
"Checking...": string;
Disconnected: string;
}
}
export declare const translations: {
en: {
Free: string;
"Input Required": string;
Cancel: string;
Confirm: string;
"Select Model": string;
"Search models...": string;
Format: string;
Thinking: string;
Vision: string;
You: string;
Assistant: string;
"Thinking...": string;
"Type your message...": string;
"API Keys Configuration": string;
"Configure API keys for LLM providers. Keys are stored locally in your browser.": string;
Configured: string;
"Not configured": string;
"\u2713 Valid": string;
"\u2717 Invalid": string;
"Testing...": string;
Update: string;
Test: string;
Remove: string;
Save: string;
"Update API key": string;
"Enter API key": string;
"Type a message...": string;
"Failed to fetch file": string;
"Invalid source type": string;
PDF: string;
Document: string;
Presentation: string;
Spreadsheet: string;
Text: string;
"Error loading file": string;
"No text content available": string;
"Failed to load PDF": string;
"Failed to load document": string;
"Failed to load spreadsheet": string;
"Error loading PDF": string;
"Error loading document": string;
"Error loading spreadsheet": string;
"Preview not available for this file type.": string;
"Click the download button above to view it on your computer.": string;
"No content available": string;
"Failed to display text content": string;
"API keys are required to use AI models. Get your keys from the provider's website.": string;
console: string;
"Copy output": string;
"Copied!": string;
"Error:": string;
"Request aborted": string;
Call: string;
Result: string;
"(no result)": string;
"Waiting for tool result\u2026": string;
"Call was aborted; no result.": string;
"No session available": string;
"No session set": string;
"Preparing tool parameters...": string;
"(no output)": string;
Input: string;
Output: string;
"Waiting for expression...": string;
"Writing expression...": string;
Calculating: string;
"Getting current time in": string;
"Getting current date and time": string;
"Waiting for command...": string;
"Writing command...": string;
"Running command...": string;
"Command failed": string;
"Enter Auth Token": string;
"Please enter your auth token.": string;
"Auth token is required for proxy transport": string;
"Execution aborted": string;
"Code parameter is required": string;
"Unknown error": string;
"Code executed successfully (no output)": string;
"Execution failed": string;
"JavaScript REPL": string;
"JavaScript code to execute": string;
"Writing JavaScript code...": string;
"Executing JavaScript": string;
"Preparing JavaScript...": string;
"Preparing command...": string;
"Preparing calculation...": string;
"Preparing tool...": string;
"Getting time...": string;
"Processing artifact...": string;
"Preparing artifact...": string;
"Processing artifact": string;
"Processed artifact": string;
"Creating artifact": string;
"Created artifact": string;
"Updating artifact": string;
"Updated artifact": string;
"Rewriting artifact": string;
"Rewrote artifact": string;
"Getting artifact": string;
"Got artifact": string;
"Deleting artifact": string;
"Deleted artifact": string;
"Getting logs": string;
"Got logs": string;
"An error occurred": string;
"Copy logs": string;
"Autoscroll enabled": string;
"Autoscroll disabled": string;
Processing: string;
Create: string;
Rewrite: string;
Get: string;
"Get logs": string;
"Show artifacts": string;
"Close artifacts": string;
Artifacts: string;
"Copy HTML": string;
"Download HTML": string;
"Reload HTML": string;
"Copy SVG": string;
"Download SVG": string;
"Copy Markdown": string;
"Download Markdown": string;
Download: string;
"No logs for {filename}": string;
"API Keys Settings": string;
Settings: string;
"API Keys": string;
Proxy: string;
"Use CORS Proxy": string;
"Proxy URL": string;
"Format: The proxy must accept requests as <proxy-url>/?url=<target-url>": string;
"Settings are stored locally in your browser": string;
Clear: string;
"API Key Required": string;
"Enter your API key for {provider}": string;
"Allows browser-based apps to bypass CORS restrictions when calling LLM providers. Required for Z-AI and Anthropic with OAuth token.": string;
Off: string;
Minimal: string;
Low: string;
Medium: string;
High: string;
"Storage Permission Required": string;
"This app needs persistent storage to save your conversations": string;
"Why is this needed?": string;
"Without persistent storage, your browser may delete saved conversations when it needs disk space. Granting this permission ensures your chat history is preserved.": string;
"What this means:": string;
"Your conversations will be saved locally in your browser": string;
"Data will not be deleted automatically to free up space": string;
"You can still manually clear data at any time": string;
"No data is sent to external servers": string;
"Continue Anyway": string;
"Requesting...": string;
"Grant Permission": string;
Sessions: string;
"Load a previous conversation": string;
"No sessions yet": string;
"Delete this session?": string;
Today: string;
Yesterday: string;
"{days} days ago": string;
messages: string;
tokens: string;
Delete: string;
"Drop files here": string;
"Command failed:": string;
"Providers & Models": string;
"Cloud Providers": string;
"Cloud LLM providers with predefined models. API keys are stored locally in your browser.": string;
"Custom Providers": string;
"User-configured servers with auto-discovered or manually defined models.": string;
"Add Provider": string;
"No custom providers configured. Click 'Add Provider' to get started.": string;
"auto-discovered": string;
Refresh: string;
Edit: string;
"Are you sure you want to delete this provider?": string;
"Edit Provider": string;
"Provider Name": string;
"e.g., My Ollama Server": string;
"Provider Type": string;
"Base URL": string;
"e.g., http://localhost:11434": string;
"API Key (Optional)": string;
"Leave empty if not required": string;
"Test Connection": string;
Discovered: string;
Models: string;
models: string;
and: string;
more: string;
"For manual provider types, add models after saving the provider.": string;
"Please fill in all required fields": string;
"Failed to save provider": string;
"OpenAI Completions Compatible": string;
"OpenAI Responses Compatible": string;
"Anthropic Messages Compatible": string;
"Checking...": string;
Disconnected: string;
"*": string;
Copy: string;
"Copy code": string;
Close: string;
Preview: string;
Code: string;
"Loading...": string;
"Select an option": string;
"Mode 1": string;
"Mode 2": string;
Required: string;
Optional: string;
};
de: {
Free: string;
"Input Required": string;
Cancel: string;
Confirm: string;
"Select Model": string;
"Search models...": string;
Format: string;
Thinking: string;
Vision: string;
You: string;
Assistant: string;
"Thinking...": string;
"Type your message...": string;
"API Keys Configuration": string;
"Configure API keys for LLM providers. Keys are stored locally in your browser.": string;
Configured: string;
"Not configured": string;
"\u2713 Valid": string;
"\u2717 Invalid": string;
"Testing...": string;
Update: string;
Test: string;
Remove: string;
Save: string;
"Update API key": string;
"Enter API key": string;
"Type a message...": string;
"Failed to fetch file": string;
"Invalid source type": string;
PDF: string;
Document: string;
Presentation: string;
Spreadsheet: string;
Text: string;
"Error loading file": string;
"No text content available": string;
"Failed to load PDF": string;
"Failed to load document": string;
"Failed to load spreadsheet": string;
"Error loading PDF": string;
"Error loading document": string;
"Error loading spreadsheet": string;
"Preview not available for this file type.": string;
"Click the download button above to view it on your computer.": string;
"No content available": string;
"Failed to display text content": string;
"API keys are required to use AI models. Get your keys from the provider's website.": string;
console: string;
"Copy output": string;
"Copied!": string;
"Error:": string;
"Request aborted": string;
Call: string;
Result: string;
"(no result)": string;
"Waiting for tool result\u2026": string;
"Call was aborted; no result.": string;
"No session available": string;
"No session set": string;
"Preparing tool parameters...": string;
"(no output)": string;
Input: string;
Output: string;
"Waiting for expression...": string;
"Writing expression...": string;
Calculating: string;
"Getting current time in": string;
"Getting current date and time": string;
"Waiting for command...": string;
"Writing command...": string;
"Running command...": string;
"Command failed": string;
"Enter Auth Token": string;
"Please enter your auth token.": string;
"Auth token is required for proxy transport": string;
"Execution aborted": string;
"Code parameter is required": string;
"Unknown error": string;
"Code executed successfully (no output)": string;
"Execution failed": string;
"JavaScript REPL": string;
"JavaScript code to execute": string;
"Writing JavaScript code...": string;
"Executing JavaScript": string;
"Preparing JavaScript...": string;
"Preparing command...": string;
"Preparing calculation...": string;
"Preparing tool...": string;
"Getting time...": string;
"Processing artifact...": string;
"Preparing artifact...": string;
"Processing artifact": string;
"Processed artifact": string;
"Creating artifact": string;
"Created artifact": string;
"Updating artifact": string;
"Updated artifact": string;
"Rewriting artifact": string;
"Rewrote artifact": string;
"Getting artifact": string;
"Got artifact": string;
"Deleting artifact": string;
"Deleted artifact": string;
"Getting logs": string;
"Got logs": string;
"An error occurred": string;
"Copy logs": string;
"Autoscroll enabled": string;
"Autoscroll disabled": string;
Processing: string;
Create: string;
Rewrite: string;
Get: string;
"Get logs": string;
"Show artifacts": string;
"Close artifacts": string;
Artifacts: string;
"Copy HTML": string;
"Download HTML": string;
"Reload HTML": string;
"Copy SVG": string;
"Download SVG": string;
"Copy Markdown": string;
"Download Markdown": string;
Download: string;
"No logs for {filename}": string;
"API Keys Settings": string;
Settings: string;
"API Keys": string;
Proxy: string;
"Use CORS Proxy": string;
"Proxy URL": string;
"Format: The proxy must accept requests as <proxy-url>/?url=<target-url>": string;
"Settings are stored locally in your browser": string;
Clear: string;
"API Key Required": string;
"Enter your API key for {provider}": string;
"Allows browser-based apps to bypass CORS restrictions when calling LLM providers. Required for Z-AI and Anthropic with OAuth token.": string;
Off: string;
Minimal: string;
Low: string;
Medium: string;
High: string;
"Storage Permission Required": string;
"This app needs persistent storage to save your conversations": string;
"Why is this needed?": string;
"Without persistent storage, your browser may delete saved conversations when it needs disk space. Granting this permission ensures your chat history is preserved.": string;
"What this means:": string;
"Your conversations will be saved locally in your browser": string;
"Data will not be deleted automatically to free up space": string;
"You can still manually clear data at any time": string;
"No data is sent to external servers": string;
"Continue Anyway": string;
"Requesting...": string;
"Grant Permission": string;
Sessions: string;
"Load a previous conversation": string;
"No sessions yet": string;
"Delete this session?": string;
Today: string;
Yesterday: string;
"{days} days ago": string;
messages: string;
tokens: string;
Delete: string;
"Drop files here": string;
"Command failed:": string;
"Providers & Models": string;
"Cloud Providers": string;
"Cloud LLM providers with predefined models. API keys are stored locally in your browser.": string;
"Custom Providers": string;
"User-configured servers with auto-discovered or manually defined models.": string;
"Add Provider": string;
"No custom providers configured. Click 'Add Provider' to get started.": string;
"auto-discovered": string;
Refresh: string;
Edit: string;
"Are you sure you want to delete this provider?": string;
"Edit Provider": string;
"Provider Name": string;
"e.g., My Ollama Server": string;
"Provider Type": string;
"Base URL": string;
"e.g., http://localhost:11434": string;
"API Key (Optional)": string;
"Leave empty if not required": string;
"Test Connection": string;
Discovered: string;
Models: string;
models: string;
and: string;
more: string;
"For manual provider types, add models after saving the provider.": string;
"Please fill in all required fields": string;
"Failed to save provider": string;
"OpenAI Completions Compatible": string;
"OpenAI Responses Compatible": string;
"Anthropic Messages Compatible": string;
"Checking...": string;
Disconnected: string;
"*": string;
Copy: string;
"Copy code": string;
Close: string;
Preview: string;
Code: string;
"Loading...": string;
"Select an option": string;
"Mode 1": string;
"Mode 2": string;
Required: string;
Optional: string;
};
};
export * from "@mariozechner/mini-lit/dist/i18n.js";
//# sourceMappingURL=i18n.d.ts.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,418 @@
import { defaultEnglish, defaultGerman, setTranslations } from "@mariozechner/mini-lit";
export const translations = {
en: {
...defaultEnglish,
Free: "Free",
"Input Required": "Input Required",
Cancel: "Cancel",
Confirm: "Confirm",
"Select Model": "Select Model",
"Search models...": "Search models...",
Format: "Format",
Thinking: "Thinking",
Vision: "Vision",
You: "You",
Assistant: "Assistant",
"Thinking...": "Thinking...",
"Type your message...": "Type your message...",
"API Keys Configuration": "API Keys Configuration",
"Configure API keys for LLM providers. Keys are stored locally in your browser.": "Configure API keys for LLM providers. Keys are stored locally in your browser.",
Configured: "Configured",
"Not configured": "Not configured",
"✓ Valid": "✓ Valid",
"✗ Invalid": "✗ Invalid",
"Testing...": "Testing...",
Update: "Update",
Test: "Test",
Remove: "Remove",
Save: "Save",
"Update API key": "Update API key",
"Enter API key": "Enter API key",
"Type a message...": "Type a message...",
"Failed to fetch file": "Failed to fetch file",
"Invalid source type": "Invalid source type",
PDF: "PDF",
Document: "Document",
Presentation: "Presentation",
Spreadsheet: "Spreadsheet",
Text: "Text",
"Error loading file": "Error loading file",
"No text content available": "No text content available",
"Failed to load PDF": "Failed to load PDF",
"Failed to load document": "Failed to load document",
"Failed to load spreadsheet": "Failed to load spreadsheet",
"Error loading PDF": "Error loading PDF",
"Error loading document": "Error loading document",
"Error loading spreadsheet": "Error loading spreadsheet",
"Preview not available for this file type.": "Preview not available for this file type.",
"Click the download button above to view it on your computer.": "Click the download button above to view it on your computer.",
"No content available": "No content available",
"Failed to display text content": "Failed to display text content",
"API keys are required to use AI models. Get your keys from the provider's website.": "API keys are required to use AI models. Get your keys from the provider's website.",
console: "console",
"Copy output": "Copy output",
"Copied!": "Copied!",
"Error:": "Error:",
"Request aborted": "Request aborted",
Call: "Call",
Result: "Result",
"(no result)": "(no result)",
"Waiting for tool result…": "Waiting for tool result…",
"Call was aborted; no result.": "Call was aborted; no result.",
"No session available": "No session available",
"No session set": "No session set",
"Preparing tool parameters...": "Preparing tool parameters...",
"(no output)": "(no output)",
Input: "Input",
Output: "Output",
"Waiting for expression...": "Waiting for expression...",
"Writing expression...": "Writing expression...",
Calculating: "Calculating",
"Getting current time in": "Getting current time in",
"Getting current date and time": "Getting current date and time",
"Waiting for command...": "Waiting for command...",
"Writing command...": "Writing command...",
"Running command...": "Running command...",
"Command failed": "Command failed",
"Enter Auth Token": "Enter Auth Token",
"Please enter your auth token.": "Please enter your auth token.",
"Auth token is required for proxy transport": "Auth token is required for proxy transport",
// JavaScript REPL strings
"Execution aborted": "Execution aborted",
"Code parameter is required": "Code parameter is required",
"Unknown error": "Unknown error",
"Code executed successfully (no output)": "Code executed successfully (no output)",
"Execution failed": "Execution failed",
"JavaScript REPL": "JavaScript REPL",
"JavaScript code to execute": "JavaScript code to execute",
"Writing JavaScript code...": "Writing JavaScript code...",
"Executing JavaScript": "Executing JavaScript",
"Preparing JavaScript...": "Preparing JavaScript...",
"Preparing command...": "Preparing command...",
"Preparing calculation...": "Preparing calculation...",
"Preparing tool...": "Preparing tool...",
"Getting time...": "Getting time...",
// Artifacts strings
"Processing artifact...": "Processing artifact...",
"Preparing artifact...": "Preparing artifact...",
"Processing artifact": "Processing artifact",
"Processed artifact": "Processed artifact",
"Creating artifact": "Creating artifact",
"Created artifact": "Created artifact",
"Updating artifact": "Updating artifact",
"Updated artifact": "Updated artifact",
"Rewriting artifact": "Rewriting artifact",
"Rewrote artifact": "Rewrote artifact",
"Getting artifact": "Getting artifact",
"Got artifact": "Got artifact",
"Deleting artifact": "Deleting artifact",
"Deleted artifact": "Deleted artifact",
"Getting logs": "Getting logs",
"Got logs": "Got logs",
"An error occurred": "An error occurred",
"Copy logs": "Copy logs",
"Autoscroll enabled": "Autoscroll enabled",
"Autoscroll disabled": "Autoscroll disabled",
Processing: "Processing",
Create: "Create",
Rewrite: "Rewrite",
Get: "Get",
"Get logs": "Get logs",
"Show artifacts": "Show artifacts",
"Close artifacts": "Close artifacts",
Artifacts: "Artifacts",
"Copy HTML": "Copy HTML",
"Download HTML": "Download HTML",
"Reload HTML": "Reload HTML",
"Copy SVG": "Copy SVG",
"Download SVG": "Download SVG",
"Copy Markdown": "Copy Markdown",
"Download Markdown": "Download Markdown",
Download: "Download",
"No logs for {filename}": "No logs for {filename}",
"API Keys Settings": "API Keys Settings",
Settings: "Settings",
"API Keys": "API Keys",
Proxy: "Proxy",
"Use CORS Proxy": "Use CORS Proxy",
"Proxy URL": "Proxy URL",
"Format: The proxy must accept requests as <proxy-url>/?url=<target-url>": "Format: The proxy must accept requests as <proxy-url>/?url=<target-url>",
"Settings are stored locally in your browser": "Settings are stored locally in your browser",
Clear: "Clear",
"API Key Required": "API Key Required",
"Enter your API key for {provider}": "Enter your API key for {provider}",
"Allows browser-based apps to bypass CORS restrictions when calling LLM providers. Required for Z-AI and Anthropic with OAuth token.": "Allows browser-based apps to bypass CORS restrictions when calling LLM providers. Required for Z-AI and Anthropic with OAuth token.",
Off: "Off",
Minimal: "Minimal",
Low: "Low",
Medium: "Medium",
High: "High",
"Storage Permission Required": "Storage Permission Required",
"This app needs persistent storage to save your conversations": "This app needs persistent storage to save your conversations",
"Why is this needed?": "Why is this needed?",
"Without persistent storage, your browser may delete saved conversations when it needs disk space. Granting this permission ensures your chat history is preserved.": "Without persistent storage, your browser may delete saved conversations when it needs disk space. Granting this permission ensures your chat history is preserved.",
"What this means:": "What this means:",
"Your conversations will be saved locally in your browser": "Your conversations will be saved locally in your browser",
"Data will not be deleted automatically to free up space": "Data will not be deleted automatically to free up space",
"You can still manually clear data at any time": "You can still manually clear data at any time",
"No data is sent to external servers": "No data is sent to external servers",
"Continue Anyway": "Continue Anyway",
"Requesting...": "Requesting...",
"Grant Permission": "Grant Permission",
Sessions: "Sessions",
"Load a previous conversation": "Load a previous conversation",
"No sessions yet": "No sessions yet",
"Delete this session?": "Delete this session?",
Today: "Today",
Yesterday: "Yesterday",
"{days} days ago": "{days} days ago",
messages: "messages",
tokens: "tokens",
Delete: "Delete",
"Drop files here": "Drop files here",
"Command failed:": "Command failed:",
// Providers & Models
"Providers & Models": "Providers & Models",
"Cloud Providers": "Cloud Providers",
"Cloud LLM providers with predefined models. API keys are stored locally in your browser.": "Cloud LLM providers with predefined models. API keys are stored locally in your browser.",
"Custom Providers": "Custom Providers",
"User-configured servers with auto-discovered or manually defined models.": "User-configured servers with auto-discovered or manually defined models.",
"Add Provider": "Add Provider",
"No custom providers configured. Click 'Add Provider' to get started.": "No custom providers configured. Click 'Add Provider' to get started.",
"auto-discovered": "auto-discovered",
Refresh: "Refresh",
Edit: "Edit",
"Are you sure you want to delete this provider?": "Are you sure you want to delete this provider?",
"Edit Provider": "Edit Provider",
"Provider Name": "Provider Name",
"e.g., My Ollama Server": "e.g., My Ollama Server",
"Provider Type": "Provider Type",
"Base URL": "Base URL",
"e.g., http://localhost:11434": "e.g., http://localhost:11434",
"API Key (Optional)": "API Key (Optional)",
"Leave empty if not required": "Leave empty if not required",
"Test Connection": "Test Connection",
Discovered: "Discovered",
Models: "Models",
models: "models",
and: "and",
more: "more",
"For manual provider types, add models after saving the provider.": "For manual provider types, add models after saving the provider.",
"Please fill in all required fields": "Please fill in all required fields",
"Failed to save provider": "Failed to save provider",
"OpenAI Completions Compatible": "OpenAI Completions Compatible",
"OpenAI Responses Compatible": "OpenAI Responses Compatible",
"Anthropic Messages Compatible": "Anthropic Messages Compatible",
"Checking...": "Checking...",
Disconnected: "Disconnected",
},
de: {
...defaultGerman,
Free: "Kostenlos",
"Input Required": "Eingabe erforderlich",
Cancel: "Abbrechen",
Confirm: "Bestätigen",
"Select Model": "Modell auswählen",
"Search models...": "Modelle suchen...",
Format: "Formatieren",
Thinking: "Thinking",
Vision: "Vision",
You: "Sie",
Assistant: "Assistent",
"Thinking...": "Denkt nach...",
"Type your message...": "Geben Sie Ihre Nachricht ein...",
"API Keys Configuration": "API-Schlüssel-Konfiguration",
"Configure API keys for LLM providers. Keys are stored locally in your browser.": "Konfigurieren Sie API-Schlüssel für LLM-Anbieter. Schlüssel werden lokal in Ihrem Browser gespeichert.",
Configured: "Konfiguriert",
"Not configured": "Nicht konfiguriert",
"✓ Valid": "✓ Gültig",
"✗ Invalid": "✗ Ungültig",
"Testing...": "Teste...",
Update: "Aktualisieren",
Test: "Testen",
Remove: "Entfernen",
Save: "Speichern",
"Update API key": "API-Schlüssel aktualisieren",
"Enter API key": "API-Schlüssel eingeben",
"Type a message...": "Nachricht eingeben...",
"Failed to fetch file": "Datei konnte nicht abgerufen werden",
"Invalid source type": "Ungültiger Quellentyp",
PDF: "PDF",
Document: "Dokument",
Presentation: "Präsentation",
Spreadsheet: "Tabelle",
Text: "Text",
"Error loading file": "Fehler beim Laden der Datei",
"No text content available": "Kein Textinhalt verfügbar",
"Failed to load PDF": "PDF konnte nicht geladen werden",
"Failed to load document": "Dokument konnte nicht geladen werden",
"Failed to load spreadsheet": "Tabelle konnte nicht geladen werden",
"Error loading PDF": "Fehler beim Laden des PDFs",
"Error loading document": "Fehler beim Laden des Dokuments",
"Error loading spreadsheet": "Fehler beim Laden der Tabelle",
"Preview not available for this file type.": "Vorschau für diesen Dateityp nicht verfügbar.",
"Click the download button above to view it on your computer.": "Klicken Sie oben auf die Download-Schaltfläche, um die Datei auf Ihrem Computer anzuzeigen.",
"No content available": "Kein Inhalt verfügbar",
"Failed to display text content": "Textinhalt konnte nicht angezeigt werden",
"API keys are required to use AI models. Get your keys from the provider's website.": "API-Schlüssel sind erforderlich, um KI-Modelle zu verwenden. Holen Sie sich Ihre Schlüssel von der Website des Anbieters.",
console: "Konsole",
"Copy output": "Ausgabe kopieren",
"Copied!": "Kopiert!",
"Error:": "Fehler:",
"Request aborted": "Anfrage abgebrochen",
Call: "Aufruf",
Result: "Ergebnis",
"(no result)": "(kein Ergebnis)",
"Waiting for tool result…": "Warte auf Tool-Ergebnis…",
"Call was aborted; no result.": "Aufruf wurde abgebrochen; kein Ergebnis.",
"No session available": "Keine Sitzung verfügbar",
"No session set": "Keine Sitzung gesetzt",
"Preparing tool parameters...": "Bereite Tool-Parameter vor...",
"(no output)": "(keine Ausgabe)",
Input: "Eingabe",
Output: "Ausgabe",
"Waiting for expression...": "Warte auf Ausdruck",
"Writing expression...": "Schreibe Ausdruck...",
Calculating: "Berechne",
"Getting current time in": "Hole aktuelle Zeit in",
"Getting current date and time": "Hole aktuelles Datum und Uhrzeit",
"Waiting for command...": "Warte auf Befehl...",
"Writing command...": "Schreibe Befehl...",
"Running command...": "Führe Befehl aus...",
"Command failed": "Befehl fehlgeschlagen",
"Enter Auth Token": "Auth-Token eingeben",
"Please enter your auth token.": "Bitte geben Sie Ihr Auth-Token ein.",
"Auth token is required for proxy transport": "Auth-Token ist für Proxy-Transport erforderlich",
// JavaScript REPL strings
"Execution aborted": "Ausführung abgebrochen",
"Code parameter is required": "Code-Parameter ist erforderlich",
"Unknown error": "Unbekannter Fehler",
"Code executed successfully (no output)": "Code erfolgreich ausgeführt (keine Ausgabe)",
"Execution failed": "Ausführung fehlgeschlagen",
"JavaScript REPL": "JavaScript REPL",
"JavaScript code to execute": "Auszuführender JavaScript-Code",
"Writing JavaScript code...": "Schreibe JavaScript-Code...",
"Executing JavaScript": "Führe JavaScript aus",
"Preparing JavaScript...": "Bereite JavaScript vor...",
"Preparing command...": "Bereite Befehl vor...",
"Preparing calculation...": "Bereite Berechnung vor...",
"Preparing tool...": "Bereite Tool vor...",
"Getting time...": "Hole Zeit...",
// Artifacts strings
"Processing artifact...": "Verarbeite Artefakt...",
"Preparing artifact...": "Bereite Artefakt vor...",
"Processing artifact": "Verarbeite Artefakt",
"Processed artifact": "Artefakt verarbeitet",
"Creating artifact": "Erstelle Artefakt",
"Created artifact": "Artefakt erstellt",
"Updating artifact": "Aktualisiere Artefakt",
"Updated artifact": "Artefakt aktualisiert",
"Rewriting artifact": "Überschreibe Artefakt",
"Rewrote artifact": "Artefakt überschrieben",
"Getting artifact": "Hole Artefakt",
"Got artifact": "Artefakt geholt",
"Deleting artifact": "Lösche Artefakt",
"Deleted artifact": "Artefakt gelöscht",
"Getting logs": "Hole Logs",
"Got logs": "Logs geholt",
"An error occurred": "Ein Fehler ist aufgetreten",
"Copy logs": "Logs kopieren",
"Autoscroll enabled": "Automatisches Scrollen aktiviert",
"Autoscroll disabled": "Automatisches Scrollen deaktiviert",
Processing: "Verarbeitung",
Create: "Erstellen",
Rewrite: "Überschreiben",
Get: "Abrufen",
"Get logs": "Logs abrufen",
"Show artifacts": "Artefakte anzeigen",
"Close artifacts": "Artefakte schließen",
Artifacts: "Artefakte",
"Copy HTML": "HTML kopieren",
"Download HTML": "HTML herunterladen",
"Reload HTML": "HTML neu laden",
"Copy SVG": "SVG kopieren",
"Download SVG": "SVG herunterladen",
"Copy Markdown": "Markdown kopieren",
"Download Markdown": "Markdown herunterladen",
Download: "Herunterladen",
"No logs for {filename}": "Keine Logs für {filename}",
"API Keys Settings": "API-Schlüssel Einstellungen",
Settings: "Einstellungen",
"API Keys": "API-Schlüssel",
Proxy: "Proxy",
"Use CORS Proxy": "CORS-Proxy verwenden",
"Proxy URL": "Proxy-URL",
"Format: The proxy must accept requests as <proxy-url>/?url=<target-url>": "Format: Der Proxy muss Anfragen als <proxy-url>/?url=<ziel-url> akzeptieren",
"Settings are stored locally in your browser": "Einstellungen werden lokal in Ihrem Browser gespeichert",
Clear: "Löschen",
"API Key Required": "API-Schlüssel erforderlich",
"Enter your API key for {provider}": "Geben Sie Ihren API-Schlüssel für {provider} ein",
"Allows browser-based apps to bypass CORS restrictions when calling LLM providers. Required for Z-AI and Anthropic with OAuth token.": "Ermöglicht browserbasierten Anwendungen, CORS-Einschränkungen beim Aufruf von LLM-Anbietern zu umgehen. Erforderlich für Z-AI und Anthropic mit OAuth-Token.",
Off: "Aus",
Minimal: "Minimal",
Low: "Niedrig",
Medium: "Mittel",
High: "Hoch",
"Storage Permission Required": "Speicherberechtigung erforderlich",
"This app needs persistent storage to save your conversations": "Diese App benötigt dauerhaften Speicher, um Ihre Konversationen zu speichern",
"Why is this needed?": "Warum wird das benötigt?",
"Without persistent storage, your browser may delete saved conversations when it needs disk space. Granting this permission ensures your chat history is preserved.": "Ohne dauerhaften Speicher kann Ihr Browser gespeicherte Konversationen löschen, wenn Speicherplatz benötigt wird. Diese Berechtigung stellt sicher, dass Ihr Chatverlauf erhalten bleibt.",
"What this means:": "Was das bedeutet:",
"Your conversations will be saved locally in your browser": "Ihre Konversationen werden lokal in Ihrem Browser gespeichert",
"Data will not be deleted automatically to free up space": "Daten werden nicht automatisch gelöscht, um Speicherplatz freizugeben",
"You can still manually clear data at any time": "Sie können Daten jederzeit manuell löschen",
"No data is sent to external servers": "Keine Daten werden an externe Server gesendet",
"Continue Anyway": "Trotzdem fortfahren",
"Requesting...": "Anfrage läuft...",
"Grant Permission": "Berechtigung erteilen",
Sessions: "Sitzungen",
"Load a previous conversation": "Frühere Konversation laden",
"No sessions yet": "Noch keine Sitzungen",
"Delete this session?": "Diese Sitzung löschen?",
Today: "Heute",
Yesterday: "Gestern",
"{days} days ago": "vor {days} Tagen",
messages: "Nachrichten",
tokens: "Tokens",
Delete: "Löschen",
"Drop files here": "Dateien hier ablegen",
"Command failed:": "Befehl fehlgeschlagen:",
// Providers & Models
"Providers & Models": "Anbieter & Modelle",
"Cloud Providers": "Cloud-Anbieter",
"Cloud LLM providers with predefined models. API keys are stored locally in your browser.": "Cloud-LLM-Anbieter mit vordefinierten Modellen. API-Schlüssel werden lokal in Ihrem Browser gespeichert.",
"Custom Providers": "Benutzerdefinierte Anbieter",
"User-configured servers with auto-discovered or manually defined models.": "Benutzerkonfigurierte Server mit automatisch erkannten oder manuell definierten Modellen.",
"Add Provider": "Anbieter hinzufügen",
"No custom providers configured. Click 'Add Provider' to get started.": "Keine benutzerdefinierten Anbieter konfiguriert. Klicken Sie auf 'Anbieter hinzufügen', um zu beginnen.",
"auto-discovered": "automatisch erkannt",
Refresh: "Aktualisieren",
Edit: "Bearbeiten",
"Are you sure you want to delete this provider?": "Sind Sie sicher, dass Sie diesen Anbieter löschen möchten?",
"Edit Provider": "Anbieter bearbeiten",
"Provider Name": "Anbietername",
"e.g., My Ollama Server": "z.B. Mein Ollama Server",
"Provider Type": "Anbietertyp",
"Base URL": "Basis-URL",
"e.g., http://localhost:11434": "z.B. http://localhost:11434",
"API Key (Optional)": "API-Schlüssel (Optional)",
"Leave empty if not required": "Leer lassen, falls nicht erforderlich",
"Test Connection": "Verbindung testen",
Discovered: "Erkannt",
Models: "Modelle",
models: "Modelle",
and: "und",
more: "mehr",
"For manual provider types, add models after saving the provider.": "Für manuelle Anbietertypen fügen Sie Modelle nach dem Speichern des Anbieters hinzu.",
"Please fill in all required fields": "Bitte füllen Sie alle erforderlichen Felder aus",
"Failed to save provider": "Fehler beim Speichern des Anbieters",
"OpenAI Completions Compatible": "OpenAI Completions Kompatibel",
"OpenAI Responses Compatible": "OpenAI Responses Kompatibel",
"Anthropic Messages Compatible": "Anthropic Messages Kompatibel",
"Checking...": "Überprüfe...",
Disconnected: "Getrennt",
},
};
setTranslations(translations);
export * from "@mariozechner/mini-lit/dist/i18n.js";
//# sourceMappingURL=i18n.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,38 @@
import type { Model } from "@mariozechner/pi-ai";
/**
* Discover models from an Ollama server.
* @param baseUrl - Base URL of the Ollama server (e.g., "http://localhost:11434")
* @param apiKey - Optional API key (currently unused by Ollama)
* @returns Array of discovered models
*/
export declare function discoverOllamaModels(baseUrl: string, _apiKey?: string): Promise<Model<any>[]>;
/**
* Discover models from a llama.cpp server via OpenAI-compatible /v1/models endpoint.
* @param baseUrl - Base URL of the llama.cpp server (e.g., "http://localhost:8080")
* @param apiKey - Optional API key
* @returns Array of discovered models
*/
export declare function discoverLlamaCppModels(baseUrl: string, apiKey?: string): Promise<Model<any>[]>;
/**
* Discover models from a vLLM server via OpenAI-compatible /v1/models endpoint.
* @param baseUrl - Base URL of the vLLM server (e.g., "http://localhost:8000")
* @param apiKey - Optional API key
* @returns Array of discovered models
*/
export declare function discoverVLLMModels(baseUrl: string, apiKey?: string): Promise<Model<any>[]>;
/**
* Discover models from an LM Studio server using the LM Studio SDK.
* @param baseUrl - Base URL of the LM Studio server (e.g., "http://localhost:1234")
* @param apiKey - Optional API key (unused for LM Studio SDK)
* @returns Array of discovered models
*/
export declare function discoverLMStudioModels(baseUrl: string, _apiKey?: string): Promise<Model<any>[]>;
/**
* Convenience function to discover models based on provider type.
* @param type - Provider type
* @param baseUrl - Base URL of the server
* @param apiKey - Optional API key
* @returns Array of discovered models
*/
export declare function discoverModels(type: "ollama" | "llama.cpp" | "vllm" | "lmstudio", baseUrl: string, apiKey?: string): Promise<Model<any>[]>;
//# sourceMappingURL=model-discovery.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"model-discovery.d.ts","sourceRoot":"","sources":["../../src/utils/model-discovery.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAGjD;;;;;GAKG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAkEnG;AAED;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAsDpG;AAED;;;;;GAKG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAsDhG;AAED;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CA4CrG;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CACnC,IAAI,EAAE,QAAQ,GAAG,WAAW,GAAG,MAAM,GAAG,UAAU,EAClD,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAWvB"}

View File

@@ -0,0 +1,243 @@
import { LMStudioClient } from "@lmstudio/sdk";
import { Ollama } from "ollama/browser";
/**
* Discover models from an Ollama server.
* @param baseUrl - Base URL of the Ollama server (e.g., "http://localhost:11434")
* @param apiKey - Optional API key (currently unused by Ollama)
* @returns Array of discovered models
*/
export async function discoverOllamaModels(baseUrl, _apiKey) {
try {
// Create Ollama client
const ollama = new Ollama({ host: baseUrl });
// Get list of available models
const { models } = await ollama.list();
// Fetch details for each model and convert to Model format
const ollamaModelPromises = models.map(async (model) => {
try {
// Get model details
const details = await ollama.show({
model: model.name,
});
// Check capabilities - filter out models that don't support tools
const capabilities = details.capabilities || [];
if (!capabilities.includes("tools")) {
console.debug(`Skipping model ${model.name}: does not support tools`);
return null;
}
// Extract model info
const modelInfo = details.model_info || {};
// Get context window size - look for architecture-specific keys
const architecture = modelInfo["general.architecture"] || "";
const contextKey = `${architecture}.context_length`;
const contextWindow = parseInt(modelInfo[contextKey] || "8192", 10);
// Ollama caps max tokens at 10x context length
const maxTokens = contextWindow * 10;
// Ollama only supports completions API
const ollamaModel = {
id: model.name,
name: model.name,
api: "openai-completions",
provider: "", // Will be set by caller
baseUrl: `${baseUrl}/v1`,
reasoning: capabilities.includes("thinking"),
input: ["text"],
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: contextWindow,
maxTokens: maxTokens,
};
return ollamaModel;
}
catch (err) {
console.error(`Failed to fetch details for model ${model.name}:`, err);
return null;
}
});
const results = await Promise.all(ollamaModelPromises);
return results.filter((m) => m !== null);
}
catch (err) {
console.error("Failed to discover Ollama models:", err);
throw new Error(`Ollama discovery failed: ${err instanceof Error ? err.message : String(err)}`);
}
}
/**
* Discover models from a llama.cpp server via OpenAI-compatible /v1/models endpoint.
* @param baseUrl - Base URL of the llama.cpp server (e.g., "http://localhost:8080")
* @param apiKey - Optional API key
* @returns Array of discovered models
*/
export async function discoverLlamaCppModels(baseUrl, apiKey) {
try {
const headers = {
"Content-Type": "application/json",
};
if (apiKey) {
headers.Authorization = `Bearer ${apiKey}`;
}
const response = await fetch(`${baseUrl}/v1/models`, {
method: "GET",
headers,
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
if (!data.data || !Array.isArray(data.data)) {
throw new Error("Invalid response format from llama.cpp server");
}
return data.data.map((model) => {
// llama.cpp doesn't always provide context window info
const contextWindow = model.context_length || 8192;
const maxTokens = model.max_tokens || 4096;
const llamaModel = {
id: model.id,
name: model.id,
api: "openai-completions",
provider: "", // Will be set by caller
baseUrl: `${baseUrl}/v1`,
reasoning: false,
input: ["text"],
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: contextWindow,
maxTokens: maxTokens,
};
return llamaModel;
});
}
catch (err) {
console.error("Failed to discover llama.cpp models:", err);
throw new Error(`llama.cpp discovery failed: ${err instanceof Error ? err.message : String(err)}`);
}
}
/**
* Discover models from a vLLM server via OpenAI-compatible /v1/models endpoint.
* @param baseUrl - Base URL of the vLLM server (e.g., "http://localhost:8000")
* @param apiKey - Optional API key
* @returns Array of discovered models
*/
export async function discoverVLLMModels(baseUrl, apiKey) {
try {
const headers = {
"Content-Type": "application/json",
};
if (apiKey) {
headers.Authorization = `Bearer ${apiKey}`;
}
const response = await fetch(`${baseUrl}/v1/models`, {
method: "GET",
headers,
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
if (!data.data || !Array.isArray(data.data)) {
throw new Error("Invalid response format from vLLM server");
}
return data.data.map((model) => {
// vLLM provides max_model_len which is the context window
const contextWindow = model.max_model_len || 8192;
const maxTokens = Math.min(contextWindow, 4096); // Cap max tokens
const vllmModel = {
id: model.id,
name: model.id,
api: "openai-completions",
provider: "", // Will be set by caller
baseUrl: `${baseUrl}/v1`,
reasoning: false,
input: ["text"],
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: contextWindow,
maxTokens: maxTokens,
};
return vllmModel;
});
}
catch (err) {
console.error("Failed to discover vLLM models:", err);
throw new Error(`vLLM discovery failed: ${err instanceof Error ? err.message : String(err)}`);
}
}
/**
* Discover models from an LM Studio server using the LM Studio SDK.
* @param baseUrl - Base URL of the LM Studio server (e.g., "http://localhost:1234")
* @param apiKey - Optional API key (unused for LM Studio SDK)
* @returns Array of discovered models
*/
export async function discoverLMStudioModels(baseUrl, _apiKey) {
try {
// Extract host and port from baseUrl
const url = new URL(baseUrl);
const port = url.port ? parseInt(url.port, 10) : 1234;
// Create LM Studio client
const client = new LMStudioClient({ baseUrl: `ws://${url.hostname}:${port}` });
// List all downloaded models
const models = await client.system.listDownloadedModels();
// Filter to only LLM models and map to our Model format
return models
.filter((model) => model.type === "llm")
.map((model) => {
const contextWindow = model.maxContextLength;
// Use 10x context length like Ollama does
const maxTokens = contextWindow;
const lmStudioModel = {
id: model.path,
name: model.displayName || model.path,
api: "openai-completions",
provider: "", // Will be set by caller
baseUrl: `${baseUrl}/v1`,
reasoning: model.trainedForToolUse || false,
input: model.vision ? ["text", "image"] : ["text"],
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: contextWindow,
maxTokens: maxTokens,
};
return lmStudioModel;
});
}
catch (err) {
console.error("Failed to discover LM Studio models:", err);
throw new Error(`LM Studio discovery failed: ${err instanceof Error ? err.message : String(err)}`);
}
}
/**
* Convenience function to discover models based on provider type.
* @param type - Provider type
* @param baseUrl - Base URL of the server
* @param apiKey - Optional API key
* @returns Array of discovered models
*/
export async function discoverModels(type, baseUrl, apiKey) {
switch (type) {
case "ollama":
return discoverOllamaModels(baseUrl, apiKey);
case "llama.cpp":
return discoverLlamaCppModels(baseUrl, apiKey);
case "vllm":
return discoverVLLMModels(baseUrl, apiKey);
case "lmstudio":
return discoverLMStudioModels(baseUrl, apiKey);
}
}
//# sourceMappingURL=model-discovery.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,37 @@
import type { Api, Model } from "@mariozechner/pi-ai";
/**
* Centralized proxy decision logic.
*
* Determines whether to use a CORS proxy for LLM API requests based on:
* - Provider name
* - API key pattern (for providers where it matters)
*/
/**
* Check if a provider/API key combination requires a CORS proxy.
*
* @param provider - Provider name (e.g., "anthropic", "openai", "zai")
* @param apiKey - API key for the provider
* @returns true if proxy is required, false otherwise
*/
export declare function shouldUseProxyForProvider(provider: string, apiKey: string): boolean;
/**
* Apply CORS proxy to a model's baseUrl if needed.
*
* @param model - The model to potentially proxy
* @param apiKey - API key for the provider
* @param proxyUrl - CORS proxy URL (e.g., "https://proxy.mariozechner.at/proxy")
* @returns Model with modified baseUrl if proxy is needed, otherwise original model
*/
export declare function applyProxyIfNeeded<T extends Api>(model: Model<T>, apiKey: string, proxyUrl?: string): Model<T>;
/**
* Check if an error is likely a CORS error.
*
* CORS errors in browsers typically manifest as:
* - TypeError with message "Failed to fetch"
* - NetworkError
*
* @param error - The error to check
* @returns true if error is likely a CORS error
*/
export declare function isCorsError(error: unknown): boolean;
//# sourceMappingURL=proxy-utils.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"proxy-utils.d.ts","sourceRoot":"","sources":["../../src/utils/proxy-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAEtD;;;;;;GAMG;AAEH;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CA2BnF;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,GAAG,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,CAqB9G;AAED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAwBnD"}

View File

@@ -0,0 +1,97 @@
/**
* Centralized proxy decision logic.
*
* Determines whether to use a CORS proxy for LLM API requests based on:
* - Provider name
* - API key pattern (for providers where it matters)
*/
/**
* Check if a provider/API key combination requires a CORS proxy.
*
* @param provider - Provider name (e.g., "anthropic", "openai", "zai")
* @param apiKey - API key for the provider
* @returns true if proxy is required, false otherwise
*/
export function shouldUseProxyForProvider(provider, apiKey) {
switch (provider.toLowerCase()) {
case "zai":
// Z-AI always requires proxy
return true;
case "anthropic":
// Anthropic OAuth tokens (sk-ant-oat-*) require proxy
// Regular API keys (sk-ant-api-*) do NOT require proxy
return apiKey.startsWith("sk-ant-oat");
// These providers work without proxy
case "openai":
case "google":
case "groq":
case "openrouter":
case "cerebras":
case "xai":
case "ollama":
case "lmstudio":
return false;
// Unknown providers - assume no proxy needed
// This allows new providers to work by default
default:
return false;
}
}
/**
* Apply CORS proxy to a model's baseUrl if needed.
*
* @param model - The model to potentially proxy
* @param apiKey - API key for the provider
* @param proxyUrl - CORS proxy URL (e.g., "https://proxy.mariozechner.at/proxy")
* @returns Model with modified baseUrl if proxy is needed, otherwise original model
*/
export function applyProxyIfNeeded(model, apiKey, proxyUrl) {
// If no proxy URL configured, return original model
if (!proxyUrl) {
return model;
}
// If model has no baseUrl, can't proxy it
if (!model.baseUrl) {
return model;
}
// Check if this provider/key needs proxy
if (!shouldUseProxyForProvider(model.provider, apiKey)) {
return model;
}
// Apply proxy to baseUrl
return {
...model,
baseUrl: `${proxyUrl}/?url=${encodeURIComponent(model.baseUrl)}`,
};
}
/**
* Check if an error is likely a CORS error.
*
* CORS errors in browsers typically manifest as:
* - TypeError with message "Failed to fetch"
* - NetworkError
*
* @param error - The error to check
* @returns true if error is likely a CORS error
*/
export function isCorsError(error) {
if (!(error instanceof Error)) {
return false;
}
// Check for common CORS error patterns
const message = error.message.toLowerCase();
// "Failed to fetch" is the standard CORS error in most browsers
if (error.name === "TypeError" && message.includes("failed to fetch")) {
return true;
}
// Some browsers report "NetworkError"
if (error.name === "NetworkError") {
return true;
}
// CORS-specific messages
if (message.includes("cors") || message.includes("cross-origin")) {
return true;
}
return false;
}
//# sourceMappingURL=proxy-utils.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"proxy-utils.js","sourceRoot":"","sources":["../../src/utils/proxy-utils.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AAEH;;;;;;GAMG;AACH,MAAM,UAAU,yBAAyB,CAAC,QAAgB,EAAE,MAAc;IACzE,QAAQ,QAAQ,CAAC,WAAW,EAAE,EAAE,CAAC;QAChC,KAAK,KAAK;YACT,6BAA6B;YAC7B,OAAO,IAAI,CAAC;QAEb,KAAK,WAAW;YACf,sDAAsD;YACtD,uDAAuD;YACvD,OAAO,MAAM,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;QAExC,qCAAqC;QACrC,KAAK,QAAQ,CAAC;QACd,KAAK,QAAQ,CAAC;QACd,KAAK,MAAM,CAAC;QACZ,KAAK,YAAY,CAAC;QAClB,KAAK,UAAU,CAAC;QAChB,KAAK,KAAK,CAAC;QACX,KAAK,QAAQ,CAAC;QACd,KAAK,UAAU;YACd,OAAO,KAAK,CAAC;QAEd,6CAA6C;QAC7C,+CAA+C;QAC/C;YACC,OAAO,KAAK,CAAC;IACf,CAAC;AACF,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAAgB,KAAe,EAAE,MAAc,EAAE,QAAiB;IACnG,oDAAoD;IACpD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACf,OAAO,KAAK,CAAC;IACd,CAAC;IAED,0CAA0C;IAC1C,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;QACpB,OAAO,KAAK,CAAC;IACd,CAAC;IAED,yCAAyC;IACzC,IAAI,CAAC,yBAAyB,CAAC,KAAK,CAAC,QAAQ,EAAE,MAAM,CAAC,EAAE,CAAC;QACxD,OAAO,KAAK,CAAC;IACd,CAAC;IAED,yBAAyB;IACzB,OAAO;QACN,GAAG,KAAK;QACR,OAAO,EAAE,GAAG,QAAQ,SAAS,kBAAkB,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE;KAChE,CAAC;AACH,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,WAAW,CAAC,KAAc;IACzC,IAAI,CAAC,CAAC,KAAK,YAAY,KAAK,CAAC,EAAE,CAAC;QAC/B,OAAO,KAAK,CAAC;IACd,CAAC;IAED,uCAAuC;IACvC,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;IAE5C,gEAAgE;IAChE,IAAI,KAAK,CAAC,IAAI,KAAK,WAAW,IAAI,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC;QACvE,OAAO,IAAI,CAAC;IACb,CAAC;IAED,sCAAsC;IACtC,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;QACnC,OAAO,IAAI,CAAC;IACb,CAAC;IAED,yBAAyB;IACzB,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;QAClE,OAAO,IAAI,CAAC;IACb,CAAC;IAED,OAAO,KAAK,CAAC;AACd,CAAC"}

View File

@@ -0,0 +1,347 @@
export declare const simpleHtml: {
systemPrompt: string;
model: {
id: string;
name: string;
api: string;
provider: string;
baseUrl: string;
reasoning: boolean;
input: string[];
cost: {
input: number;
output: number;
cacheRead: number;
cacheWrite: number;
};
contextWindow: number;
maxTokens: number;
};
messages: ({
role: string;
content: {
type: string;
text: string;
}[];
api?: undefined;
provider?: undefined;
model?: undefined;
usage?: undefined;
stopReason?: undefined;
toolCallId?: undefined;
toolName?: undefined;
output?: undefined;
isError?: undefined;
} | {
role: string;
content: ({
type: string;
text: string;
id?: undefined;
name?: undefined;
arguments?: undefined;
} | {
type: string;
id: string;
name: string;
arguments: {
command: string;
filename: string;
content: string;
};
text?: undefined;
})[];
api: string;
provider: string;
model: string;
usage: {
input: number;
output: number;
cacheRead: number;
cacheWrite: number;
cost: {
input: number;
output: number;
cacheRead: number;
cacheWrite: number;
total: number;
};
};
stopReason: string;
toolCallId?: undefined;
toolName?: undefined;
output?: undefined;
isError?: undefined;
} | {
role: string;
toolCallId: string;
toolName: string;
output: string;
isError: boolean;
content?: undefined;
api?: undefined;
provider?: undefined;
model?: undefined;
usage?: undefined;
stopReason?: undefined;
})[];
};
export declare const longSession: {
systemPrompt: string;
model: {
id: string;
name: string;
api: string;
provider: string;
baseUrl: string;
reasoning: boolean;
input: string[];
cost: {
input: number;
output: number;
cacheRead: number;
cacheWrite: number;
};
contextWindow: number;
maxTokens: number;
};
messages: ({
role: string;
content: {
type: string;
text: string;
}[];
api?: undefined;
provider?: undefined;
model?: undefined;
usage?: undefined;
stopReason?: undefined;
toolCallId?: undefined;
toolName?: undefined;
output?: undefined;
isError?: undefined;
details?: undefined;
errorMessage?: undefined;
} | {
role: string;
content: ({
type: string;
text: string;
id?: undefined;
name?: undefined;
arguments?: undefined;
} | {
type: string;
id: string;
name: string;
arguments: {
command: string;
filename: string;
content: string;
};
text?: undefined;
})[];
api: string;
provider: string;
model: string;
usage: {
input: number;
output: number;
cacheRead: number;
cacheWrite: number;
cost: {
input: number;
output: number;
cacheRead: number;
cacheWrite: number;
total: number;
};
};
stopReason: string;
toolCallId?: undefined;
toolName?: undefined;
output?: undefined;
isError?: undefined;
details?: undefined;
errorMessage?: undefined;
} | {
role: string;
toolCallId: string;
toolName: string;
output: string;
isError: boolean;
content?: undefined;
api?: undefined;
provider?: undefined;
model?: undefined;
usage?: undefined;
stopReason?: undefined;
details?: undefined;
errorMessage?: undefined;
} | {
role: string;
content: ({
type: string;
text: string;
id?: undefined;
name?: undefined;
arguments?: undefined;
} | {
type: string;
id: string;
name: string;
arguments: {
code: string;
};
text?: undefined;
})[];
api: string;
provider: string;
model: string;
usage: {
input: number;
output: number;
cacheRead: number;
cacheWrite: number;
cost: {
input: number;
output: number;
cacheRead: number;
cacheWrite: number;
total: number;
};
};
stopReason: string;
toolCallId?: undefined;
toolName?: undefined;
output?: undefined;
isError?: undefined;
details?: undefined;
errorMessage?: undefined;
} | {
role: string;
toolCallId: string;
toolName: string;
output: string;
details: {
files: never[];
};
isError: boolean;
content?: undefined;
api?: undefined;
provider?: undefined;
model?: undefined;
usage?: undefined;
stopReason?: undefined;
errorMessage?: undefined;
} | {
role: string;
content: {
type: string;
text: string;
}[];
api: string;
provider: string;
model: string;
usage: {
input: number;
output: number;
cacheRead: number;
cacheWrite: number;
cost: {
input: number;
output: number;
cacheRead: number;
cacheWrite: number;
total: number;
};
};
stopReason: string;
errorMessage: string;
toolCallId?: undefined;
toolName?: undefined;
output?: undefined;
isError?: undefined;
details?: undefined;
} | {
role: string;
content: ({
type: string;
text: string;
id?: undefined;
name?: undefined;
arguments?: undefined;
} | {
type: string;
id: string;
name: string;
arguments: {
command: string;
filename: string;
title: string;
content: string;
};
text?: undefined;
})[];
api: string;
provider: string;
model: string;
usage: {
input: number;
output: number;
cacheRead: number;
cacheWrite: number;
cost: {
input: number;
output: number;
cacheRead: number;
cacheWrite: number;
total: number;
};
};
stopReason: string;
toolCallId?: undefined;
toolName?: undefined;
output?: undefined;
isError?: undefined;
details?: undefined;
errorMessage?: undefined;
} | {
role: string;
content: {
type: string;
id: string;
name: string;
arguments: {
command: string;
filename: string;
old_str: string;
new_str: string;
};
}[];
api: string;
provider: string;
model: string;
usage: {
input: number;
output: number;
cacheRead: number;
cacheWrite: number;
cost: {
input: number;
output: number;
cacheRead: number;
cacheWrite: number;
total: number;
};
};
stopReason: string;
toolCallId?: undefined;
toolName?: undefined;
output?: undefined;
isError?: undefined;
details?: undefined;
errorMessage?: undefined;
})[];
};
//# sourceMappingURL=test-sessions.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"test-sessions.d.ts","sourceRoot":"","sources":["../../src/utils/test-sessions.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqGtB,CAAC;AAEF,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8lEvB,CAAC"}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long