/*
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
if you want to view the source, please visit the github repository of this plugin
*/

var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __esm = (fn, res) => function __init() {
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
};
var __commonJS = (cb, mod) => function __require() {
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);

// src/api.ts
async function deleteContentByType(khojUrl, khojApiKey, contentType) {
  const response = await fetch(`${khojUrl}/api/content/type/${contentType}?client=obsidian`, {
    method: "DELETE",
    headers: khojApiKey ? { "Authorization": `Bearer ${khojApiKey}` } : {}
  });
  if (!response.ok) {
    const text = await response.text().catch(() => "");
    throw new Error(`Failed to delete content type ${contentType}: ${response.status} ${text}`);
  }
}
async function uploadContentBatch(khojUrl, khojApiKey, files) {
  const formData = new FormData();
  files.forEach((fileItem) => {
    formData.append("files", fileItem.blob, fileItem.path);
  });
  const response = await fetch(`${khojUrl}/api/content?client=obsidian`, {
    method: "PATCH",
    headers: khojApiKey ? { "Authorization": `Bearer ${khojApiKey}` } : {},
    body: formData
  });
  if (!response.ok) {
    const text = await response.text().catch(() => "");
    throw new Error(`Failed to upload batch: ${response.status} ${text}`);
  }
  return await response.text();
}
var init_api = __esm({
  "src/api.ts"() {
  }
});

// src/search_modal.ts
var import_obsidian, KhojSearchModal;
var init_search_modal = __esm({
  "src/search_modal.ts"() {
    import_obsidian = require("obsidian");
    init_utils();
    KhojSearchModal = class extends import_obsidian.SuggestModal {
      constructor(app, setting, find_similar_notes = false) {
        var _a;
        super(app);
        this.rerank = false;
        this.query = "";
        this.currentController = null;
        this.isLoading = false;
        this.isFileFilterMode = false;
        this.fileSelected = "";
        this.allFiles = [];
        this.app = app;
        this.setting = setting;
        this.find_similar_notes = find_similar_notes;
        this.inputEl.hidden = this.find_similar_notes;
        this.loadingEl = createDiv({ cls: "search-loading" });
        const spinnerEl = this.loadingEl.createDiv({ cls: "search-loading-spinner" });
        this.loadingEl.style.position = "absolute";
        this.loadingEl.style.top = "50%";
        this.loadingEl.style.left = "50%";
        this.loadingEl.style.transform = "translate(-50%, -50%)";
        this.loadingEl.style.zIndex = "1000";
        this.loadingEl.style.display = "none";
        this.modalEl.appendChild(this.loadingEl);
        this.emptyStateText = "";
        this.scope.register(["Mod"], "Enter", async () => {
          this.rerank = true;
          this.inputEl.dispatchEvent(new Event("input"));
          this.rerank = false;
        });
        this.scope.register(["Shift"], "Enter", async () => {
          if (this.query != "")
            createNoteAndCloseModal(this.query, this);
        });
        this.scope.register(["Ctrl", "Shift"], "Enter", async () => {
          if (this.query != "")
            createNoteAndCloseModal(this.query, this, { newLeaf: true });
        });
        const modalInstructions = [
          {
            command: "\u2191\u2193",
            purpose: "to navigate"
          },
          {
            command: "\u21B5",
            purpose: "to open"
          },
          {
            command: import_obsidian.Platform.isMacOS ? "cmd \u21B5" : "ctrl \u21B5",
            purpose: "to rerank"
          },
          {
            command: "esc",
            purpose: "to dismiss"
          }
        ];
        this.setInstructions(modalInstructions);
        this.setPlaceholder("Search with Khoj...");
        this.allFiles = this.app.vault.getFiles().map((file) => ({
          path: file.path,
          inVault: true
        }));
        this.inputEl.addEventListener("input", () => {
          const fileFilterMatch = this.inputEl.value.match(/file:([^"\s]*)$/);
          if (fileFilterMatch) {
            this.isFileFilterMode = true;
          } else {
            this.isFileFilterMode = false;
            this.fileSelected = "";
          }
        });
        const originalSelectSuggestion = this.selectSuggestion.bind(this);
        this.selectSuggestion = async (value, evt) => {
          if (this.isFileFilterMode) {
            await this.onChooseSuggestion(value, evt);
          } else {
            originalSelectSuggestion(value, evt);
          }
        };
        this.resultsTitle = createDiv();
        this.resultsTitle.style.padding = "8px";
        this.resultsTitle.style.fontWeight = "bold";
        (_a = this.resultContainerEl.parentElement) == null ? void 0 : _a.insertBefore(this.resultsTitle, this.resultContainerEl);
      }
      isFileInVault(filePath) {
        const normalizedPath = filePath.replace(/\\/g, "/");
        return this.app.vault.getFiles().some((file) => file.path === normalizedPath);
      }
      async getSuggestions(query) {
        const fileFilterMatch = query.match(/file:([^"\s]*)$/);
        if (this.isFileFilterMode && fileFilterMatch) {
          const partialPath = fileFilterMatch[1] || "";
          this.resultsTitle.setText("Select a file:");
          return this.allFiles.filter((file) => file.path.toLowerCase().includes(partialPath.toLowerCase().trim())).map((file) => ({
            entry: file.path,
            file: file.path,
            inVault: file.inVault
          }));
        }
        if (query.trim()) {
          this.resultsTitle.setText("Search results:");
        } else {
          this.resultsTitle.setText("");
        }
        if (!query.trim()) {
          this.isLoading = false;
          this.updateLoadingState();
          return [];
        }
        this.isLoading = true;
        this.updateLoadingState();
        if (this.currentController) {
          this.currentController.abort();
        }
        try {
          this.currentController = new AbortController();
          let encodedQuery = encodeURIComponent(query);
          let searchUrl = `${this.setting.khojUrl}/api/search?q=${encodedQuery}&n=${this.setting.resultsCount}&r=${this.rerank}&client=obsidian`;
          let headers = {
            "Authorization": `Bearer ${this.setting.khojApiKey}`
          };
          const response = await fetch(searchUrl, {
            headers,
            signal: this.currentController.signal
          });
          if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
          }
          const data = await response.json();
          let results = data.filter((result) => {
            var _a;
            return !this.find_similar_notes || !result.additional.file.endsWith((_a = this.app.workspace.getActiveFile()) == null ? void 0 : _a.path);
          }).map((result) => {
            const isInVault = this.isFileInVault(result.additional.file);
            if (!this.allFiles.some((file) => file.path === result.additional.file)) {
              this.allFiles.push({
                path: result.additional.file,
                inVault: isInVault
              });
            }
            return {
              entry: result.entry,
              file: result.additional.file,
              inVault: isInVault
            };
          }).sort((a, b) => Number(b.inVault) - Number(a.inVault));
          this.query = query;
          this.isLoading = false;
          this.updateLoadingState();
          return results;
        } catch (error) {
          if (error.name === "AbortError") {
            return void 0;
          }
          console.error("Search error:", error);
          this.isLoading = false;
          this.updateLoadingState();
          return [];
        }
      }
      updateLoadingState() {
        this.loadingEl.style.display = this.isLoading ? "block" : "none";
      }
      async onOpen() {
        if (this.find_similar_notes) {
          let file = this.app.workspace.getActiveFile();
          if (file && file.extension === "md") {
            this.rerank = true;
            this.inputEl.value = await this.app.vault.read(file).then((file_str) => file_str.slice(0, 42110));
            this.inputEl.dispatchEvent(new Event("input"));
            this.rerank = false;
          } else {
            this.resultContainerEl.setText("Cannot find similar notes for non-markdown files");
          }
        }
      }
      async renderSuggestion(result, el) {
        var _a;
        if (this.isFileFilterMode) {
          el.createEl("div", {
            text: result.entry,
            cls: "khoj-file-suggestion"
          });
          return;
        }
        let lines_to_render = 8;
        let os_path_separator = result.file.includes("\\") ? "\\" : "/";
        let filename = result.file.split(os_path_separator).pop();
        const fileEl = el.createEl("div", {
          cls: `khoj-result-file ${result.inVault ? "in-vault" : "not-in-vault"}`
        });
        fileEl.setText(filename != null ? filename : "");
        if (!result.inVault) {
          fileEl.createSpan({
            text: " (not in vault)",
            cls: "khoj-result-file-status"
          });
        }
        let result_el = el.createEl("div", { cls: "khoj-result-entry" });
        let resultToRender = "";
        let fileExtension = (_a = filename == null ? void 0 : filename.split(".").pop()) != null ? _a : "";
        if (supportedImageFilesTypes.includes(fileExtension) && filename && result.inVault) {
          let linkToEntry = filename;
          let imageFiles = this.app.vault.getFiles().filter((file) => supportedImageFilesTypes.includes(fileExtension));
          let fileInVault = getFileFromPath(imageFiles, result.file);
          if (fileInVault)
            linkToEntry = this.app.vault.getResourcePath(fileInVault);
          resultToRender = `![](${linkToEntry})`;
        } else {
          result.entry = result.entry.replace(/---[\n\r][\s\S]*---[\n\r]/, "");
          let entry_snipped_indicator = result.entry.split("\n").length > lines_to_render ? " **...**" : "";
          let snipped_entry = result.entry.split("\n").slice(0, lines_to_render).join("\n");
          resultToRender = `${snipped_entry}${entry_snipped_indicator}`;
        }
        import_obsidian.MarkdownRenderer.renderMarkdown(resultToRender, result_el, result.file, null);
      }
      async onChooseSuggestion(result, _) {
        if (this.isFileFilterMode) {
          const currentValue = this.inputEl.value;
          const beforeFile = currentValue.substring(0, currentValue.lastIndexOf("file:"));
          this.inputEl.value = `${beforeFile}file:"${result.entry}"`;
          this.fileSelected = result.entry;
          this.isFileFilterMode = false;
          this.inputEl.dispatchEvent(new Event("input"));
          return;
        }
        if (!result.inVault) {
          new import_obsidian.Notice("This file is not in your vault");
          return;
        }
        const mdFiles = this.app.vault.getMarkdownFiles();
        const binaryFiles = this.app.vault.getFiles().filter((file) => supportedBinaryFileTypes.includes(file.extension));
        let linkToEntry = getLinkToEntry(mdFiles.concat(binaryFiles), result.file, result.entry);
        if (linkToEntry)
          this.app.workspace.openLinkText(linkToEntry, "");
      }
    };
  }
});

// src/utils.ts
var utils_exports = {};
__export(utils_exports, {
  KhojView: () => KhojView,
  calculateVaultSyncMetrics: () => calculateVaultSyncMetrics,
  canConnectToBackend: () => canConnectToBackend,
  createCopyParentText: () => createCopyParentText,
  createNote: () => createNote,
  createNoteAndCloseModal: () => createNoteAndCloseModal,
  fetchChatModels: () => fetchChatModels,
  fetchUserServerSettings: () => fetchUserServerSettings,
  fileTypeToExtension: () => fileTypeToExtension,
  getBackendStatusMessage: () => getBackendStatusMessage,
  getFileFromPath: () => getFileFromPath,
  getFilesToSync: () => getFilesToSync,
  getLinkToEntry: () => getLinkToEntry,
  getVaultAbsolutePath: () => getVaultAbsolutePath,
  jumpToPreviousView: () => jumpToPreviousView,
  openKhojPluginSettings: () => openKhojPluginSettings,
  pasteTextAtCursor: () => pasteTextAtCursor,
  populateHeaderPane: () => populateHeaderPane,
  supportedBinaryFileTypes: () => supportedBinaryFileTypes,
  supportedFileTypes: () => supportedFileTypes,
  supportedImageFilesTypes: () => supportedImageFilesTypes,
  updateContentIndex: () => updateContentIndex,
  updateServerChatModel: () => updateServerChatModel
});
function getVaultAbsolutePath(vault) {
  let adaptor = vault.adapter;
  if (adaptor instanceof import_obsidian2.FileSystemAdapter) {
    return adaptor.getBasePath();
  }
  return "";
}
function fileExtensionToMimeType(extension) {
  switch (extension) {
    case "pdf":
      return "application/pdf";
    case "png":
      return "image/png";
    case "jpg":
    case "jpeg":
      return "image/jpeg";
    case "md":
    case "markdown":
      return "text/markdown";
    case "org":
      return "text/org";
    default:
      return "text/plain";
  }
}
function filenameToMimeType(filename) {
  switch (filename.extension) {
    case "pdf":
      return "application/pdf";
    case "png":
      return "image/png";
    case "jpg":
    case "jpeg":
      return "image/jpeg";
    case "webp":
      return "image/webp";
    case "md":
    case "markdown":
      return "text/markdown";
    case "org":
      return "text/org";
    default:
      console.warn(`Unknown file type: ${filename.extension}. Defaulting to text/plain.`);
      return "text/plain";
  }
}
function getFilesToSync(vault, setting) {
  const files = vault.getFiles().filter((file) => supportedFileTypes.includes(file.extension)).filter((file) => {
    if (fileTypeToExtension.markdown.includes(file.extension))
      return setting.syncFileType.markdown;
    if (fileTypeToExtension.pdf.includes(file.extension))
      return setting.syncFileType.pdf;
    if (fileTypeToExtension.image.includes(file.extension))
      return setting.syncFileType.images;
    return false;
  }).filter((file) => {
    if (setting.syncFolders.length === 0)
      return true;
    return setting.syncFolders.some((folder) => file.path.startsWith(folder + "/") || file.path === folder);
  }).filter((file) => {
    if (setting.excludeFolders.length === 0)
      return true;
    return !setting.excludeFolders.some((folder) => file.path.startsWith(folder + "/") || file.path === folder);
  }).sort((a, b) => {
    const typeOrder = ["markdown", "pdf", "image"];
    const aType = typeOrder.findIndex((type) => fileTypeToExtension[type].includes(a.extension));
    const bType = typeOrder.findIndex((type) => fileTypeToExtension[type].includes(b.extension));
    return aType - bType;
  });
  return files;
}
async function updateContentIndex(vault, setting, lastSync, regenerate = false, userTriggered = false, onProgress) {
  var _a, _b, _c;
  console.log(`Khoj: Updating Khoj content index...`);
  const files = getFilesToSync(vault, setting);
  console.log(`Khoj: Found ${files.length} eligible files in vault`);
  let countOfFilesToIndex = 0;
  let countOfFilesToDelete = 0;
  lastSync = lastSync.size > 0 ? lastSync : /* @__PURE__ */ new Map();
  const filesToSync = regenerate ? files : files.filter((file) => {
    var _a2;
    return file.stat.mtime >= ((_a2 = lastSync.get(file)) != null ? _a2 : 0);
  });
  if (userTriggered) {
    new import_obsidian2.Notice(`\u{1F504} Syncing ${filesToSync.length} of ${files.length} files to Khoj...`);
  }
  console.log(`Khoj: ${filesToSync.length} files to sync (${files.length} total eligible)`);
  const MAX_BATCH_SIZE = 10 * 1024 * 1024;
  const MAX_BATCH_ITEMS = 50;
  let fileData = [];
  let currentBatch = [];
  let currentBatchSize = 0;
  for (const file of files) {
    if (!regenerate && file.stat.mtime < ((_a = lastSync.get(file)) != null ? _a : 0)) {
      continue;
    }
    countOfFilesToIndex++;
    const encoding = supportedBinaryFileTypes.includes(file.extension) ? "binary" : "utf8";
    const mimeType = fileExtensionToMimeType(file.extension) + (encoding === "utf8" ? "; charset=UTF-8" : "");
    const fileContent = encoding == "binary" ? await vault.readBinary(file) : await vault.read(file);
    const fileItem = { blob: new Blob([fileContent], { type: mimeType }), path: file.path };
    const fileSize = typeof fileContent === "string" ? new Blob([fileContent]).size : fileContent.byteLength;
    if ((currentBatchSize + fileSize > MAX_BATCH_SIZE || currentBatch.length >= MAX_BATCH_ITEMS) && currentBatch.length > 0) {
      fileData.push(currentBatch);
      currentBatch = [];
      currentBatchSize = 0;
    }
    currentBatch.push(fileItem);
    currentBatchSize += fileSize;
  }
  let filesToDelete = [];
  for (const lastSyncedFile of lastSync.keys()) {
    if (!files.includes(lastSyncedFile)) {
      countOfFilesToDelete++;
      const fileObj = new Blob([""], { type: filenameToMimeType(lastSyncedFile) });
      currentBatch.push({ blob: fileObj, path: lastSyncedFile.path });
      filesToDelete.push(lastSyncedFile);
    }
  }
  if (currentBatch.length > 0) {
    fileData.push(currentBatch);
  }
  let error_message = null;
  if (regenerate) {
    const contentTypesToDelete = [];
    if (setting.syncFileType.markdown)
      contentTypesToDelete.push("markdown");
    if (setting.syncFileType.pdf)
      contentTypesToDelete.push("pdf");
    if (setting.syncFileType.images)
      contentTypesToDelete.push("image");
    try {
      for (const contentType of contentTypesToDelete) {
        await deleteContentByType(setting.khojUrl, setting.khojApiKey, contentType);
      }
    } catch (err) {
      console.error("Khoj: Error deleting content types:", err);
      error_message = "\u2757\uFE0FFailed to clear existing content index";
      fileData = [];
    }
  }
  let responses = [];
  let processedFiles = 0;
  const totalFiles = fileData.reduce((sum, batch) => sum + batch.length, 0);
  if (onProgress) {
    onProgress({ processed: 0, total: totalFiles });
  }
  for (const batch of fileData) {
    try {
      const resultText = await uploadContentBatch(setting.khojUrl, setting.khojApiKey, batch);
      responses.push(resultText);
      processedFiles += batch.length;
      if (onProgress) {
        onProgress({ processed: processedFiles, total: totalFiles });
      }
    } catch (err) {
      console.error("Khoj: Failed to upload batch:", err);
      if ((_b = err.message) == null ? void 0 : _b.includes("429")) {
        error_message = `\u2757\uFE0FRequests were throttled. Upgrade your subscription or try again later.`;
      } else {
        error_message = `\u2757\uFE0FFailed to sync content with Khoj server. Error: ${(_c = err.message) != null ? _c : String(err)}`;
      }
      break;
    }
  }
  files.filter((file) => responses.find((response) => response.includes(file.path))).reduce((newSync, file) => {
    newSync.set(file, new Date().getTime());
    return newSync;
  }, lastSync);
  filesToDelete.filter((file) => responses.find((response) => response.includes(file.path))).forEach((file) => lastSync.delete(file));
  if (error_message) {
    new import_obsidian2.Notice(error_message);
  } else {
    const summary = `Updated ${countOfFilesToIndex}, deleted ${countOfFilesToDelete} files`;
    if (userTriggered)
      new import_obsidian2.Notice(`\u2705 ${summary}`);
    console.log(`\u2705 Refreshed Khoj content index. ${summary}.`);
  }
  return lastSync;
}
async function openKhojPluginSettings() {
  const setting = this.app.setting;
  await setting.open();
  setting.openTabById("khoj");
}
async function createNote(name, newLeaf = false) {
  var _a, _b;
  try {
    let pathPrefix;
    switch (this.app.vault.getConfig("newFileLocation")) {
      case "current":
        pathPrefix = ((_b = (_a = this.app.workspace.getActiveFile()) == null ? void 0 : _a.parent.path) != null ? _b : "") + "/";
        break;
      case "folder":
        pathPrefix = this.app.vault.getConfig("newFileFolderPath") + "/";
        break;
      default:
        pathPrefix = "";
        break;
    }
    await this.app.workspace.openLinkText(`${pathPrefix}${name}.md`, "", newLeaf);
  } catch (e) {
    console.error("Khoj: Could not create note.\n" + e.message);
    throw e;
  }
}
async function createNoteAndCloseModal(query, modal, opt) {
  try {
    await createNote(query, opt == null ? void 0 : opt.newLeaf);
  } catch (e) {
    new import_obsidian2.Notice(e.message);
    return;
  }
  modal.close();
}
async function canConnectToBackend(khojUrl, khojApiKey, showNotice = false) {
  let connectedToBackend = false;
  let userInfo = null;
  if (!!khojUrl) {
    let headers = !!khojApiKey ? { "Authorization": `Bearer ${khojApiKey}` } : void 0;
    try {
      let response = await (0, import_obsidian2.request)({ url: `${khojUrl}/api/v1/user`, method: "GET", headers });
      connectedToBackend = true;
      userInfo = JSON.parse(response);
    } catch (error) {
      connectedToBackend = false;
      console.log(`Khoj connection error:

${error}`);
    }
    ;
  }
  let statusMessage = getBackendStatusMessage(connectedToBackend, userInfo == null ? void 0 : userInfo.email, khojUrl, khojApiKey);
  if (showNotice)
    new import_obsidian2.Notice(statusMessage);
  return { connectedToBackend, statusMessage, userInfo };
}
function getBackendStatusMessage(connectedToServer, userEmail, khojUrl, khojApiKey) {
  if (!khojApiKey && khojUrl === "https://app.khoj.dev")
    return `\u{1F308} Welcome to Khoj! Get your API key from ${khojUrl}/settings#clients and set it in the Khoj plugin settings on Obsidian`;
  if (!connectedToServer)
    return `\u2757\uFE0FCould not connect to Khoj at ${khojUrl}. Ensure your can access it`;
  else if (!userEmail)
    return `\u2705 Connected to Khoj. \u2757\uFE0FGet a valid API key from ${khojUrl}/settings#clients to log in`;
  else if (userEmail === "default@example.com")
    return `\u2705 Welcome back to Khoj`;
  else
    return `\u2705 Welcome back to Khoj, ${userEmail}`;
}
async function populateHeaderPane(headerEl, setting, viewType) {
  let userInfo = null;
  try {
    const { userInfo: extractedUserInfo } = await canConnectToBackend(setting.khojUrl, setting.khojApiKey, false);
    userInfo = extractedUserInfo;
  } catch (error) {
    console.error("\u2757\uFE0FCould not connect to Khoj");
  }
  const titlePaneEl = headerEl.createDiv();
  titlePaneEl.className = "khoj-header-title-pane";
  const titleEl = titlePaneEl.createDiv();
  titleEl.className = "khoj-logo";
  titleEl.textContent = "Khoj";
  const nav = titlePaneEl.createEl("nav");
  nav.className = "khoj-nav";
  titlePaneEl.appendChild(titleEl);
  titlePaneEl.appendChild(nav);
  const chatLink = nav.createEl("a");
  chatLink.id = "chat-nav";
  chatLink.className = "khoj-nav chat-nav";
  chatLink.dataset.view = KhojView.CHAT;
  const chatIcon = chatLink.createEl("span");
  chatIcon.className = "khoj-nav-icon khoj-nav-icon-chat";
  (0, import_obsidian2.setIcon)(chatIcon, "khoj-chat");
  const chatText = chatLink.createEl("span");
  chatText.className = "khoj-nav-item-text";
  chatText.textContent = "Chat";
  chatLink.appendChild(chatIcon);
  chatLink.appendChild(chatText);
  const searchLink = nav.createEl("a");
  searchLink.id = "search-nav";
  searchLink.className = "khoj-nav search-nav";
  const searchIcon = searchLink.createEl("span");
  searchIcon.className = "khoj-nav-icon khoj-nav-icon-search";
  (0, import_obsidian2.setIcon)(searchIcon, "khoj-search");
  const searchText = searchLink.createEl("span");
  searchText.className = "khoj-nav-item-text";
  searchText.textContent = "Search";
  searchLink.appendChild(searchIcon);
  searchLink.appendChild(searchText);
  const similarLink = nav.createEl("a");
  similarLink.id = "similar-nav";
  similarLink.className = "khoj-nav similar-nav";
  similarLink.dataset.view = KhojView.SIMILAR;
  const similarIcon = similarLink.createEl("span");
  similarIcon.id = "similar-nav-icon";
  similarIcon.className = "khoj-nav-icon khoj-nav-icon-similar";
  (0, import_obsidian2.setIcon)(similarIcon, "webhook");
  const similarText = similarLink.createEl("span");
  similarText.className = "khoj-nav-item-text";
  similarText.textContent = "Similar";
  similarLink.appendChild(similarIcon);
  similarLink.appendChild(similarText);
  const getCurrentKhojLeaf = () => {
    const activeLeaf = this.app.workspace.activeLeaf;
    if (activeLeaf && activeLeaf.view && (activeLeaf.view.getViewType() === KhojView.CHAT || activeLeaf.view.getViewType() === KhojView.SIMILAR)) {
      return activeLeaf;
    }
    return void 0;
  };
  chatLink.addEventListener("click", () => {
    const khojPlugin = this.app.plugins.plugins.khoj;
    khojPlugin == null ? void 0 : khojPlugin.activateView(KhojView.CHAT, getCurrentKhojLeaf());
  });
  searchLink.addEventListener("click", () => {
    new KhojSearchModal(this.app, setting).open();
  });
  similarLink.addEventListener("click", () => {
    const khojPlugin = this.app.plugins.plugins.khoj;
    khojPlugin == null ? void 0 : khojPlugin.activateView(KhojView.SIMILAR, getCurrentKhojLeaf());
  });
  nav.appendChild(chatLink);
  nav.appendChild(searchLink);
  nav.appendChild(similarLink);
  headerEl.appendChild(titlePaneEl);
  if (viewType === KhojView.CHAT) {
    const newChatEl = headerEl.createDiv("khoj-header-right-container");
    const agentContainer = newChatEl.createDiv("khoj-header-agent-container");
    agentContainer.createEl("select", {
      attr: {
        class: "khoj-header-agent-select",
        id: "khoj-header-agent-select"
      }
    });
    const newChatButton = newChatEl.createEl("button");
    newChatButton.className = "khoj-header-new-chat-button";
    newChatButton.title = "Start New Chat (Ctrl+Alt+N)";
    (0, import_obsidian2.setIcon)(newChatButton, "plus-circle");
    newChatButton.textContent = "New Chat";
    newChatButton.addEventListener("click", () => {
      const khojPlugin = this.app.plugins.plugins.khoj;
      if (khojPlugin) {
        khojPlugin.activateView(KhojView.CHAT).then(() => {
          setTimeout(() => {
            const leaves = this.app.workspace.getLeavesOfType(KhojView.CHAT);
            if (leaves.length > 0) {
              const chatView = leaves[0].view;
              if (chatView && typeof chatView.createNewConversation === "function") {
                chatView.createNewConversation();
              }
            }
          }, 100);
        });
      }
    });
    headerEl.appendChild(newChatEl);
  }
  const updateActiveState = () => {
    var _a;
    const activeLeaf = this.app.workspace.activeLeaf;
    if (!activeLeaf)
      return;
    const viewType2 = (_a = activeLeaf.view) == null ? void 0 : _a.getViewType();
    chatLink.classList.remove("khoj-nav-selected");
    similarLink.classList.remove("khoj-nav-selected");
    if (viewType2 === KhojView.CHAT) {
      chatLink.classList.add("khoj-nav-selected");
    } else if (viewType2 === KhojView.SIMILAR) {
      similarLink.classList.add("khoj-nav-selected");
    }
  };
  updateActiveState();
  this.app.workspace.on("active-leaf-change", updateActiveState);
}
function copyParentText(event, message, originalButton) {
  var _a;
  const button = event.currentTarget;
  if (!button || !((_a = button == null ? void 0 : button.parentNode) == null ? void 0 : _a.textContent))
    return;
  if (!!button.firstChild)
    button.removeChild(button.firstChild);
  const textContent = message != null ? message : button.parentNode.textContent.trim();
  navigator.clipboard.writeText(textContent).then(() => {
    (0, import_obsidian2.setIcon)(button, "copy-check");
    setTimeout(() => {
      (0, import_obsidian2.setIcon)(button, originalButton);
    }, 1e3);
  }).catch((error) => {
    console.error("Error copying text to clipboard:", error);
    const originalButtonText = button.innerHTML;
    (0, import_obsidian2.setIcon)(button, "x-circle");
    setTimeout(() => {
      button.innerHTML = originalButtonText;
      (0, import_obsidian2.setIcon)(button, originalButton);
    }, 2e3);
  });
  return textContent;
}
function createCopyParentText(message, originalButton = "copy-plus") {
  return function(event) {
    let markdownMessage = copyParentText(event, message, originalButton);
    const editRegex = /<details class="khoj-edit-accordion">[\s\S]*?<pre><code class="language-khoj-edit">([\s\S]*?)<\/code><\/pre>[\s\S]*?<\/details>/g;
    markdownMessage = markdownMessage == null ? void 0 : markdownMessage.replace(editRegex, (_, content) => {
      return `<khoj-edit>
${content}
</khoj-edit>`;
    });
    return markdownMessage;
  };
}
function jumpToPreviousView() {
  var _a;
  const editor = (_a = this.app.workspace.getActiveFileView()) == null ? void 0 : _a.editor;
  if (!editor)
    return;
  editor.focus();
}
function pasteTextAtCursor(text) {
  var _a;
  const editor = (_a = this.app.workspace.getActiveFileView()) == null ? void 0 : _a.editor;
  if (!editor || !text)
    return;
  const cursor = editor.getCursor();
  if (editor == null ? void 0 : editor.getSelection()) {
    editor.replaceSelection(text);
  } else if (cursor) {
    editor.replaceRange(text, cursor);
  }
}
function getFileFromPath(sourceFiles, chosenFile) {
  let fileMatch = sourceFiles.sort((a, b) => b.path.length - a.path.length).find((file) => chosenFile.replace(/\\/g, "/").endsWith(file.path));
  return fileMatch;
}
function getLinkToEntry(sourceFiles, chosenFile, chosenEntry) {
  let fileMatch = getFileFromPath(sourceFiles, chosenFile);
  if (fileMatch) {
    let resultHeading = fileMatch.extension !== "pdf" ? chosenEntry.split("\n", 1)[0] : "";
    let linkToEntry = resultHeading.startsWith("#") ? `${fileMatch.path}${resultHeading}` : fileMatch.path;
    console.log(`Link: ${linkToEntry}, File: ${fileMatch.path}, Heading: ${resultHeading}`);
    return linkToEntry;
  }
}
async function calculateVaultSyncMetrics(vault, setting) {
  try {
    const files = getFilesToSync(vault, setting);
    const usedBytes = files.reduce((acc, file) => {
      var _a, _b;
      return acc + ((_b = (_a = file.stat) == null ? void 0 : _a.size) != null ? _b : 0);
    }, 0);
    const FREE_LIMIT = 10 * 1024 * 1024;
    const PAID_LIMIT = 500 * 1024 * 1024;
    let totalBytes = FREE_LIMIT;
    try {
      if (setting.userInfo && setting.userInfo.is_active === true) {
        totalBytes = PAID_LIMIT;
      } else {
        totalBytes = FREE_LIMIT;
      }
    } catch (err) {
      console.warn("Khoj: Error reading userInfo.is_active, defaulting to free limit", err);
      totalBytes = FREE_LIMIT;
    }
    return { usedBytes, totalBytes };
  } catch (err) {
    console.error("Khoj: Error calculating vault sync metrics:", err);
    return { usedBytes: 0, totalBytes: 10 * 1024 * 1024 };
  }
}
async function fetchChatModels(settings) {
  if (!settings.connectedToBackend || !settings.khojUrl) {
    return [];
  }
  try {
    const response = await fetch(`${settings.khojUrl}/api/model/chat/options`, {
      method: "GET",
      headers: settings.khojApiKey ? { "Authorization": `Bearer ${settings.khojApiKey}` } : {}
    });
    if (response.ok) {
      const modelsData = await response.json();
      if (Array.isArray(modelsData)) {
        return modelsData.map((model) => ({
          id: model.id.toString(),
          name: model.name
        }));
      }
    } else {
      console.warn("Khoj: Failed to fetch chat models:", response.statusText);
    }
  } catch (error) {
    console.error("Khoj: Error fetching chat models:", error);
  }
  return [];
}
async function fetchUserServerSettings(settings) {
  if (!settings.connectedToBackend || !settings.khojUrl) {
    return null;
  }
  try {
    const response = await fetch(`${settings.khojUrl}/api/settings?detailed=true`, {
      method: "GET",
      headers: settings.khojApiKey ? { "Authorization": `Bearer ${settings.khojApiKey}` } : {}
    });
    if (response.ok) {
      return await response.json();
    } else {
      console.warn("Khoj: Failed to fetch user server settings:", response.statusText);
    }
  } catch (error) {
    console.error("Khoj: Error fetching user server settings:", error);
  }
  return null;
}
async function updateServerChatModel(modelId, settings) {
  if (!settings.connectedToBackend || !settings.khojUrl) {
    new import_obsidian2.Notice("\uFE0F\u26D4\uFE0F Connect to Khoj to update chat model.");
    return false;
  }
  try {
    const response = await fetch(`${settings.khojUrl}/api/model/chat?id=${modelId}`, {
      method: "POST",
      headers: settings.khojApiKey ? { "Authorization": `Bearer ${settings.khojApiKey}` } : {}
    });
    if (response.ok) {
      settings.selectedChatModelId = modelId;
      return true;
    } else {
      const errorData = await response.text();
      new import_obsidian2.Notice(`\uFE0F\u26D4\uFE0F Failed to update chat model on server: ${response.status} ${errorData}`);
      console.error("Khoj: Failed to update chat model:", response.status, errorData);
      return false;
    }
  } catch (error) {
    new import_obsidian2.Notice("\uFE0F\u26D4\uFE0F Error updating chat model on server. See console.");
    console.error("Khoj: Error updating chat model:", error);
    return false;
  }
}
var import_obsidian2, fileTypeToExtension, supportedImageFilesTypes, supportedBinaryFileTypes, supportedFileTypes, KhojView;
var init_utils = __esm({
  "src/utils.ts"() {
    import_obsidian2 = require("obsidian");
    init_api();
    init_search_modal();
    fileTypeToExtension = {
      "pdf": ["pdf"],
      "image": ["png", "jpg", "jpeg", "webp"],
      "markdown": ["md", "markdown"]
    };
    supportedImageFilesTypes = fileTypeToExtension.image;
    supportedBinaryFileTypes = fileTypeToExtension.pdf.concat(supportedImageFilesTypes);
    supportedFileTypes = fileTypeToExtension.markdown.concat(supportedBinaryFileTypes);
    KhojView = /* @__PURE__ */ ((KhojView2) => {
      KhojView2["CHAT"] = "khoj-chat-view";
      KhojView2["SIMILAR"] = "khoj-similar-view";
      return KhojView2;
    })(KhojView || {});
  }
});

// node_modules/dompurify/dist/purify.cjs.js
var require_purify_cjs = __commonJS({
  "node_modules/dompurify/dist/purify.cjs.js"(exports, module2) {
    "use strict";
    var {
      entries,
      setPrototypeOf,
      isFrozen,
      getPrototypeOf,
      getOwnPropertyDescriptor
    } = Object;
    var {
      freeze,
      seal,
      create
    } = Object;
    var {
      apply,
      construct
    } = typeof Reflect !== "undefined" && Reflect;
    if (!freeze) {
      freeze = function freeze2(x) {
        return x;
      };
    }
    if (!seal) {
      seal = function seal2(x) {
        return x;
      };
    }
    if (!apply) {
      apply = function apply2(fun, thisValue, args) {
        return fun.apply(thisValue, args);
      };
    }
    if (!construct) {
      construct = function construct2(Func, args) {
        return new Func(...args);
      };
    }
    var arrayForEach = unapply(Array.prototype.forEach);
    var arrayLastIndexOf = unapply(Array.prototype.lastIndexOf);
    var arrayPop = unapply(Array.prototype.pop);
    var arrayPush = unapply(Array.prototype.push);
    var arraySplice = unapply(Array.prototype.splice);
    var stringToLowerCase = unapply(String.prototype.toLowerCase);
    var stringToString = unapply(String.prototype.toString);
    var stringMatch = unapply(String.prototype.match);
    var stringReplace = unapply(String.prototype.replace);
    var stringIndexOf = unapply(String.prototype.indexOf);
    var stringTrim = unapply(String.prototype.trim);
    var objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty);
    var regExpTest = unapply(RegExp.prototype.test);
    var typeErrorCreate = unconstruct(TypeError);
    function unapply(func) {
      return function(thisArg) {
        if (thisArg instanceof RegExp) {
          thisArg.lastIndex = 0;
        }
        for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
          args[_key - 1] = arguments[_key];
        }
        return apply(func, thisArg, args);
      };
    }
    function unconstruct(func) {
      return function() {
        for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
          args[_key2] = arguments[_key2];
        }
        return construct(func, args);
      };
    }
    function addToSet(set, array) {
      let transformCaseFunc = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : stringToLowerCase;
      if (setPrototypeOf) {
        setPrototypeOf(set, null);
      }
      let l = array.length;
      while (l--) {
        let element = array[l];
        if (typeof element === "string") {
          const lcElement = transformCaseFunc(element);
          if (lcElement !== element) {
            if (!isFrozen(array)) {
              array[l] = lcElement;
            }
            element = lcElement;
          }
        }
        set[element] = true;
      }
      return set;
    }
    function cleanArray(array) {
      for (let index = 0; index < array.length; index++) {
        const isPropertyExist = objectHasOwnProperty(array, index);
        if (!isPropertyExist) {
          array[index] = null;
        }
      }
      return array;
    }
    function clone(object) {
      const newObject = create(null);
      for (const [property, value] of entries(object)) {
        const isPropertyExist = objectHasOwnProperty(object, property);
        if (isPropertyExist) {
          if (Array.isArray(value)) {
            newObject[property] = cleanArray(value);
          } else if (value && typeof value === "object" && value.constructor === Object) {
            newObject[property] = clone(value);
          } else {
            newObject[property] = value;
          }
        }
      }
      return newObject;
    }
    function lookupGetter(object, prop) {
      while (object !== null) {
        const desc = getOwnPropertyDescriptor(object, prop);
        if (desc) {
          if (desc.get) {
            return unapply(desc.get);
          }
          if (typeof desc.value === "function") {
            return unapply(desc.value);
          }
        }
        object = getPrototypeOf(object);
      }
      function fallbackValue() {
        return null;
      }
      return fallbackValue;
    }
    var html$1 = freeze(["a", "abbr", "acronym", "address", "area", "article", "aside", "audio", "b", "bdi", "bdo", "big", "blink", "blockquote", "body", "br", "button", "canvas", "caption", "center", "cite", "code", "col", "colgroup", "content", "data", "datalist", "dd", "decorator", "del", "details", "dfn", "dialog", "dir", "div", "dl", "dt", "element", "em", "fieldset", "figcaption", "figure", "font", "footer", "form", "h1", "h2", "h3", "h4", "h5", "h6", "head", "header", "hgroup", "hr", "html", "i", "img", "input", "ins", "kbd", "label", "legend", "li", "main", "map", "mark", "marquee", "menu", "menuitem", "meter", "nav", "nobr", "ol", "optgroup", "option", "output", "p", "picture", "pre", "progress", "q", "rp", "rt", "ruby", "s", "samp", "section", "select", "shadow", "small", "source", "spacer", "span", "strike", "strong", "style", "sub", "summary", "sup", "table", "tbody", "td", "template", "textarea", "tfoot", "th", "thead", "time", "tr", "track", "tt", "u", "ul", "var", "video", "wbr"]);
    var svg$1 = freeze(["svg", "a", "altglyph", "altglyphdef", "altglyphitem", "animatecolor", "animatemotion", "animatetransform", "circle", "clippath", "defs", "desc", "ellipse", "filter", "font", "g", "glyph", "glyphref", "hkern", "image", "line", "lineargradient", "marker", "mask", "metadata", "mpath", "path", "pattern", "polygon", "polyline", "radialgradient", "rect", "stop", "style", "switch", "symbol", "text", "textpath", "title", "tref", "tspan", "view", "vkern"]);
    var svgFilters = freeze(["feBlend", "feColorMatrix", "feComponentTransfer", "feComposite", "feConvolveMatrix", "feDiffuseLighting", "feDisplacementMap", "feDistantLight", "feDropShadow", "feFlood", "feFuncA", "feFuncB", "feFuncG", "feFuncR", "feGaussianBlur", "feImage", "feMerge", "feMergeNode", "feMorphology", "feOffset", "fePointLight", "feSpecularLighting", "feSpotLight", "feTile", "feTurbulence"]);
    var svgDisallowed = freeze(["animate", "color-profile", "cursor", "discard", "font-face", "font-face-format", "font-face-name", "font-face-src", "font-face-uri", "foreignobject", "hatch", "hatchpath", "mesh", "meshgradient", "meshpatch", "meshrow", "missing-glyph", "script", "set", "solidcolor", "unknown", "use"]);
    var mathMl$1 = freeze(["math", "menclose", "merror", "mfenced", "mfrac", "mglyph", "mi", "mlabeledtr", "mmultiscripts", "mn", "mo", "mover", "mpadded", "mphantom", "mroot", "mrow", "ms", "mspace", "msqrt", "mstyle", "msub", "msup", "msubsup", "mtable", "mtd", "mtext", "mtr", "munder", "munderover", "mprescripts"]);
    var mathMlDisallowed = freeze(["maction", "maligngroup", "malignmark", "mlongdiv", "mscarries", "mscarry", "msgroup", "mstack", "msline", "msrow", "semantics", "annotation", "annotation-xml", "mprescripts", "none"]);
    var text = freeze(["#text"]);
    var html = freeze(["accept", "action", "align", "alt", "autocapitalize", "autocomplete", "autopictureinpicture", "autoplay", "background", "bgcolor", "border", "capture", "cellpadding", "cellspacing", "checked", "cite", "class", "clear", "color", "cols", "colspan", "controls", "controlslist", "coords", "crossorigin", "datetime", "decoding", "default", "dir", "disabled", "disablepictureinpicture", "disableremoteplayback", "download", "draggable", "enctype", "enterkeyhint", "face", "for", "headers", "height", "hidden", "high", "href", "hreflang", "id", "inputmode", "integrity", "ismap", "kind", "label", "lang", "list", "loading", "loop", "low", "max", "maxlength", "media", "method", "min", "minlength", "multiple", "muted", "name", "nonce", "noshade", "novalidate", "nowrap", "open", "optimum", "pattern", "placeholder", "playsinline", "popover", "popovertarget", "popovertargetaction", "poster", "preload", "pubdate", "radiogroup", "readonly", "rel", "required", "rev", "reversed", "role", "rows", "rowspan", "spellcheck", "scope", "selected", "shape", "size", "sizes", "span", "srclang", "start", "src", "srcset", "step", "style", "summary", "tabindex", "title", "translate", "type", "usemap", "valign", "value", "width", "wrap", "xmlns", "slot"]);
    var svg = freeze(["accent-height", "accumulate", "additive", "alignment-baseline", "amplitude", "ascent", "attributename", "attributetype", "azimuth", "basefrequency", "baseline-shift", "begin", "bias", "by", "class", "clip", "clippathunits", "clip-path", "clip-rule", "color", "color-interpolation", "color-interpolation-filters", "color-profile", "color-rendering", "cx", "cy", "d", "dx", "dy", "diffuseconstant", "direction", "display", "divisor", "dur", "edgemode", "elevation", "end", "exponent", "fill", "fill-opacity", "fill-rule", "filter", "filterunits", "flood-color", "flood-opacity", "font-family", "font-size", "font-size-adjust", "font-stretch", "font-style", "font-variant", "font-weight", "fx", "fy", "g1", "g2", "glyph-name", "glyphref", "gradientunits", "gradienttransform", "height", "href", "id", "image-rendering", "in", "in2", "intercept", "k", "k1", "k2", "k3", "k4", "kerning", "keypoints", "keysplines", "keytimes", "lang", "lengthadjust", "letter-spacing", "kernelmatrix", "kernelunitlength", "lighting-color", "local", "marker-end", "marker-mid", "marker-start", "markerheight", "markerunits", "markerwidth", "maskcontentunits", "maskunits", "max", "mask", "media", "method", "mode", "min", "name", "numoctaves", "offset", "operator", "opacity", "order", "orient", "orientation", "origin", "overflow", "paint-order", "path", "pathlength", "patterncontentunits", "patterntransform", "patternunits", "points", "preservealpha", "preserveaspectratio", "primitiveunits", "r", "rx", "ry", "radius", "refx", "refy", "repeatcount", "repeatdur", "restart", "result", "rotate", "scale", "seed", "shape-rendering", "slope", "specularconstant", "specularexponent", "spreadmethod", "startoffset", "stddeviation", "stitchtiles", "stop-color", "stop-opacity", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke", "stroke-width", "style", "surfacescale", "systemlanguage", "tabindex", "tablevalues", "targetx", "targety", "transform", "transform-origin", "text-anchor", "text-decoration", "text-rendering", "textlength", "type", "u1", "u2", "unicode", "values", "viewbox", "visibility", "version", "vert-adv-y", "vert-origin-x", "vert-origin-y", "width", "word-spacing", "wrap", "writing-mode", "xchannelselector", "ychannelselector", "x", "x1", "x2", "xmlns", "y", "y1", "y2", "z", "zoomandpan"]);
    var mathMl = freeze(["accent", "accentunder", "align", "bevelled", "close", "columnsalign", "columnlines", "columnspan", "denomalign", "depth", "dir", "display", "displaystyle", "encoding", "fence", "frame", "height", "href", "id", "largeop", "length", "linethickness", "lspace", "lquote", "mathbackground", "mathcolor", "mathsize", "mathvariant", "maxsize", "minsize", "movablelimits", "notation", "numalign", "open", "rowalign", "rowlines", "rowspacing", "rowspan", "rspace", "rquote", "scriptlevel", "scriptminsize", "scriptsizemultiplier", "selection", "separator", "separators", "stretchy", "subscriptshift", "supscriptshift", "symmetric", "voffset", "width", "xmlns"]);
    var xml = freeze(["xlink:href", "xml:id", "xlink:title", "xml:space", "xmlns:xlink"]);
    var MUSTACHE_EXPR = seal(/\{\{[\w\W]*|[\w\W]*\}\}/gm);
    var ERB_EXPR = seal(/<%[\w\W]*|[\w\W]*%>/gm);
    var TMPLIT_EXPR = seal(/\$\{[\w\W]*/gm);
    var DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]+$/);
    var ARIA_ATTR = seal(/^aria-[\-\w]+$/);
    var IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i);
    var IS_SCRIPT_OR_DATA = seal(/^(?:\w+script|data):/i);
    var ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g);
    var DOCTYPE_NAME = seal(/^html$/i);
    var CUSTOM_ELEMENT = seal(/^[a-z][.\w]*(-[.\w]+)+$/i);
    var EXPRESSIONS = /* @__PURE__ */ Object.freeze({
      __proto__: null,
      ARIA_ATTR,
      ATTR_WHITESPACE,
      CUSTOM_ELEMENT,
      DATA_ATTR,
      DOCTYPE_NAME,
      ERB_EXPR,
      IS_ALLOWED_URI,
      IS_SCRIPT_OR_DATA,
      MUSTACHE_EXPR,
      TMPLIT_EXPR
    });
    var NODE_TYPE = {
      element: 1,
      attribute: 2,
      text: 3,
      cdataSection: 4,
      entityReference: 5,
      entityNode: 6,
      progressingInstruction: 7,
      comment: 8,
      document: 9,
      documentType: 10,
      documentFragment: 11,
      notation: 12
    };
    var getGlobal = function getGlobal2() {
      return typeof window === "undefined" ? null : window;
    };
    var _createTrustedTypesPolicy = function _createTrustedTypesPolicy2(trustedTypes, purifyHostElement) {
      if (typeof trustedTypes !== "object" || typeof trustedTypes.createPolicy !== "function") {
        return null;
      }
      let suffix = null;
      const ATTR_NAME = "data-tt-policy-suffix";
      if (purifyHostElement && purifyHostElement.hasAttribute(ATTR_NAME)) {
        suffix = purifyHostElement.getAttribute(ATTR_NAME);
      }
      const policyName = "dompurify" + (suffix ? "#" + suffix : "");
      try {
        return trustedTypes.createPolicy(policyName, {
          createHTML(html2) {
            return html2;
          },
          createScriptURL(scriptUrl) {
            return scriptUrl;
          }
        });
      } catch (_) {
        console.warn("TrustedTypes policy " + policyName + " could not be created.");
        return null;
      }
    };
    var _createHooksMap = function _createHooksMap2() {
      return {
        afterSanitizeAttributes: [],
        afterSanitizeElements: [],
        afterSanitizeShadowDOM: [],
        beforeSanitizeAttributes: [],
        beforeSanitizeElements: [],
        beforeSanitizeShadowDOM: [],
        uponSanitizeAttribute: [],
        uponSanitizeElement: [],
        uponSanitizeShadowNode: []
      };
    };
    function createDOMPurify() {
      let window2 = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : getGlobal();
      const DOMPurify2 = (root) => createDOMPurify(root);
      DOMPurify2.version = "3.2.6";
      DOMPurify2.removed = [];
      if (!window2 || !window2.document || window2.document.nodeType !== NODE_TYPE.document || !window2.Element) {
        DOMPurify2.isSupported = false;
        return DOMPurify2;
      }
      let {
        document: document2
      } = window2;
      const originalDocument = document2;
      const currentScript = originalDocument.currentScript;
      const {
        DocumentFragment,
        HTMLTemplateElement,
        Node,
        Element,
        NodeFilter,
        NamedNodeMap = window2.NamedNodeMap || window2.MozNamedAttrMap,
        HTMLFormElement,
        DOMParser,
        trustedTypes
      } = window2;
      const ElementPrototype = Element.prototype;
      const cloneNode = lookupGetter(ElementPrototype, "cloneNode");
      const remove = lookupGetter(ElementPrototype, "remove");
      const getNextSibling = lookupGetter(ElementPrototype, "nextSibling");
      const getChildNodes = lookupGetter(ElementPrototype, "childNodes");
      const getParentNode = lookupGetter(ElementPrototype, "parentNode");
      if (typeof HTMLTemplateElement === "function") {
        const template = document2.createElement("template");
        if (template.content && template.content.ownerDocument) {
          document2 = template.content.ownerDocument;
        }
      }
      let trustedTypesPolicy;
      let emptyHTML = "";
      const {
        implementation,
        createNodeIterator,
        createDocumentFragment,
        getElementsByTagName
      } = document2;
      const {
        importNode
      } = originalDocument;
      let hooks = _createHooksMap();
      DOMPurify2.isSupported = typeof entries === "function" && typeof getParentNode === "function" && implementation && implementation.createHTMLDocument !== void 0;
      const {
        MUSTACHE_EXPR: MUSTACHE_EXPR2,
        ERB_EXPR: ERB_EXPR2,
        TMPLIT_EXPR: TMPLIT_EXPR2,
        DATA_ATTR: DATA_ATTR2,
        ARIA_ATTR: ARIA_ATTR2,
        IS_SCRIPT_OR_DATA: IS_SCRIPT_OR_DATA2,
        ATTR_WHITESPACE: ATTR_WHITESPACE2,
        CUSTOM_ELEMENT: CUSTOM_ELEMENT2
      } = EXPRESSIONS;
      let {
        IS_ALLOWED_URI: IS_ALLOWED_URI$1
      } = EXPRESSIONS;
      let ALLOWED_TAGS = null;
      const DEFAULT_ALLOWED_TAGS = addToSet({}, [...html$1, ...svg$1, ...svgFilters, ...mathMl$1, ...text]);
      let ALLOWED_ATTR = null;
      const DEFAULT_ALLOWED_ATTR = addToSet({}, [...html, ...svg, ...mathMl, ...xml]);
      let CUSTOM_ELEMENT_HANDLING = Object.seal(create(null, {
        tagNameCheck: {
          writable: true,
          configurable: false,
          enumerable: true,
          value: null
        },
        attributeNameCheck: {
          writable: true,
          configurable: false,
          enumerable: true,
          value: null
        },
        allowCustomizedBuiltInElements: {
          writable: true,
          configurable: false,
          enumerable: true,
          value: false
        }
      }));
      let FORBID_TAGS = null;
      let FORBID_ATTR = null;
      let ALLOW_ARIA_ATTR = true;
      let ALLOW_DATA_ATTR = true;
      let ALLOW_UNKNOWN_PROTOCOLS = false;
      let ALLOW_SELF_CLOSE_IN_ATTR = true;
      let SAFE_FOR_TEMPLATES = false;
      let SAFE_FOR_XML = true;
      let WHOLE_DOCUMENT = false;
      let SET_CONFIG = false;
      let FORCE_BODY = false;
      let RETURN_DOM = false;
      let RETURN_DOM_FRAGMENT = false;
      let RETURN_TRUSTED_TYPE = false;
      let SANITIZE_DOM = true;
      let SANITIZE_NAMED_PROPS = false;
      const SANITIZE_NAMED_PROPS_PREFIX = "user-content-";
      let KEEP_CONTENT = true;
      let IN_PLACE = false;
      let USE_PROFILES = {};
      let FORBID_CONTENTS = null;
      const DEFAULT_FORBID_CONTENTS = addToSet({}, ["annotation-xml", "audio", "colgroup", "desc", "foreignobject", "head", "iframe", "math", "mi", "mn", "mo", "ms", "mtext", "noembed", "noframes", "noscript", "plaintext", "script", "style", "svg", "template", "thead", "title", "video", "xmp"]);
      let DATA_URI_TAGS = null;
      const DEFAULT_DATA_URI_TAGS = addToSet({}, ["audio", "video", "img", "source", "image", "track"]);
      let URI_SAFE_ATTRIBUTES = null;
      const DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ["alt", "class", "for", "id", "label", "name", "pattern", "placeholder", "role", "summary", "title", "value", "style", "xmlns"]);
      const MATHML_NAMESPACE = "http://www.w3.org/1998/Math/MathML";
      const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
      const HTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
      let NAMESPACE = HTML_NAMESPACE;
      let IS_EMPTY_INPUT = false;
      let ALLOWED_NAMESPACES = null;
      const DEFAULT_ALLOWED_NAMESPACES = addToSet({}, [MATHML_NAMESPACE, SVG_NAMESPACE, HTML_NAMESPACE], stringToString);
      let MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ["mi", "mo", "mn", "ms", "mtext"]);
      let HTML_INTEGRATION_POINTS = addToSet({}, ["annotation-xml"]);
      const COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, ["title", "style", "font", "a", "script"]);
      let PARSER_MEDIA_TYPE = null;
      const SUPPORTED_PARSER_MEDIA_TYPES = ["application/xhtml+xml", "text/html"];
      const DEFAULT_PARSER_MEDIA_TYPE = "text/html";
      let transformCaseFunc = null;
      let CONFIG = null;
      const formElement = document2.createElement("form");
      const isRegexOrFunction = function isRegexOrFunction2(testValue) {
        return testValue instanceof RegExp || testValue instanceof Function;
      };
      const _parseConfig = function _parseConfig2() {
        let cfg = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {};
        if (CONFIG && CONFIG === cfg) {
          return;
        }
        if (!cfg || typeof cfg !== "object") {
          cfg = {};
        }
        cfg = clone(cfg);
        PARSER_MEDIA_TYPE = SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1 ? DEFAULT_PARSER_MEDIA_TYPE : cfg.PARSER_MEDIA_TYPE;
        transformCaseFunc = PARSER_MEDIA_TYPE === "application/xhtml+xml" ? stringToString : stringToLowerCase;
        ALLOWED_TAGS = objectHasOwnProperty(cfg, "ALLOWED_TAGS") ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc) : DEFAULT_ALLOWED_TAGS;
        ALLOWED_ATTR = objectHasOwnProperty(cfg, "ALLOWED_ATTR") ? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc) : DEFAULT_ALLOWED_ATTR;
        ALLOWED_NAMESPACES = objectHasOwnProperty(cfg, "ALLOWED_NAMESPACES") ? addToSet({}, cfg.ALLOWED_NAMESPACES, stringToString) : DEFAULT_ALLOWED_NAMESPACES;
        URI_SAFE_ATTRIBUTES = objectHasOwnProperty(cfg, "ADD_URI_SAFE_ATTR") ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), cfg.ADD_URI_SAFE_ATTR, transformCaseFunc) : DEFAULT_URI_SAFE_ATTRIBUTES;
        DATA_URI_TAGS = objectHasOwnProperty(cfg, "ADD_DATA_URI_TAGS") ? addToSet(clone(DEFAULT_DATA_URI_TAGS), cfg.ADD_DATA_URI_TAGS, transformCaseFunc) : DEFAULT_DATA_URI_TAGS;
        FORBID_CONTENTS = objectHasOwnProperty(cfg, "FORBID_CONTENTS") ? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc) : DEFAULT_FORBID_CONTENTS;
        FORBID_TAGS = objectHasOwnProperty(cfg, "FORBID_TAGS") ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) : clone({});
        FORBID_ATTR = objectHasOwnProperty(cfg, "FORBID_ATTR") ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : clone({});
        USE_PROFILES = objectHasOwnProperty(cfg, "USE_PROFILES") ? cfg.USE_PROFILES : false;
        ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false;
        ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false;
        ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false;
        ALLOW_SELF_CLOSE_IN_ATTR = cfg.ALLOW_SELF_CLOSE_IN_ATTR !== false;
        SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false;
        SAFE_FOR_XML = cfg.SAFE_FOR_XML !== false;
        WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false;
        RETURN_DOM = cfg.RETURN_DOM || false;
        RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false;
        RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false;
        FORCE_BODY = cfg.FORCE_BODY || false;
        SANITIZE_DOM = cfg.SANITIZE_DOM !== false;
        SANITIZE_NAMED_PROPS = cfg.SANITIZE_NAMED_PROPS || false;
        KEEP_CONTENT = cfg.KEEP_CONTENT !== false;
        IN_PLACE = cfg.IN_PLACE || false;
        IS_ALLOWED_URI$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI;
        NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE;
        MATHML_TEXT_INTEGRATION_POINTS = cfg.MATHML_TEXT_INTEGRATION_POINTS || MATHML_TEXT_INTEGRATION_POINTS;
        HTML_INTEGRATION_POINTS = cfg.HTML_INTEGRATION_POINTS || HTML_INTEGRATION_POINTS;
        CUSTOM_ELEMENT_HANDLING = cfg.CUSTOM_ELEMENT_HANDLING || {};
        if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck)) {
          CUSTOM_ELEMENT_HANDLING.tagNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck;
        }
        if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)) {
          CUSTOM_ELEMENT_HANDLING.attributeNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck;
        }
        if (cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements === "boolean") {
          CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements;
        }
        if (SAFE_FOR_TEMPLATES) {
          ALLOW_DATA_ATTR = false;
        }
        if (RETURN_DOM_FRAGMENT) {
          RETURN_DOM = true;
        }
        if (USE_PROFILES) {
          ALLOWED_TAGS = addToSet({}, text);
          ALLOWED_ATTR = [];
          if (USE_PROFILES.html === true) {
            addToSet(ALLOWED_TAGS, html$1);
            addToSet(ALLOWED_ATTR, html);
          }
          if (USE_PROFILES.svg === true) {
            addToSet(ALLOWED_TAGS, svg$1);
            addToSet(ALLOWED_ATTR, svg);
            addToSet(ALLOWED_ATTR, xml);
          }
          if (USE_PROFILES.svgFilters === true) {
            addToSet(ALLOWED_TAGS, svgFilters);
            addToSet(ALLOWED_ATTR, svg);
            addToSet(ALLOWED_ATTR, xml);
          }
          if (USE_PROFILES.mathMl === true) {
            addToSet(ALLOWED_TAGS, mathMl$1);
            addToSet(ALLOWED_ATTR, mathMl);
            addToSet(ALLOWED_ATTR, xml);
          }
        }
        if (cfg.ADD_TAGS) {
          if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {
            ALLOWED_TAGS = clone(ALLOWED_TAGS);
          }
          addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);
        }
        if (cfg.ADD_ATTR) {
          if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {
            ALLOWED_ATTR = clone(ALLOWED_ATTR);
          }
          addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);
        }
        if (cfg.ADD_URI_SAFE_ATTR) {
          addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc);
        }
        if (cfg.FORBID_CONTENTS) {
          if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
            FORBID_CONTENTS = clone(FORBID_CONTENTS);
          }
          addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc);
        }
        if (KEEP_CONTENT) {
          ALLOWED_TAGS["#text"] = true;
        }
        if (WHOLE_DOCUMENT) {
          addToSet(ALLOWED_TAGS, ["html", "head", "body"]);
        }
        if (ALLOWED_TAGS.table) {
          addToSet(ALLOWED_TAGS, ["tbody"]);
          delete FORBID_TAGS.tbody;
        }
        if (cfg.TRUSTED_TYPES_POLICY) {
          if (typeof cfg.TRUSTED_TYPES_POLICY.createHTML !== "function") {
            throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');
          }
          if (typeof cfg.TRUSTED_TYPES_POLICY.createScriptURL !== "function") {
            throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');
          }
          trustedTypesPolicy = cfg.TRUSTED_TYPES_POLICY;
          emptyHTML = trustedTypesPolicy.createHTML("");
        } else {
          if (trustedTypesPolicy === void 0) {
            trustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, currentScript);
          }
          if (trustedTypesPolicy !== null && typeof emptyHTML === "string") {
            emptyHTML = trustedTypesPolicy.createHTML("");
          }
        }
        if (freeze) {
          freeze(cfg);
        }
        CONFIG = cfg;
      };
      const ALL_SVG_TAGS = addToSet({}, [...svg$1, ...svgFilters, ...svgDisallowed]);
      const ALL_MATHML_TAGS = addToSet({}, [...mathMl$1, ...mathMlDisallowed]);
      const _checkValidNamespace = function _checkValidNamespace2(element) {
        let parent = getParentNode(element);
        if (!parent || !parent.tagName) {
          parent = {
            namespaceURI: NAMESPACE,
            tagName: "template"
          };
        }
        const tagName = stringToLowerCase(element.tagName);
        const parentTagName = stringToLowerCase(parent.tagName);
        if (!ALLOWED_NAMESPACES[element.namespaceURI]) {
          return false;
        }
        if (element.namespaceURI === SVG_NAMESPACE) {
          if (parent.namespaceURI === HTML_NAMESPACE) {
            return tagName === "svg";
          }
          if (parent.namespaceURI === MATHML_NAMESPACE) {
            return tagName === "svg" && (parentTagName === "annotation-xml" || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
          }
          return Boolean(ALL_SVG_TAGS[tagName]);
        }
        if (element.namespaceURI === MATHML_NAMESPACE) {
          if (parent.namespaceURI === HTML_NAMESPACE) {
            return tagName === "math";
          }
          if (parent.namespaceURI === SVG_NAMESPACE) {
            return tagName === "math" && HTML_INTEGRATION_POINTS[parentTagName];
          }
          return Boolean(ALL_MATHML_TAGS[tagName]);
        }
        if (element.namespaceURI === HTML_NAMESPACE) {
          if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {
            return false;
          }
          if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
            return false;
          }
          return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]);
        }
        if (PARSER_MEDIA_TYPE === "application/xhtml+xml" && ALLOWED_NAMESPACES[element.namespaceURI]) {
          return true;
        }
        return false;
      };
      const _forceRemove = function _forceRemove2(node) {
        arrayPush(DOMPurify2.removed, {
          element: node
        });
        try {
          getParentNode(node).removeChild(node);
        } catch (_) {
          remove(node);
        }
      };
      const _removeAttribute = function _removeAttribute2(name, element) {
        try {
          arrayPush(DOMPurify2.removed, {
            attribute: element.getAttributeNode(name),
            from: element
          });
        } catch (_) {
          arrayPush(DOMPurify2.removed, {
            attribute: null,
            from: element
          });
        }
        element.removeAttribute(name);
        if (name === "is") {
          if (RETURN_DOM || RETURN_DOM_FRAGMENT) {
            try {
              _forceRemove(element);
            } catch (_) {
            }
          } else {
            try {
              element.setAttribute(name, "");
            } catch (_) {
            }
          }
        }
      };
      const _initDocument = function _initDocument2(dirty) {
        let doc = null;
        let leadingWhitespace = null;
        if (FORCE_BODY) {
          dirty = "<remove></remove>" + dirty;
        } else {
          const matches = stringMatch(dirty, /^[\r\n\t ]+/);
          leadingWhitespace = matches && matches[0];
        }
        if (PARSER_MEDIA_TYPE === "application/xhtml+xml" && NAMESPACE === HTML_NAMESPACE) {
          dirty = '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' + dirty + "</body></html>";
        }
        const dirtyPayload = trustedTypesPolicy ? trustedTypesPolicy.createHTML(dirty) : dirty;
        if (NAMESPACE === HTML_NAMESPACE) {
          try {
            doc = new DOMParser().parseFromString(dirtyPayload, PARSER_MEDIA_TYPE);
          } catch (_) {
          }
        }
        if (!doc || !doc.documentElement) {
          doc = implementation.createDocument(NAMESPACE, "template", null);
          try {
            doc.documentElement.innerHTML = IS_EMPTY_INPUT ? emptyHTML : dirtyPayload;
          } catch (_) {
          }
        }
        const body = doc.body || doc.documentElement;
        if (dirty && leadingWhitespace) {
          body.insertBefore(document2.createTextNode(leadingWhitespace), body.childNodes[0] || null);
        }
        if (NAMESPACE === HTML_NAMESPACE) {
          return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? "html" : "body")[0];
        }
        return WHOLE_DOCUMENT ? doc.documentElement : body;
      };
      const _createNodeIterator = function _createNodeIterator2(root) {
        return createNodeIterator.call(root.ownerDocument || root, root, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_PROCESSING_INSTRUCTION | NodeFilter.SHOW_CDATA_SECTION, null);
      };
      const _isClobbered = function _isClobbered2(element) {
        return element instanceof HTMLFormElement && (typeof element.nodeName !== "string" || typeof element.textContent !== "string" || typeof element.removeChild !== "function" || !(element.attributes instanceof NamedNodeMap) || typeof element.removeAttribute !== "function" || typeof element.setAttribute !== "function" || typeof element.namespaceURI !== "string" || typeof element.insertBefore !== "function" || typeof element.hasChildNodes !== "function");
      };
      const _isNode = function _isNode2(value) {
        return typeof Node === "function" && value instanceof Node;
      };
      function _executeHooks(hooks2, currentNode, data) {
        arrayForEach(hooks2, (hook) => {
          hook.call(DOMPurify2, currentNode, data, CONFIG);
        });
      }
      const _sanitizeElements = function _sanitizeElements2(currentNode) {
        let content = null;
        _executeHooks(hooks.beforeSanitizeElements, currentNode, null);
        if (_isClobbered(currentNode)) {
          _forceRemove(currentNode);
          return true;
        }
        const tagName = transformCaseFunc(currentNode.nodeName);
        _executeHooks(hooks.uponSanitizeElement, currentNode, {
          tagName,
          allowedTags: ALLOWED_TAGS
        });
        if (SAFE_FOR_XML && currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(/<[/\w!]/g, currentNode.innerHTML) && regExpTest(/<[/\w!]/g, currentNode.textContent)) {
          _forceRemove(currentNode);
          return true;
        }
        if (currentNode.nodeType === NODE_TYPE.progressingInstruction) {
          _forceRemove(currentNode);
          return true;
        }
        if (SAFE_FOR_XML && currentNode.nodeType === NODE_TYPE.comment && regExpTest(/<[/\w]/g, currentNode.data)) {
          _forceRemove(currentNode);
          return true;
        }
        if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
          if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) {
            if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) {
              return false;
            }
            if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)) {
              return false;
            }
          }
          if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
            const parentNode = getParentNode(currentNode) || currentNode.parentNode;
            const childNodes = getChildNodes(currentNode) || currentNode.childNodes;
            if (childNodes && parentNode) {
              const childCount = childNodes.length;
              for (let i = childCount - 1; i >= 0; --i) {
                const childClone = cloneNode(childNodes[i], true);
                childClone.__removalCount = (currentNode.__removalCount || 0) + 1;
                parentNode.insertBefore(childClone, getNextSibling(currentNode));
              }
            }
          }
          _forceRemove(currentNode);
          return true;
        }
        if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) {
          _forceRemove(currentNode);
          return true;
        }
        if ((tagName === "noscript" || tagName === "noembed" || tagName === "noframes") && regExpTest(/<\/no(script|embed|frames)/i, currentNode.innerHTML)) {
          _forceRemove(currentNode);
          return true;
        }
        if (SAFE_FOR_TEMPLATES && currentNode.nodeType === NODE_TYPE.text) {
          content = currentNode.textContent;
          arrayForEach([MUSTACHE_EXPR2, ERB_EXPR2, TMPLIT_EXPR2], (expr) => {
            content = stringReplace(content, expr, " ");
          });
          if (currentNode.textContent !== content) {
            arrayPush(DOMPurify2.removed, {
              element: currentNode.cloneNode()
            });
            currentNode.textContent = content;
          }
        }
        _executeHooks(hooks.afterSanitizeElements, currentNode, null);
        return false;
      };
      const _isValidAttribute = function _isValidAttribute2(lcTag, lcName, value) {
        if (SANITIZE_DOM && (lcName === "id" || lcName === "name") && (value in document2 || value in formElement)) {
          return false;
        }
        if (ALLOW_DATA_ATTR && !FORBID_ATTR[lcName] && regExpTest(DATA_ATTR2, lcName))
          ;
        else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR2, lcName))
          ;
        else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) {
          if (_isBasicCustomElement(lcTag) && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, lcTag) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(lcTag)) && (CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.attributeNameCheck, lcName) || CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.attributeNameCheck(lcName)) || lcName === "is" && CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, value) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(value)))
            ;
          else {
            return false;
          }
        } else if (URI_SAFE_ATTRIBUTES[lcName])
          ;
        else if (regExpTest(IS_ALLOWED_URI$1, stringReplace(value, ATTR_WHITESPACE2, "")))
          ;
        else if ((lcName === "src" || lcName === "xlink:href" || lcName === "href") && lcTag !== "script" && stringIndexOf(value, "data:") === 0 && DATA_URI_TAGS[lcTag])
          ;
        else if (ALLOW_UNKNOWN_PROTOCOLS && !regExpTest(IS_SCRIPT_OR_DATA2, stringReplace(value, ATTR_WHITESPACE2, "")))
          ;
        else if (value) {
          return false;
        } else
          ;
        return true;
      };
      const _isBasicCustomElement = function _isBasicCustomElement2(tagName) {
        return tagName !== "annotation-xml" && stringMatch(tagName, CUSTOM_ELEMENT2);
      };
      const _sanitizeAttributes = function _sanitizeAttributes2(currentNode) {
        _executeHooks(hooks.beforeSanitizeAttributes, currentNode, null);
        const {
          attributes
        } = currentNode;
        if (!attributes || _isClobbered(currentNode)) {
          return;
        }
        const hookEvent = {
          attrName: "",
          attrValue: "",
          keepAttr: true,
          allowedAttributes: ALLOWED_ATTR,
          forceKeepAttr: void 0
        };
        let l = attributes.length;
        while (l--) {
          const attr = attributes[l];
          const {
            name,
            namespaceURI,
            value: attrValue
          } = attr;
          const lcName = transformCaseFunc(name);
          const initValue = attrValue;
          let value = name === "value" ? initValue : stringTrim(initValue);
          hookEvent.attrName = lcName;
          hookEvent.attrValue = value;
          hookEvent.keepAttr = true;
          hookEvent.forceKeepAttr = void 0;
          _executeHooks(hooks.uponSanitizeAttribute, currentNode, hookEvent);
          value = hookEvent.attrValue;
          if (SANITIZE_NAMED_PROPS && (lcName === "id" || lcName === "name")) {
            _removeAttribute(name, currentNode);
            value = SANITIZE_NAMED_PROPS_PREFIX + value;
          }
          if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|title)/i, value)) {
            _removeAttribute(name, currentNode);
            continue;
          }
          if (hookEvent.forceKeepAttr) {
            continue;
          }
          if (!hookEvent.keepAttr) {
            _removeAttribute(name, currentNode);
            continue;
          }
          if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(/\/>/i, value)) {
            _removeAttribute(name, currentNode);
            continue;
          }
          if (SAFE_FOR_TEMPLATES) {
            arrayForEach([MUSTACHE_EXPR2, ERB_EXPR2, TMPLIT_EXPR2], (expr) => {
              value = stringReplace(value, expr, " ");
            });
          }
          const lcTag = transformCaseFunc(currentNode.nodeName);
          if (!_isValidAttribute(lcTag, lcName, value)) {
            _removeAttribute(name, currentNode);
            continue;
          }
          if (trustedTypesPolicy && typeof trustedTypes === "object" && typeof trustedTypes.getAttributeType === "function") {
            if (namespaceURI)
              ;
            else {
              switch (trustedTypes.getAttributeType(lcTag, lcName)) {
                case "TrustedHTML": {
                  value = trustedTypesPolicy.createHTML(value);
                  break;
                }
                case "TrustedScriptURL": {
                  value = trustedTypesPolicy.createScriptURL(value);
                  break;
                }
              }
            }
          }
          if (value !== initValue) {
            try {
              if (namespaceURI) {
                currentNode.setAttributeNS(namespaceURI, name, value);
              } else {
                currentNode.setAttribute(name, value);
              }
              if (_isClobbered(currentNode)) {
                _forceRemove(currentNode);
              } else {
                arrayPop(DOMPurify2.removed);
              }
            } catch (_) {
              _removeAttribute(name, currentNode);
            }
          }
        }
        _executeHooks(hooks.afterSanitizeAttributes, currentNode, null);
      };
      const _sanitizeShadowDOM = function _sanitizeShadowDOM2(fragment) {
        let shadowNode = null;
        const shadowIterator = _createNodeIterator(fragment);
        _executeHooks(hooks.beforeSanitizeShadowDOM, fragment, null);
        while (shadowNode = shadowIterator.nextNode()) {
          _executeHooks(hooks.uponSanitizeShadowNode, shadowNode, null);
          _sanitizeElements(shadowNode);
          _sanitizeAttributes(shadowNode);
          if (shadowNode.content instanceof DocumentFragment) {
            _sanitizeShadowDOM2(shadowNode.content);
          }
        }
        _executeHooks(hooks.afterSanitizeShadowDOM, fragment, null);
      };
      DOMPurify2.sanitize = function(dirty) {
        let cfg = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {};
        let body = null;
        let importedNode = null;
        let currentNode = null;
        let returnNode = null;
        IS_EMPTY_INPUT = !dirty;
        if (IS_EMPTY_INPUT) {
          dirty = "<!-->";
        }
        if (typeof dirty !== "string" && !_isNode(dirty)) {
          if (typeof dirty.toString === "function") {
            dirty = dirty.toString();
            if (typeof dirty !== "string") {
              throw typeErrorCreate("dirty is not a string, aborting");
            }
          } else {
            throw typeErrorCreate("toString is not a function");
          }
        }
        if (!DOMPurify2.isSupported) {
          return dirty;
        }
        if (!SET_CONFIG) {
          _parseConfig(cfg);
        }
        DOMPurify2.removed = [];
        if (typeof dirty === "string") {
          IN_PLACE = false;
        }
        if (IN_PLACE) {
          if (dirty.nodeName) {
            const tagName = transformCaseFunc(dirty.nodeName);
            if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
              throw typeErrorCreate("root node is forbidden and cannot be sanitized in-place");
            }
          }
        } else if (dirty instanceof Node) {
          body = _initDocument("<!---->");
          importedNode = body.ownerDocument.importNode(dirty, true);
          if (importedNode.nodeType === NODE_TYPE.element && importedNode.nodeName === "BODY") {
            body = importedNode;
          } else if (importedNode.nodeName === "HTML") {
            body = importedNode;
          } else {
            body.appendChild(importedNode);
          }
        } else {
          if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT && dirty.indexOf("<") === -1) {
            return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(dirty) : dirty;
          }
          body = _initDocument(dirty);
          if (!body) {
            return RETURN_DOM ? null : RETURN_TRUSTED_TYPE ? emptyHTML : "";
          }
        }
        if (body && FORCE_BODY) {
          _forceRemove(body.firstChild);
        }
        const nodeIterator = _createNodeIterator(IN_PLACE ? dirty : body);
        while (currentNode = nodeIterator.nextNode()) {
          _sanitizeElements(currentNode);
          _sanitizeAttributes(currentNode);
          if (currentNode.content instanceof DocumentFragment) {
            _sanitizeShadowDOM(currentNode.content);
          }
        }
        if (IN_PLACE) {
          return dirty;
        }
        if (RETURN_DOM) {
          if (RETURN_DOM_FRAGMENT) {
            returnNode = createDocumentFragment.call(body.ownerDocument);
            while (body.firstChild) {
              returnNode.appendChild(body.firstChild);
            }
          } else {
            returnNode = body;
          }
          if (ALLOWED_ATTR.shadowroot || ALLOWED_ATTR.shadowrootmode) {
            returnNode = importNode.call(originalDocument, returnNode, true);
          }
          return returnNode;
        }
        let serializedHTML = WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML;
        if (WHOLE_DOCUMENT && ALLOWED_TAGS["!doctype"] && body.ownerDocument && body.ownerDocument.doctype && body.ownerDocument.doctype.name && regExpTest(DOCTYPE_NAME, body.ownerDocument.doctype.name)) {
          serializedHTML = "<!DOCTYPE " + body.ownerDocument.doctype.name + ">\n" + serializedHTML;
        }
        if (SAFE_FOR_TEMPLATES) {
          arrayForEach([MUSTACHE_EXPR2, ERB_EXPR2, TMPLIT_EXPR2], (expr) => {
            serializedHTML = stringReplace(serializedHTML, expr, " ");
          });
        }
        return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(serializedHTML) : serializedHTML;
      };
      DOMPurify2.setConfig = function() {
        let cfg = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {};
        _parseConfig(cfg);
        SET_CONFIG = true;
      };
      DOMPurify2.clearConfig = function() {
        CONFIG = null;
        SET_CONFIG = false;
      };
      DOMPurify2.isValidAttribute = function(tag, attr, value) {
        if (!CONFIG) {
          _parseConfig({});
        }
        const lcTag = transformCaseFunc(tag);
        const lcName = transformCaseFunc(attr);
        return _isValidAttribute(lcTag, lcName, value);
      };
      DOMPurify2.addHook = function(entryPoint, hookFunction) {
        if (typeof hookFunction !== "function") {
          return;
        }
        arrayPush(hooks[entryPoint], hookFunction);
      };
      DOMPurify2.removeHook = function(entryPoint, hookFunction) {
        if (hookFunction !== void 0) {
          const index = arrayLastIndexOf(hooks[entryPoint], hookFunction);
          return index === -1 ? void 0 : arraySplice(hooks[entryPoint], index, 1)[0];
        }
        return arrayPop(hooks[entryPoint]);
      };
      DOMPurify2.removeHooks = function(entryPoint) {
        hooks[entryPoint] = [];
      };
      DOMPurify2.removeAllHooks = function() {
        hooks = _createHooksMap();
      };
      return DOMPurify2;
    }
    var purify = createDOMPurify();
    module2.exports = purify;
  }
});

// node_modules/isomorphic-dompurify/browser.js
var require_browser = __commonJS({
  "node_modules/isomorphic-dompurify/browser.js"(exports, module2) {
    module2.exports = window.DOMPurify || (window.DOMPurify = require_purify_cjs().default || require_purify_cjs());
  }
});

// src/main.ts
var main_exports = {};
__export(main_exports, {
  default: () => Khoj
});
module.exports = __toCommonJS(main_exports);
var import_obsidian8 = require("obsidian");

// src/settings.ts
var import_obsidian3 = require("obsidian");
init_utils();
var DEFAULT_SETTINGS = {
  resultsCount: 15,
  khojUrl: "https://app.khoj.dev",
  khojApiKey: "",
  connectedToBackend: false,
  autoConfigure: true,
  lastSync: /* @__PURE__ */ new Map(),
  syncFileType: {
    markdown: true,
    images: true,
    pdf: true
  },
  userInfo: null,
  syncFolders: [],
  excludeFolders: [],
  syncInterval: 60,
  autoVoiceResponse: true,
  fileAccessMode: "read",
  selectedChatModelId: null,
  availableChatModels: []
};
var KhojSettingTab = class extends import_obsidian3.PluginSettingTab {
  constructor(app, plugin) {
    super(app, plugin);
    this.chatModelSetting = null;
    this.storageProgressEl = null;
    this.storageProgressText = null;
    this.plugin = plugin;
  }
  display() {
    var _a, _b;
    const { containerEl } = this;
    containerEl.empty();
    this.chatModelSetting = null;
    let backendStatusMessage = getBackendStatusMessage(this.plugin.settings.connectedToBackend, (_a = this.plugin.settings.userInfo) == null ? void 0 : _a.email, this.plugin.settings.khojUrl, this.plugin.settings.khojApiKey);
    const connectHeaderEl = containerEl.createEl("h3", { title: backendStatusMessage });
    const connectHeaderContentEl = connectHeaderEl.createSpan({ cls: "khoj-connect-settings-header" });
    const connectTitleEl = connectHeaderContentEl.createSpan({ text: "Connect" });
    const backendStatusEl = connectTitleEl.createSpan({ text: this.connectStatusIcon(), cls: "khoj-connect-settings-header-status" });
    if (this.plugin.settings.userInfo && this.plugin.settings.connectedToBackend) {
      if (this.plugin.settings.userInfo.photo) {
        const profilePicEl = connectHeaderContentEl.createEl("img", {
          attr: { src: this.plugin.settings.userInfo.photo },
          cls: "khoj-profile"
        });
        profilePicEl.addEventListener("click", () => {
          new import_obsidian3.Notice(backendStatusMessage);
        });
      } else if (this.plugin.settings.userInfo.email) {
        const initial = this.plugin.settings.userInfo.email[0].toUpperCase();
        const profilePicEl = connectHeaderContentEl.createDiv({
          text: initial,
          cls: "khoj-profile khoj-profile-initial"
        });
        profilePicEl.addEventListener("click", () => {
          new import_obsidian3.Notice(backendStatusMessage);
        });
      }
    }
    if (this.plugin.settings.userInfo && this.plugin.settings.userInfo.email) {
      connectHeaderEl.title = ((_b = this.plugin.settings.userInfo) == null ? void 0 : _b.email) === "default@example.com" ? "Signed in" : `Signed in as ${this.plugin.settings.userInfo.email}`;
    }
    const apiKeySetting = new import_obsidian3.Setting(containerEl).setName("Khoj API Key").addText((text) => text.setValue(`${this.plugin.settings.khojApiKey}`).onChange(async (value) => {
      this.plugin.settings.khojApiKey = value.trim();
      ({
        connectedToBackend: this.plugin.settings.connectedToBackend,
        userInfo: this.plugin.settings.userInfo,
        statusMessage: backendStatusMessage
      } = await canConnectToBackend(this.plugin.settings.khojUrl, this.plugin.settings.khojApiKey));
      if (!this.plugin.settings.connectedToBackend) {
        this.plugin.settings.availableChatModels = [];
        this.plugin.settings.selectedChatModelId = null;
      }
      await this.plugin.saveSettings();
      backendStatusEl.setText(this.connectStatusIcon());
      connectHeaderEl.title = backendStatusMessage;
      await this.refreshModelsAndServerPreference();
    }));
    apiKeySetting.descEl.createEl("span", {
      text: "Connect your Khoj Cloud account. "
    });
    apiKeySetting.descEl.createEl("a", {
      text: "Get your API Key",
      href: `${this.plugin.settings.khojUrl}/settings#clients`,
      attr: { target: "_blank" }
    });
    new import_obsidian3.Setting(containerEl).setName("Khoj URL").setDesc("The URL of the Khoj backend.").addText((text) => text.setValue(`${this.plugin.settings.khojUrl}`).onChange(async (value) => {
      this.plugin.settings.khojUrl = value.trim().replace(/\/$/, "");
      ({
        connectedToBackend: this.plugin.settings.connectedToBackend,
        userInfo: this.plugin.settings.userInfo,
        statusMessage: backendStatusMessage
      } = await canConnectToBackend(this.plugin.settings.khojUrl, this.plugin.settings.khojApiKey));
      if (!this.plugin.settings.connectedToBackend) {
        this.plugin.settings.availableChatModels = [];
        this.plugin.settings.selectedChatModelId = null;
      }
      await this.plugin.saveSettings();
      backendStatusEl.setText(this.connectStatusIcon());
      connectHeaderEl.title = backendStatusMessage;
      await this.refreshModelsAndServerPreference();
    }));
    containerEl.createEl("h3", { text: "Interact" });
    this.renderChatModelDropdown();
    if (this.plugin.settings.connectedToBackend) {
      setTimeout(async () => {
        await this.refreshModelsAndServerPreference();
      }, 1e3);
    }
    new import_obsidian3.Setting(containerEl).setName("Auto Voice Response").setDesc("Automatically read responses after voice messages").addToggle((toggle) => toggle.setValue(this.plugin.settings.autoVoiceResponse).onChange(async (value) => {
      this.plugin.settings.autoVoiceResponse = value;
      await this.plugin.saveSettings();
    }));
    new import_obsidian3.Setting(containerEl).setName("Results Count").setDesc("The number of results to show in search and use for chat.").addSlider((slider) => slider.setLimits(1, 30, 1).setValue(this.plugin.settings.resultsCount).setDynamicTooltip().onChange(async (value) => {
      this.plugin.settings.resultsCount = value;
      await this.plugin.saveSettings();
    }));
    containerEl.createEl("h3", { text: "Sync" });
    new import_obsidian3.Setting(containerEl).setName("Auto Sync").setDesc("Automatically index your vault with Khoj.").addToggle((toggle) => toggle.setValue(this.plugin.settings.autoConfigure).onChange(async (value) => {
      this.plugin.settings.autoConfigure = value;
      await this.plugin.saveSettings();
    }));
    new import_obsidian3.Setting(containerEl).setName("Sync Notes").setDesc("Index Markdown files in your vault with Khoj.").addToggle((toggle) => toggle.setValue(this.plugin.settings.syncFileType.markdown).onChange(async (value) => {
      this.plugin.settings.syncFileType.markdown = value;
      await this.plugin.saveSettings();
      this.refreshStorageDisplay();
    }));
    new import_obsidian3.Setting(containerEl).setName("Sync Images").setDesc("Index images in your vault with Khoj.").addToggle((toggle) => toggle.setValue(this.plugin.settings.syncFileType.images).onChange(async (value) => {
      this.plugin.settings.syncFileType.images = value;
      await this.plugin.saveSettings();
      this.refreshStorageDisplay();
    }));
    new import_obsidian3.Setting(containerEl).setName("Sync PDFs").setDesc("Index PDF files in your vault with Khoj.").addToggle((toggle) => toggle.setValue(this.plugin.settings.syncFileType.pdf).onChange(async (value) => {
      this.plugin.settings.syncFileType.pdf = value;
      await this.plugin.saveSettings();
      this.refreshStorageDisplay();
    }));
    const syncIntervalValues = [1, 5, 10, 20, 30, 45, 60, 120, 1440];
    new import_obsidian3.Setting(containerEl).setName("Sync Interval").setDesc("Minutes between automatic synchronizations").addDropdown((dropdown) => dropdown.addOptions(Object.fromEntries(syncIntervalValues.map((value) => [
      value.toString(),
      value === 1 ? "1 minute" : value === 1440 ? "24 hours" : `${value} minutes`
    ]))).setValue(this.plugin.settings.syncInterval.toString()).onChange(async (value) => {
      this.plugin.settings.syncInterval = parseInt(value);
      await this.plugin.saveSettings();
      this.plugin.restartSyncTimer();
    }));
    const includeFoldersContainer = containerEl.createDiv("include-folders-container");
    new import_obsidian3.Setting(includeFoldersContainer).setName("Include Folders").setDesc("Folders to sync (leave empty to sync entire vault)").addButton((button) => button.setButtonText("Add Folder").onClick(() => {
      const modal = new FolderSuggestModal(this.app, async (folder) => {
        if (!this.plugin.settings.syncFolders.includes(folder)) {
          this.plugin.settings.syncFolders.push(folder);
          await this.plugin.saveSettings();
          this.updateIncludeFolderList(includeFolderListEl);
          this.refreshStorageDisplay();
        }
      });
      modal.open();
    }));
    const includeFolderListEl = includeFoldersContainer.createDiv("folder-list");
    this.updateIncludeFolderList(includeFolderListEl);
    const excludeFoldersContainer = containerEl.createDiv("exclude-folders-container");
    new import_obsidian3.Setting(excludeFoldersContainer).setName("Exclude Folders").setDesc("Folders to exclude from sync (takes precedence over includes)").addButton((button) => button.setButtonText("Add Folder").onClick(() => {
      const modal = new FolderSuggestModal(this.app, async (folder) => {
        if (folder === "") {
          new import_obsidian3.Notice("Cannot exclude the root folder");
          return;
        }
        if (!this.plugin.settings.excludeFolders.includes(folder)) {
          this.plugin.settings.excludeFolders.push(folder);
          await this.plugin.saveSettings();
          this.updateExcludeFolderList(excludeFolderListEl);
          this.refreshStorageDisplay();
        }
      });
      modal.open();
    }));
    const excludeFolderListEl = excludeFoldersContainer.createDiv("folder-list");
    this.updateExcludeFolderList(excludeFolderListEl);
    let indexVaultSetting = new import_obsidian3.Setting(containerEl);
    indexVaultSetting.setName("Force Sync").setDesc("Manually force Khoj to re-index your Obsidian Vault.").addButton((button) => button.setButtonText("Update").setCta().onClick(async () => {
      button.setButtonText("Updating \u{1F311}");
      button.removeCta();
      indexVaultSetting = indexVaultSetting.setDisabled(true);
      const progress_indicator = window.setInterval(() => {
        if (button.buttonEl.innerText === "Updating \u{1F311}") {
          button.setButtonText("Updating \u{1F318}");
        } else if (button.buttonEl.innerText === "Updating \u{1F318}") {
          button.setButtonText("Updating \u{1F317}");
        } else if (button.buttonEl.innerText === "Updating \u{1F317}") {
          button.setButtonText("Updating \u{1F316}");
        } else if (button.buttonEl.innerText === "Updating \u{1F316}") {
          button.setButtonText("Updating \u{1F315}");
        } else if (button.buttonEl.innerText === "Updating \u{1F315}") {
          button.setButtonText("Updating \u{1F314}");
        } else if (button.buttonEl.innerText === "Updating \u{1F314}") {
          button.setButtonText("Updating \u{1F313}");
        } else if (button.buttonEl.innerText === "Updating \u{1F313}") {
          button.setButtonText("Updating \u{1F312}");
        } else if (button.buttonEl.innerText === "Updating \u{1F312}") {
          button.setButtonText("Updating \u{1F311}");
        }
      }, 300);
      this.plugin.registerInterval(progress_indicator);
      const syncProgressEl2 = document.getElementById("khoj-sync-progress");
      const syncProgressText2 = document.getElementById("khoj-sync-progress-text");
      if (syncProgressEl2 && syncProgressText2) {
        syncProgressEl2.style.display = "";
        syncProgressText2.style.display = "";
        syncProgressText2.textContent = "Preparing files...";
        syncProgressEl2.value = 0;
        syncProgressEl2.max = 1;
      }
      const onProgress = (progress) => {
        const el = document.getElementById("khoj-sync-progress");
        const txt = document.getElementById("khoj-sync-progress-text");
        if (!el || !txt)
          return;
        el.max = Math.max(progress.total, 1);
        el.value = Math.min(progress.processed, el.max);
        txt.textContent = `Syncing... ${progress.processed} / ${progress.total} files`;
      };
      try {
        this.plugin.settings.lastSync = await updateContentIndex(this.app.vault, this.plugin.settings, this.plugin.settings.lastSync, true, true, onProgress);
      } finally {
        const el = document.getElementById("khoj-sync-progress");
        const txt = document.getElementById("khoj-sync-progress-text");
        if (el)
          el.style.display = "none";
        if (txt)
          txt.style.display = "none";
        this.refreshStorageDisplay();
        window.clearInterval(progress_indicator);
        button.setButtonText("Update");
        button.setCta();
        indexVaultSetting = indexVaultSetting.setDisabled(false);
      }
    }));
    const storageSetting = new import_obsidian3.Setting(containerEl).setName("Estimated Cloud Storage").setDesc("Estimated storage usage based on files configured for sync. This is a client-side estimation.").then(() => {
    });
    this.storageProgressEl = document.createElement("progress");
    this.storageProgressEl.value = 0;
    this.storageProgressEl.max = 1;
    this.storageProgressEl.style.width = "100%";
    this.storageProgressText = document.createElement("span");
    this.storageProgressText.textContent = "Calculating...";
    storageSetting.descEl.appendChild(this.storageProgressEl);
    storageSetting.descEl.appendChild(this.storageProgressText);
    const syncProgressEl = document.createElement("progress");
    syncProgressEl.id = "khoj-sync-progress";
    syncProgressEl.value = 0;
    syncProgressEl.max = 1;
    syncProgressEl.style.width = "100%";
    syncProgressEl.style.display = "none";
    const syncProgressText = document.createElement("span");
    syncProgressText.id = "khoj-sync-progress-text";
    syncProgressText.textContent = "";
    syncProgressText.style.display = "none";
    storageSetting.descEl.appendChild(syncProgressEl);
    storageSetting.descEl.appendChild(syncProgressText);
    this.refreshStorageDisplay();
  }
  connectStatusIcon() {
    var _a;
    if (this.plugin.settings.connectedToBackend && ((_a = this.plugin.settings.userInfo) == null ? void 0 : _a.email))
      return "\u{1F7E2}";
    else if (this.plugin.settings.connectedToBackend)
      return "\u{1F7E1}";
    else
      return "\u{1F534}";
  }
  async refreshStorageDisplay() {
    if (!this.storageProgressEl || !this.storageProgressText)
      return;
    this.storageProgressEl.removeAttribute("value");
    this.storageProgressText.textContent = "Calculating...";
    try {
      const { calculateVaultSyncMetrics: calculateVaultSyncMetrics2 } = await Promise.resolve().then(() => (init_utils(), utils_exports));
      const metrics = await calculateVaultSyncMetrics2(this.app.vault, this.plugin.settings);
      const usedMB = metrics.usedBytes / (1024 * 1024);
      const totalMB = metrics.totalBytes / (1024 * 1024);
      const usedStr = `${usedMB.toFixed(1)} MB`;
      const totalStr = `${totalMB.toFixed(0)} MB`;
      this.storageProgressEl.value = metrics.usedBytes;
      this.storageProgressEl.max = metrics.totalBytes;
      this.storageProgressText.textContent = `${usedStr} / ${totalStr}`;
    } catch (err) {
      console.error("Khoj: Failed to update storage display", err);
      this.storageProgressText.textContent = "Estimation unavailable";
    }
  }
  async refreshModelsAndServerPreference() {
    let serverSelectedModelId = null;
    if (this.plugin.settings.connectedToBackend) {
      const [availableModels, serverConfig] = await Promise.all([
        fetchChatModels(this.plugin.settings),
        fetchUserServerSettings(this.plugin.settings)
      ]);
      this.plugin.settings.availableChatModels = availableModels;
      if (serverConfig && serverConfig.selected_chat_model_config !== void 0 && serverConfig.selected_chat_model_config !== null) {
        const serverModelIdStr = serverConfig.selected_chat_model_config.toString();
        if (this.plugin.settings.availableChatModels.some((m) => m.id === serverModelIdStr)) {
          serverSelectedModelId = serverModelIdStr;
        } else {
          console.warn(`Khoj: Server's selected model ID ${serverModelIdStr} not in available models. Falling back to default.`);
          serverSelectedModelId = null;
        }
      } else {
        serverSelectedModelId = null;
      }
      this.plugin.settings.selectedChatModelId = serverSelectedModelId;
    } else {
      this.plugin.settings.availableChatModels = [];
      this.plugin.settings.selectedChatModelId = null;
    }
    await this.plugin.saveSettings();
    this.renderChatModelDropdown();
  }
  renderChatModelDropdown() {
    if (!this.chatModelSetting) {
      this.chatModelSetting = new import_obsidian3.Setting(this.containerEl).setName("Chat Model");
    } else {
      this.chatModelSetting.descEl.empty();
      this.chatModelSetting.controlEl.empty();
    }
    const modelSetting = this.chatModelSetting;
    if (!this.plugin.settings.connectedToBackend) {
      modelSetting.setDesc("Connect to Khoj to load and set chat model options.");
      modelSetting.addText((text) => text.setValue("Not connected").setDisabled(true));
      return;
    }
    if (this.plugin.settings.availableChatModels.length === 0 && this.plugin.settings.connectedToBackend) {
      modelSetting.setDesc("Fetching models or no models available. Check Khoj connection or try refreshing.");
      modelSetting.addButton((button) => button.setButtonText("Refresh Models").onClick(async () => {
        button.setButtonText("Refreshing...").setDisabled(true);
        await this.refreshModelsAndServerPreference();
      }));
      return;
    }
    modelSetting.setDesc("The default AI model used for chat.");
    modelSetting.addDropdown((dropdown) => {
      dropdown.addOption("", "Default");
      this.plugin.settings.availableChatModels.forEach((model) => {
        dropdown.addOption(model.id, model.name);
      });
      dropdown.setValue(this.plugin.settings.selectedChatModelId || "").onChange(async (value) => {
        const success = await updateServerChatModel(value, this.plugin.settings);
        if (success) {
          await this.plugin.saveSettings();
        } else {
          dropdown.setValue(this.plugin.settings.selectedChatModelId || "");
        }
      });
    });
  }
  updateIncludeFolderList(containerEl) {
    this.updateFolderList(containerEl, this.plugin.settings.syncFolders, "Including entire vault", async (folder) => {
      this.plugin.settings.syncFolders = this.plugin.settings.syncFolders.filter((f) => f !== folder);
      await this.plugin.saveSettings();
      this.updateIncludeFolderList(containerEl);
      this.refreshStorageDisplay();
    });
  }
  updateExcludeFolderList(containerEl) {
    this.updateFolderList(containerEl, this.plugin.settings.excludeFolders, "No folders excluded", async (folder) => {
      this.plugin.settings.excludeFolders = this.plugin.settings.excludeFolders.filter((f) => f !== folder);
      await this.plugin.saveSettings();
      this.updateExcludeFolderList(containerEl);
      this.refreshStorageDisplay();
    });
  }
  updateFolderList(containerEl, folders, emptyText, onRemove) {
    containerEl.empty();
    if (folders.length === 0) {
      containerEl.createEl("div", {
        text: emptyText,
        cls: "folder-list-empty"
      });
      return;
    }
    const list = containerEl.createEl("ul", { cls: "folder-list" });
    folders.forEach((folder) => {
      const item = list.createEl("li", { cls: "folder-list-item" });
      item.createSpan({ text: folder });
      const removeButton = item.createEl("button", {
        cls: "folder-list-remove",
        text: "\xD7"
      });
      removeButton.addEventListener("click", () => onRemove(folder));
    });
  }
};
var FolderSuggestModal = class extends import_obsidian3.SuggestModal {
  constructor(app, onChoose) {
    super(app);
    this.onChoose = onChoose;
  }
  getSuggestions(query) {
    const folders = this.getAllFolders();
    if (!query)
      return folders;
    return folders.filter((folder) => folder.toLowerCase().includes(query.toLowerCase()));
  }
  renderSuggestion(folder, el) {
    el.createSpan({
      text: folder || "/",
      cls: "folder-suggest-item"
    });
  }
  onChooseSuggestion(folder, _) {
    this.onChoose(folder);
  }
  getAllFolders() {
    const folders = /* @__PURE__ */ new Set();
    folders.add("");
    this.app.vault.getAllLoadedFiles().forEach((file) => {
      var _a;
      const folderPath = (_a = file.parent) == null ? void 0 : _a.path;
      if (folderPath) {
        folders.add(folderPath);
        let parent = folderPath;
        while (parent.includes("/")) {
          parent = parent.substring(0, parent.lastIndexOf("/"));
          folders.add(parent);
        }
      }
    });
    return Array.from(folders).sort();
  }
};

// src/main.ts
init_search_modal();

// src/chat_view.ts
var import_obsidian6 = require("obsidian");
var DOMPurify = __toESM(require_browser());

// src/pane_view.ts
var import_obsidian4 = require("obsidian");
init_utils();
var KhojPaneView = class extends import_obsidian4.ItemView {
  constructor(leaf, plugin) {
    super(leaf);
    this.setting = plugin.settings;
    this.plugin = plugin;
  }
  async onOpen() {
    var _a, _b, _c;
    let { contentEl } = this;
    let headerEl = contentEl.createDiv({ attr: { id: "khoj-header", class: "khoj-header" } });
    const viewType = this.getViewType();
    await populateHeaderPane(headerEl, this.setting, viewType);
    if (viewType === "khoj-chat-view" /* CHAT */) {
      (_a = headerEl.querySelector(".chat-nav")) == null ? void 0 : _a.classList.add("khoj-nav-selected");
    } else if (viewType === "khoj-similar-view" /* SIMILAR */) {
      (_b = headerEl.querySelector(".similar-nav")) == null ? void 0 : _b.classList.add("khoj-nav-selected");
    }
    let similarNavSvgEl = (_c = headerEl.getElementsByClassName("khoj-nav-icon-similar")[0]) == null ? void 0 : _c.firstElementChild;
    if (!!similarNavSvgEl)
      similarNavSvgEl.id = "similar-nav-icon-svg";
  }
  async activateView(viewType) {
    const { workspace } = this.app;
    let leaf = null;
    const leaves = workspace.getLeavesOfType(viewType);
    if (leaves.length > 0) {
      leaf = leaves[0];
    } else {
      leaf = workspace.getRightLeaf(false);
      await (leaf == null ? void 0 : leaf.setViewState({ type: viewType, active: true }));
    }
    if (leaf) {
      if (viewType === "khoj-chat-view" /* CHAT */) {
        let chatInput = this.contentEl.getElementsByClassName("khoj-chat-input")[0];
        if (chatInput)
          chatInput.focus();
      }
      workspace.revealLeaf(leaf);
    }
  }
};

// src/chat_view.ts
init_utils();
init_search_modal();

// src/interact_with_files.ts
var import_obsidian5 = require("obsidian");

// node_modules/diff/libesm/diff/base.js
var Diff = class {
  diff(oldStr, newStr, options = {}) {
    let callback;
    if (typeof options === "function") {
      callback = options;
      options = {};
    } else if ("callback" in options) {
      callback = options.callback;
    }
    const oldString = this.castInput(oldStr, options);
    const newString = this.castInput(newStr, options);
    const oldTokens = this.removeEmpty(this.tokenize(oldString, options));
    const newTokens = this.removeEmpty(this.tokenize(newString, options));
    return this.diffWithOptionsObj(oldTokens, newTokens, options, callback);
  }
  diffWithOptionsObj(oldTokens, newTokens, options, callback) {
    var _a;
    const done = (value) => {
      value = this.postProcess(value, options);
      if (callback) {
        setTimeout(function() {
          callback(value);
        }, 0);
        return void 0;
      } else {
        return value;
      }
    };
    const newLen = newTokens.length, oldLen = oldTokens.length;
    let editLength = 1;
    let maxEditLength = newLen + oldLen;
    if (options.maxEditLength != null) {
      maxEditLength = Math.min(maxEditLength, options.maxEditLength);
    }
    const maxExecutionTime = (_a = options.timeout) !== null && _a !== void 0 ? _a : Infinity;
    const abortAfterTimestamp = Date.now() + maxExecutionTime;
    const bestPath = [{ oldPos: -1, lastComponent: void 0 }];
    let newPos = this.extractCommon(bestPath[0], newTokens, oldTokens, 0, options);
    if (bestPath[0].oldPos + 1 >= oldLen && newPos + 1 >= newLen) {
      return done(this.buildValues(bestPath[0].lastComponent, newTokens, oldTokens));
    }
    let minDiagonalToConsider = -Infinity, maxDiagonalToConsider = Infinity;
    const execEditLength = () => {
      for (let diagonalPath = Math.max(minDiagonalToConsider, -editLength); diagonalPath <= Math.min(maxDiagonalToConsider, editLength); diagonalPath += 2) {
        let basePath;
        const removePath = bestPath[diagonalPath - 1], addPath = bestPath[diagonalPath + 1];
        if (removePath) {
          bestPath[diagonalPath - 1] = void 0;
        }
        let canAdd = false;
        if (addPath) {
          const addPathNewPos = addPath.oldPos - diagonalPath;
          canAdd = addPath && 0 <= addPathNewPos && addPathNewPos < newLen;
        }
        const canRemove = removePath && removePath.oldPos + 1 < oldLen;
        if (!canAdd && !canRemove) {
          bestPath[diagonalPath] = void 0;
          continue;
        }
        if (!canRemove || canAdd && removePath.oldPos < addPath.oldPos) {
          basePath = this.addToPath(addPath, true, false, 0, options);
        } else {
          basePath = this.addToPath(removePath, false, true, 1, options);
        }
        newPos = this.extractCommon(basePath, newTokens, oldTokens, diagonalPath, options);
        if (basePath.oldPos + 1 >= oldLen && newPos + 1 >= newLen) {
          return done(this.buildValues(basePath.lastComponent, newTokens, oldTokens)) || true;
        } else {
          bestPath[diagonalPath] = basePath;
          if (basePath.oldPos + 1 >= oldLen) {
            maxDiagonalToConsider = Math.min(maxDiagonalToConsider, diagonalPath - 1);
          }
          if (newPos + 1 >= newLen) {
            minDiagonalToConsider = Math.max(minDiagonalToConsider, diagonalPath + 1);
          }
        }
      }
      editLength++;
    };
    if (callback) {
      (function exec() {
        setTimeout(function() {
          if (editLength > maxEditLength || Date.now() > abortAfterTimestamp) {
            return callback(void 0);
          }
          if (!execEditLength()) {
            exec();
          }
        }, 0);
      })();
    } else {
      while (editLength <= maxEditLength && Date.now() <= abortAfterTimestamp) {
        const ret = execEditLength();
        if (ret) {
          return ret;
        }
      }
    }
  }
  addToPath(path, added, removed, oldPosInc, options) {
    const last = path.lastComponent;
    if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
      return {
        oldPos: path.oldPos + oldPosInc,
        lastComponent: { count: last.count + 1, added, removed, previousComponent: last.previousComponent }
      };
    } else {
      return {
        oldPos: path.oldPos + oldPosInc,
        lastComponent: { count: 1, added, removed, previousComponent: last }
      };
    }
  }
  extractCommon(basePath, newTokens, oldTokens, diagonalPath, options) {
    const newLen = newTokens.length, oldLen = oldTokens.length;
    let oldPos = basePath.oldPos, newPos = oldPos - diagonalPath, commonCount = 0;
    while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(oldTokens[oldPos + 1], newTokens[newPos + 1], options)) {
      newPos++;
      oldPos++;
      commonCount++;
      if (options.oneChangePerToken) {
        basePath.lastComponent = { count: 1, previousComponent: basePath.lastComponent, added: false, removed: false };
      }
    }
    if (commonCount && !options.oneChangePerToken) {
      basePath.lastComponent = { count: commonCount, previousComponent: basePath.lastComponent, added: false, removed: false };
    }
    basePath.oldPos = oldPos;
    return newPos;
  }
  equals(left, right, options) {
    if (options.comparator) {
      return options.comparator(left, right);
    } else {
      return left === right || !!options.ignoreCase && left.toLowerCase() === right.toLowerCase();
    }
  }
  removeEmpty(array) {
    const ret = [];
    for (let i = 0; i < array.length; i++) {
      if (array[i]) {
        ret.push(array[i]);
      }
    }
    return ret;
  }
  castInput(value, options) {
    return value;
  }
  tokenize(value, options) {
    return Array.from(value);
  }
  join(chars) {
    return chars.join("");
  }
  postProcess(changeObjects, options) {
    return changeObjects;
  }
  get useLongestToken() {
    return false;
  }
  buildValues(lastComponent, newTokens, oldTokens) {
    const components = [];
    let nextComponent;
    while (lastComponent) {
      components.push(lastComponent);
      nextComponent = lastComponent.previousComponent;
      delete lastComponent.previousComponent;
      lastComponent = nextComponent;
    }
    components.reverse();
    const componentLen = components.length;
    let componentPos = 0, newPos = 0, oldPos = 0;
    for (; componentPos < componentLen; componentPos++) {
      const component = components[componentPos];
      if (!component.removed) {
        if (!component.added && this.useLongestToken) {
          let value = newTokens.slice(newPos, newPos + component.count);
          value = value.map(function(value2, i) {
            const oldValue = oldTokens[oldPos + i];
            return oldValue.length > value2.length ? oldValue : value2;
          });
          component.value = this.join(value);
        } else {
          component.value = this.join(newTokens.slice(newPos, newPos + component.count));
        }
        newPos += component.count;
        if (!component.added) {
          oldPos += component.count;
        }
      } else {
        component.value = this.join(oldTokens.slice(oldPos, oldPos + component.count));
        oldPos += component.count;
      }
    }
    return components;
  }
};

// node_modules/diff/libesm/util/string.js
function longestCommonPrefix(str1, str2) {
  let i;
  for (i = 0; i < str1.length && i < str2.length; i++) {
    if (str1[i] != str2[i]) {
      return str1.slice(0, i);
    }
  }
  return str1.slice(0, i);
}
function longestCommonSuffix(str1, str2) {
  let i;
  if (!str1 || !str2 || str1[str1.length - 1] != str2[str2.length - 1]) {
    return "";
  }
  for (i = 0; i < str1.length && i < str2.length; i++) {
    if (str1[str1.length - (i + 1)] != str2[str2.length - (i + 1)]) {
      return str1.slice(-i);
    }
  }
  return str1.slice(-i);
}
function replacePrefix(string, oldPrefix, newPrefix) {
  if (string.slice(0, oldPrefix.length) != oldPrefix) {
    throw Error(`string ${JSON.stringify(string)} doesn't start with prefix ${JSON.stringify(oldPrefix)}; this is a bug`);
  }
  return newPrefix + string.slice(oldPrefix.length);
}
function replaceSuffix(string, oldSuffix, newSuffix) {
  if (!oldSuffix) {
    return string + newSuffix;
  }
  if (string.slice(-oldSuffix.length) != oldSuffix) {
    throw Error(`string ${JSON.stringify(string)} doesn't end with suffix ${JSON.stringify(oldSuffix)}; this is a bug`);
  }
  return string.slice(0, -oldSuffix.length) + newSuffix;
}
function removePrefix(string, oldPrefix) {
  return replacePrefix(string, oldPrefix, "");
}
function removeSuffix(string, oldSuffix) {
  return replaceSuffix(string, oldSuffix, "");
}
function maximumOverlap(string1, string2) {
  return string2.slice(0, overlapCount(string1, string2));
}
function overlapCount(a, b) {
  let startA = 0;
  if (a.length > b.length) {
    startA = a.length - b.length;
  }
  let endB = b.length;
  if (a.length < b.length) {
    endB = a.length;
  }
  const map = Array(endB);
  let k = 0;
  map[0] = 0;
  for (let j = 1; j < endB; j++) {
    if (b[j] == b[k]) {
      map[j] = map[k];
    } else {
      map[j] = k;
    }
    while (k > 0 && b[j] != b[k]) {
      k = map[k];
    }
    if (b[j] == b[k]) {
      k++;
    }
  }
  k = 0;
  for (let i = startA; i < a.length; i++) {
    while (k > 0 && a[i] != b[k]) {
      k = map[k];
    }
    if (a[i] == b[k]) {
      k++;
    }
  }
  return k;
}
function trailingWs(string) {
  let i;
  for (i = string.length - 1; i >= 0; i--) {
    if (!string[i].match(/\s/)) {
      break;
    }
  }
  return string.substring(i + 1);
}
function leadingWs(string) {
  const match = string.match(/^\s*/);
  return match ? match[0] : "";
}

// node_modules/diff/libesm/diff/word.js
var extendedWordChars = "a-zA-Z0-9_\\u{AD}\\u{C0}-\\u{D6}\\u{D8}-\\u{F6}\\u{F8}-\\u{2C6}\\u{2C8}-\\u{2D7}\\u{2DE}-\\u{2FF}\\u{1E00}-\\u{1EFF}";
var tokenizeIncludingWhitespace = new RegExp(`[${extendedWordChars}]+|\\s+|[^${extendedWordChars}]`, "ug");
var WordDiff = class extends Diff {
  equals(left, right, options) {
    if (options.ignoreCase) {
      left = left.toLowerCase();
      right = right.toLowerCase();
    }
    return left.trim() === right.trim();
  }
  tokenize(value, options = {}) {
    let parts;
    if (options.intlSegmenter) {
      const segmenter = options.intlSegmenter;
      if (segmenter.resolvedOptions().granularity != "word") {
        throw new Error('The segmenter passed must have a granularity of "word"');
      }
      parts = [];
      for (const segmentObj of Array.from(segmenter.segment(value))) {
        const segment = segmentObj.segment;
        if (parts.length && /\s/.test(parts[parts.length - 1]) && /\s/.test(segment)) {
          parts[parts.length - 1] += segment;
        } else {
          parts.push(segment);
        }
      }
    } else {
      parts = value.match(tokenizeIncludingWhitespace) || [];
    }
    const tokens = [];
    let prevPart = null;
    parts.forEach((part) => {
      if (/\s/.test(part)) {
        if (prevPart == null) {
          tokens.push(part);
        } else {
          tokens.push(tokens.pop() + part);
        }
      } else if (prevPart != null && /\s/.test(prevPart)) {
        if (tokens[tokens.length - 1] == prevPart) {
          tokens.push(tokens.pop() + part);
        } else {
          tokens.push(prevPart + part);
        }
      } else {
        tokens.push(part);
      }
      prevPart = part;
    });
    return tokens;
  }
  join(tokens) {
    return tokens.map((token, i) => {
      if (i == 0) {
        return token;
      } else {
        return token.replace(/^\s+/, "");
      }
    }).join("");
  }
  postProcess(changes, options) {
    if (!changes || options.oneChangePerToken) {
      return changes;
    }
    let lastKeep = null;
    let insertion = null;
    let deletion = null;
    changes.forEach((change) => {
      if (change.added) {
        insertion = change;
      } else if (change.removed) {
        deletion = change;
      } else {
        if (insertion || deletion) {
          dedupeWhitespaceInChangeObjects(lastKeep, deletion, insertion, change);
        }
        lastKeep = change;
        insertion = null;
        deletion = null;
      }
    });
    if (insertion || deletion) {
      dedupeWhitespaceInChangeObjects(lastKeep, deletion, insertion, null);
    }
    return changes;
  }
};
var wordDiff = new WordDiff();
function diffWords(oldStr, newStr, options) {
  if ((options === null || options === void 0 ? void 0 : options.ignoreWhitespace) != null && !options.ignoreWhitespace) {
    return diffWordsWithSpace(oldStr, newStr, options);
  }
  return wordDiff.diff(oldStr, newStr, options);
}
function dedupeWhitespaceInChangeObjects(startKeep, deletion, insertion, endKeep) {
  if (deletion && insertion) {
    const oldWsPrefix = leadingWs(deletion.value);
    const oldWsSuffix = trailingWs(deletion.value);
    const newWsPrefix = leadingWs(insertion.value);
    const newWsSuffix = trailingWs(insertion.value);
    if (startKeep) {
      const commonWsPrefix = longestCommonPrefix(oldWsPrefix, newWsPrefix);
      startKeep.value = replaceSuffix(startKeep.value, newWsPrefix, commonWsPrefix);
      deletion.value = removePrefix(deletion.value, commonWsPrefix);
      insertion.value = removePrefix(insertion.value, commonWsPrefix);
    }
    if (endKeep) {
      const commonWsSuffix = longestCommonSuffix(oldWsSuffix, newWsSuffix);
      endKeep.value = replacePrefix(endKeep.value, newWsSuffix, commonWsSuffix);
      deletion.value = removeSuffix(deletion.value, commonWsSuffix);
      insertion.value = removeSuffix(insertion.value, commonWsSuffix);
    }
  } else if (insertion) {
    if (startKeep) {
      const ws = leadingWs(insertion.value);
      insertion.value = insertion.value.substring(ws.length);
    }
    if (endKeep) {
      const ws = leadingWs(endKeep.value);
      endKeep.value = endKeep.value.substring(ws.length);
    }
  } else if (startKeep && endKeep) {
    const newWsFull = leadingWs(endKeep.value), delWsStart = leadingWs(deletion.value), delWsEnd = trailingWs(deletion.value);
    const newWsStart = longestCommonPrefix(newWsFull, delWsStart);
    deletion.value = removePrefix(deletion.value, newWsStart);
    const newWsEnd = longestCommonSuffix(removePrefix(newWsFull, newWsStart), delWsEnd);
    deletion.value = removeSuffix(deletion.value, newWsEnd);
    endKeep.value = replacePrefix(endKeep.value, newWsFull, newWsEnd);
    startKeep.value = replaceSuffix(startKeep.value, newWsFull, newWsFull.slice(0, newWsFull.length - newWsEnd.length));
  } else if (endKeep) {
    const endKeepWsPrefix = leadingWs(endKeep.value);
    const deletionWsSuffix = trailingWs(deletion.value);
    const overlap = maximumOverlap(deletionWsSuffix, endKeepWsPrefix);
    deletion.value = removeSuffix(deletion.value, overlap);
  } else if (startKeep) {
    const startKeepWsSuffix = trailingWs(startKeep.value);
    const deletionWsPrefix = leadingWs(deletion.value);
    const overlap = maximumOverlap(startKeepWsSuffix, deletionWsPrefix);
    deletion.value = removePrefix(deletion.value, overlap);
  }
}
var WordsWithSpaceDiff = class extends Diff {
  tokenize(value) {
    const regex = new RegExp(`(\\r?\\n)|[${extendedWordChars}]+|[^\\S\\n\\r]+|[^${extendedWordChars}]`, "ug");
    return value.match(regex) || [];
  }
};
var wordsWithSpaceDiff = new WordsWithSpaceDiff();
function diffWordsWithSpace(oldStr, newStr, options) {
  return wordsWithSpaceDiff.diff(oldStr, newStr, options);
}

// src/interact_with_files.ts
var FileInteractions = class {
  constructor(app) {
    this.EDIT_BLOCK_START = "<khoj_edit>";
    this.EDIT_BLOCK_END = "</khoj_edit>";
    this.CONTEXT_FILES_LIMIT = 3;
    this.app = app;
  }
  getRecentActiveMarkdownFiles(N) {
    const seen = /* @__PURE__ */ new Set();
    const recentActiveFiles = this.app.workspace.getLeavesOfType("markdown").sort((a, b) => b.activeTime - a.activeTime).map((leaf) => {
      var _a;
      return (_a = leaf.view) == null ? void 0 : _a.file;
    }).filter((file) => {
      if (!file || seen.has(file.path))
        return false;
      seen.add(file.path);
      return true;
    }).slice(0, N);
    console.log(`Using ${recentActiveFiles.length} recently viewed md files for context: ${recentActiveFiles.map((file) => file.path).join(", ")}`);
    return recentActiveFiles;
  }
  async getOpenFilesContent(fileAccessMode) {
    if (fileAccessMode === "none")
      return "";
    const recentFiles = this.getRecentActiveMarkdownFiles(this.CONTEXT_FILES_LIMIT);
    if (recentFiles.length === 0)
      return "";
    let editInstructions = "";
    if (fileAccessMode === "write") {
      editInstructions = `
If the user requests, you can suggest edits to files provided in the WORKING_FILE_SET provided below.
Once you understand the user request you MUST:

1. Decide if you need to propose *SEARCH/REPLACE* edits to any files that haven't been added to the chat.

If you need to propose edits to existing files not already added to the chat, you *MUST* tell the user their full path names and ask them to *add the files to the chat*.
End your reply and wait for their approval.
You can keep asking if you then decide you need to edit more files.

2. Think step-by-step and explain the needed changes in a few short sentences before each EDIT block.

3. Describe each change with a *SEARCH/REPLACE block* like the examples below.

All changes to files must use this *SEARCH/REPLACE block* format.
ONLY EVER RETURN EDIT TEXT IN A *SEARCH/REPLACE BLOCK*!

# *SEARCH/REPLACE block* Rules:

Every *SEARCH/REPLACE block* must use this format:
1. The opening fence: \`${this.EDIT_BLOCK_START}\`
2. The *FULL* file path alone on a line, verbatim. No bold asterisks, no quotes around it, no escaping of characters, etc.
3. The start of search block: <<<<<<< SEARCH
4. A contiguous chunk of lines to search for in the source file
5. The dividing line: =======
6. The lines to replace into the source file
7. The end of the replace block: >>>>>>> REPLACE
8. The closing fence: \`${this.EDIT_BLOCK_END}\`

Use the *FULL* file path, as shown to you by the user.

Every *SEARCH* section must *EXACTLY MATCH* the existing file content, character for character, including all comments, docstrings, etc.
If the file contains code or other data wrapped/escaped in json/xml/quotes or other containers, you need to propose edits to the literal contents of the file, including the container markup.

*SEARCH/REPLACE* blocks will *only* replace the first match occurrence.
Including multiple unique *SEARCH/REPLACE* blocks if needed.
Include enough lines in each SEARCH section to uniquely match each set of lines that need to change.

Keep *SEARCH/REPLACE* blocks concise.
Break large *SEARCH/REPLACE* blocks into a series of smaller blocks that each change a small portion of the file.
Include just the changing lines, and a few surrounding lines if needed for uniqueness.
Do not include long runs of unchanging lines in *SEARCH/REPLACE* blocks.

Only create *SEARCH/REPLACE* blocks for files that the user has added to the chat!

To move text within a file, use 2 *SEARCH/REPLACE* blocks: 1 to delete it from its current location, 1 to insert it in the new location.

Pay attention to which filenames the user wants you to edit, especially if they are asking you to create a new file.

If you want to put text in a new file, use a *SEARCH/REPLACE block* with:
- A new file path, including dir name if needed
- An empty \`SEARCH\` section
- The new file's contents in the \`REPLACE\` section

ONLY EVER RETURN EDIT TEXT IN A *SEARCH/REPLACE BLOCK*!

<EDIT_INSTRUCTIONS>
Suggest edits using targeted modifications. Use multiple edit blocks to make precise changes rather than rewriting entire sections.

Here's how to use the *SEARCH/REPLACE block* format:

${this.EDIT_BLOCK_START}
target-filename
<<<<<<< SEARCH
from flask import Flask
=======
import math
from flask import Flask
>>>>>>> REPLACE
${this.EDIT_BLOCK_END}

\u26A0\uFE0F Important:
- The target-filename parameter is required and must match an open file name.
- The XML format ${this.EDIT_BLOCK_START}...${this.EDIT_BLOCK_END} ensures reliable parsing.
- The SEARCH block content must completely and uniquely identify the section to edit.
- The REPLACE block content will replace the first SEARCH block match in the specified \`target-filename\`.

\u{1F4DD} Example note:

\`\`\`
---
date: 2024-01-20
tags: meeting, planning
status: active
---
# file: Meeting Notes.md

Action items from today:
- Review Q4 metrics
- Schedule follow-up with marketing team about new campaign launch
- Update project timeline and milestones for Q1 2024

Next steps:
- Send summary to team
- Book conference room for next week
\`\`\`

Examples of targeted edits:

1. Using just a few words to identify long text (notice how "campaign launch" is kept in content):

Add deadline and specificity to the marketing team follow-up.
${this.EDIT_BLOCK_START}
Meeting Notes.md
<<<<<<< SEARCH
- Schedule follow-up with marketing team about new campaign launch
=======
- Schedule follow-up with marketing team by Wednesday to discuss Q1 campaign launch
>>>>>>> REPLACE
${this.EDIT_BLOCK_END}

2. Multiple targeted changes with escaped characters:

Add HIGH priority flag with code reference to Q4 metrics review"
${this.EDIT_BLOCK_START}
Meeting Notes.md
<<<<<<< SEARCH
- Review Q4 metrics
=======
- [HIGH] Review Q4 metrics (see "metrics.ts" and \`calculateQ4Metrics()\`)
>>>>>>> REPLACE
</${this.EDIT_BLOCK_END}>

Add resource allocation to project timeline task
<${this.EDIT_BLOCK_START}>
Meeting Notes.md
<<<<<<< SEARCH
- Update project timeline and milestones for Q1 2024
=======
- Update project timeline and add resource allocation for Q1 2024
>>>>>>> REPLACE
</${this.EDIT_BLOCK_END}>

3. Adding new content between sections:
Insert a new section for discussion points after the action items section:
${this.EDIT_BLOCK_START}
Meeting Notes.md
<<<<<<< SEARCH
Action items from today:
- Review Q4 metrics
- Schedule follow-up with marketing team about new campaign launch
- Update project timeline and milestones for Q1 2024
=======
Action items from today:
- Review Q4 metrics
- Schedule follow-up
- Update timeline

Discussion Points:
- Budget review
- Team feedback
>>>>>>> REPLACE
</${this.EDIT_BLOCK_END}>

4. Completely replacing a file content (preserving frontmatter):
Replace entire file content while keeping frontmatter metadata
${this.EDIT_BLOCK_START}
Meeting Notes.md
<<<<<<< SEARCH
=======
# Project Overview

## Goals
- Increase user engagement by 25%
- Launch mobile app by Q3
- Expand to 3 new markets

## Timeline
1. Q1: Research & Planning
2. Q2: Development
3. Q3: Testing & Launch
4. Q4: Market Expansion
>>>>>>> REPLACE
${this.EDIT_BLOCK_END}

- The SEARCH block must uniquely identify the section to edit
- The REPLACE block content replaces the first SEARCH block match in the specified file
- Frontmatter metadata (between --- markers at top of file) cannot be modified
- Use an empty SEARCH block to replace entire file content with content in REPLACE block (while preserving frontmatter).
- Remember to escape special characters: use " for quotes in content
- Each edit block must be fenced in ${this.EDIT_BLOCK_START}...${this.EDIT_BLOCK_END} XML tags

</EDIT_INSTRUCTIONS>
`;
    }
    let openFilesContent = `
For context, the user is currently working on the following files:
<WORKING_FILE_SET>

`;
    for (const file of recentFiles) {
      let fileContent;
      try {
        fileContent = await this.app.vault.read(file);
      } catch (error) {
        console.error(`Error reading file ${file.path}:`, error);
        continue;
      }
      openFilesContent += `<OPEN_FILE>
# file: ${file.basename}.md

${fileContent}
</OPEN_FILE>

`;
    }
    openFilesContent += "</WORKING_FILE_SET>\n";
    let context;
    if (fileAccessMode === "write") {
      context = `

<SYSTEM>${editInstructions + openFilesContent}</SYSTEM>`;
    } else {
      context = `

<SYSTEM>${openFilesContent}</SYSTEM>`;
    }
    return context;
  }
  levenshteinDistance(a, b) {
    if (a.length === 0)
      return b.length;
    if (b.length === 0)
      return a.length;
    const matrix = Array(b.length + 1).fill(null).map(() => Array(a.length + 1).fill(null));
    for (let i = 0; i <= a.length; i++)
      matrix[0][i] = i;
    for (let j = 0; j <= b.length; j++)
      matrix[j][0] = j;
    for (let j = 1; j <= b.length; j++) {
      for (let i = 1; i <= a.length; i++) {
        const cost = a[i - 1] === b[j - 1] ? 0 : 1;
        matrix[j][i] = Math.min(matrix[j][i - 1] + 1, matrix[j - 1][i] + 1, matrix[j - 1][i - 1] + cost);
      }
    }
    return matrix[b.length][a.length];
  }
  findBestMatchingFile(targetName, files) {
    const MAX_DISTANCE = 10;
    let bestMatch = null;
    for (const file of files) {
      const distanceWithExt = this.levenshteinDistance(targetName.toLowerCase(), file.name.toLowerCase());
      const distanceWithoutExt = this.levenshteinDistance(targetName.toLowerCase(), file.basename.toLowerCase());
      const distance = Math.min(distanceWithExt, distanceWithoutExt);
      if (distance <= MAX_DISTANCE && (!bestMatch || distance < bestMatch.distance)) {
        bestMatch = { file, distance };
      }
    }
    return (bestMatch == null ? void 0 : bestMatch.file) || null;
  }
  parseEditBlock(content, isComplete = true) {
    let cleanContent = "";
    try {
      cleanContent = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "").trim();
      if (!isComplete) {
        const partialData = {
          file: "",
          find: "",
          replace: ""
        };
        const firstLineMatch = cleanContent.match(/^([^\n]+)/);
        if (firstLineMatch) {
          partialData.file = firstLineMatch[1].trim();
        }
        const searchStartMatch = cleanContent.match(/<<<<<<< SEARCH\n([\s\S]*)/);
        if (searchStartMatch) {
          partialData.find = searchStartMatch[1];
          if (!partialData.file) {
            const lines = cleanContent.split("\n");
            const searchIndex = lines.findIndex((line) => line.startsWith("<<<<<<< SEARCH"));
            if (searchIndex > 0) {
              partialData.file = lines[searchIndex - 1].trim();
            }
          }
        }
        return {
          editData: partialData,
          cleanContent,
          inProgress: true
        };
      }
      const newFormatRegex = /^([^\n]+)\n<<<<<<< SEARCH\n([\s\S]*?)\n=======\n([\s\S]*?)\n?>>>>>>> REPLACE\s*$/;
      const newFormatMatch = newFormatRegex.exec(cleanContent);
      let editData = null;
      if (newFormatMatch) {
        editData = {
          file: newFormatMatch[1].trim(),
          find: newFormatMatch[2],
          replace: newFormatMatch[3]
        };
      }
      let error = null;
      if (editData && !editData.file) {
        error = {
          type: "missing_field",
          message: 'Missing "file" field in edit block',
          details: 'The "file" field is required and should contain the target file name'
        };
      } else if (editData && (editData.find === void 0 || editData.find === null)) {
        error = {
          type: "missing_field",
          message: 'Missing "find" field markers',
          details: 'The "find" field is required. It should contain the content to find in the file or be empty for new files'
        };
      } else if (editData && editData.replace === void 0) {
        error = {
          type: "missing_field",
          message: 'Missing "replace" field in edit block',
          details: 'The "replace" field is required. It should contain the content to replace or be empty to indicate deletion'
        };
      }
      return error ? { editData, cleanContent, error } : { editData, cleanContent };
    } catch (error) {
      console.error("Error parsing edit block:", error);
      console.error("Content causing error:", content);
      return {
        editData: null,
        cleanContent,
        error: {
          type: "invalid_format",
          message: "Invalid JSON format in edit block",
          details: error.message
        }
      };
    }
  }
  parseEditBlocks(message) {
    const editBlocks = [];
    const editBlockRegex = new RegExp(`${this.EDIT_BLOCK_START}([\\s\\S]*?)${this.EDIT_BLOCK_END}`, "g");
    let match;
    while ((match = editBlockRegex.exec(message)) !== null) {
      const { editData, cleanContent, error } = this.parseEditBlock(match[1]);
      if (error) {
        console.error("Failed to parse edit block:", error);
        console.debug("Content causing error:", match[1]);
        editBlocks.push({
          file: "unknown",
          find: "",
          replace: `Error: ${error.message}
Original content:
${match[1]}`,
          note: "Error parsing edit block",
          hasError: true,
          error
        });
        continue;
      }
      if (!editData) {
        console.debug("No edit data parsed");
        continue;
      }
      editBlocks.push({
        note: "Suggested edit",
        file: editData.file,
        find: editData.find,
        replace: editData.replace,
        hasError: !!error,
        error: error || void 0
      });
    }
    return editBlocks;
  }
  createPreviewWithDiff(originalText, newText) {
    const HIGHLIGHT_TOKEN = "___KHOJ_HIGHLIGHT_MARKER___";
    const STRIKETHROUGH_TOKEN = "___KHOJ_STRIKETHROUGH_MARKER___";
    const preserveFormatting = (text) => {
      let processed = text.replace(/==(.*?)==/g, `${HIGHLIGHT_TOKEN}$1${HIGHLIGHT_TOKEN}`);
      processed = processed.replace(/~~(.*?)~~/g, `${STRIKETHROUGH_TOKEN}$1${STRIKETHROUGH_TOKEN}`);
      return processed;
    };
    const restoreFormatting = (text) => {
      let processed = text.replace(new RegExp(HIGHLIGHT_TOKEN + "(.*?)" + HIGHLIGHT_TOKEN, "g"), "==$1==");
      processed = processed.replace(new RegExp(STRIKETHROUGH_TOKEN + "(.*?)" + STRIKETHROUGH_TOKEN, "g"), "~~$1~~");
      return processed;
    };
    const preservedOriginal = preserveFormatting(originalText);
    const preservedNew = preserveFormatting(newText);
    let prefixLength = 0;
    const minLength = Math.min(preservedOriginal.length, preservedNew.length);
    while (prefixLength < minLength && preservedOriginal[prefixLength] === preservedNew[prefixLength]) {
      prefixLength++;
    }
    let suffixLength = 0;
    while (suffixLength < minLength - prefixLength && preservedOriginal[preservedOriginal.length - 1 - suffixLength] === preservedNew[preservedNew.length - 1 - suffixLength]) {
      suffixLength++;
    }
    const commonPrefix = preservedOriginal.slice(0, prefixLength);
    const commonSuffix = preservedOriginal.slice(preservedOriginal.length - suffixLength);
    const originalDiff = preservedOriginal.slice(prefixLength, preservedOriginal.length - suffixLength);
    const newDiff = preservedNew.slice(prefixLength, preservedNew.length - suffixLength);
    const formatLines = (text, marker) => {
      if (!text)
        return "";
      return text.split("\n").map((line) => {
        line = line.trim();
        if (!line) {
          return marker === "==" ? "" : "~~";
        }
        return `${marker}${line}${marker}`;
      }).filter((line) => line !== "~~").join("\n");
    };
    const diffPreview = commonPrefix + (originalDiff ? formatLines(originalDiff, "~~") : "") + (newDiff ? formatLines(newDiff, "==") : "") + commonSuffix;
    return restoreFormatting(diffPreview);
  }
  textNormalize(text) {
    return text.replace(/\u00A0/g, " ").replace(/[\u2002\u2003\u2007\u2008\u2009\u200A\u205F\u3000]/g, " ").replace(/[\u2013\u2014]/g, "-").replace(/[\u2018\u2019]/g, "'").replace(/[\u201C\u201D]/g, '"').replace(/\u2026/g, "...").normalize("NFC");
  }
  processSingleEdit(rawFindText, replaceText, rawCurrentFileContent, frontmatterEndIndex) {
    let startIndex = -1;
    let endIndex = -1;
    const findText = this.textNormalize(rawFindText);
    const currentFileContent = this.textNormalize(rawCurrentFileContent);
    if (findText === "") {
      startIndex = frontmatterEndIndex;
      endIndex = currentFileContent.length;
    } else {
      startIndex = currentFileContent.indexOf(findText, frontmatterEndIndex);
      if (startIndex !== -1) {
        endIndex = startIndex + findText.length;
      }
    }
    if (startIndex === -1 || endIndex === -1 || startIndex > endIndex) {
      return {
        preview: "",
        newContent: currentFileContent,
        error: `No matching text found in file.`
      };
    }
    const textToReplace = currentFileContent.substring(startIndex, endIndex);
    const newText = replaceText.trim();
    const preview = this.createPreviewWithDiff(textToReplace, newText);
    const newContent = currentFileContent.substring(0, startIndex) + preview + currentFileContent.substring(endIndex);
    return { preview, newContent };
  }
  async applyEditBlocks(editBlocks, onRetryNeeded) {
    var _a;
    if (editBlocks.length === 0) {
      return { editResults: [], fileBackups: /* @__PURE__ */ new Map() };
    }
    const fileBackups = /* @__PURE__ */ new Map();
    const currentFileContents = /* @__PURE__ */ new Map();
    const files = this.getRecentActiveMarkdownFiles(this.CONTEXT_FILES_LIMIT);
    const editResults = [];
    const blocksNeedingRetry = [];
    const validationResults = [];
    for (const block of editBlocks) {
      try {
        if (block.hasError) {
          validationResults.push({
            block,
            valid: false,
            error: ((_a = block.error) == null ? void 0 : _a.message) || "Parsing error"
          });
          continue;
        }
        const targetFile = this.findBestMatchingFile(block.file, files);
        if (!targetFile) {
          validationResults.push({
            block,
            valid: false,
            error: `No matching file found for "${block.file}"`
          });
          continue;
        }
        if (!fileBackups.has(targetFile.path)) {
          const content = await this.app.vault.read(targetFile);
          fileBackups.set(targetFile.path, content);
          currentFileContents.set(targetFile.path, content);
        }
        const currentContent = currentFileContents.get(targetFile.path);
        const frontmatterMatch = currentContent.match(/^---\n[\s\S]*?\n---\n/);
        const frontmatterEndIndex = frontmatterMatch ? frontmatterMatch[0].length : 0;
        const processedEdit = this.processSingleEdit(block.find, block.replace, currentContent, frontmatterEndIndex);
        if (processedEdit.error) {
          validationResults.push({ block, valid: false, error: processedEdit.error });
          continue;
        }
        validationResults.push({ block, valid: true, targetFile });
        currentFileContents.set(targetFile.path, processedEdit.newContent);
      } catch (error) {
        validationResults.push({ block, valid: false, error: error.message });
      }
    }
    const allValid = validationResults.every((result) => result.valid);
    if (!allValid) {
      currentFileContents.clear();
      for (const result of validationResults) {
        if (!result.valid) {
          blocksNeedingRetry.push({
            ...result.block,
            hasError: true,
            error: {
              type: "invalid_format",
              message: result.error || "Validation failed",
              details: result.error || "Could not validate edit"
            }
          });
          editResults.push({
            block: result.block,
            success: false,
            error: result.error || "Validation failed"
          });
        } else {
          editResults.push({
            block: result.block,
            success: false,
            error: "Other edits in the group failed validation"
          });
        }
      }
      if (blocksNeedingRetry.length > 0 && onRetryNeeded) {
        onRetryNeeded(blocksNeedingRetry[0]);
      }
      return { editResults, fileBackups };
    }
    try {
      currentFileContents.clear();
      for (const [path, content] of fileBackups.entries()) {
        currentFileContents.set(path, content);
      }
      for (const result of validationResults) {
        const block = result.block;
        const targetFile = result.targetFile;
        const content = currentFileContents.get(targetFile.path);
        const frontmatterMatch = content.match(/^---\n[\s\S]*?\n---\n/);
        const frontmatterEndIndex = frontmatterMatch ? frontmatterMatch[0].length : 0;
        const processedEdit = this.processSingleEdit(block.find, block.replace, content, frontmatterEndIndex);
        if (processedEdit.error) {
          throw new Error(`Failed to re-locate edit markers for file "${targetFile.basename}" during application. Content may have shifted.`);
        }
        await this.app.vault.modify(targetFile, processedEdit.newContent);
        currentFileContents.set(targetFile.path, processedEdit.newContent);
        editResults.push({ block: { ...block, replace: processedEdit.preview }, success: true });
      }
    } catch (error) {
      console.error(`Error applying edits:`, error);
      for (const [path, content] of fileBackups.entries()) {
        const file = this.app.vault.getAbstractFileByPath(path);
        if (file && file instanceof import_obsidian5.TFile) {
          await this.app.vault.modify(file, content);
        }
      }
      for (const block of editBlocks) {
        blocksNeedingRetry.push(block);
        editResults.push({
          block,
          success: false,
          error: `Failed to apply edits: ${error.message}`
        });
      }
      if (blocksNeedingRetry.length > 0 && onRetryNeeded) {
        onRetryNeeded(blocksNeedingRetry[0]);
      }
    }
    return { editResults, fileBackups };
  }
  transformEditBlocks(message) {
    const files = this.app.workspace.getLeavesOfType("markdown").map((leaf) => {
      var _a;
      return (_a = leaf.view) == null ? void 0 : _a.file;
    }).filter((file) => file && file.extension === "md");
    const partialBlocks = this.detectPartialEditBlocks(message);
    let transformedMessage = message;
    for (const block of partialBlocks) {
      const isComplete = block.isComplete;
      const content = block.content;
      const { editData, cleanContent, error, inProgress } = this.parseEditBlock(content, isComplete);
      if (!editData && !error) {
        continue;
      }
      const diff = diffWords((editData == null ? void 0 : editData.find) || "", (editData == null ? void 0 : editData.replace) || "");
      let diffContent = diff.map((part) => {
        if (part.added) {
          return `<span class="cm-positive">${part.value}</span>`;
        } else if (part.removed) {
          return `<span class="cm-negative"><s>${part.value}</s></span>`;
        } else {
          return `<span>${part.value}</span>`;
        }
      }).join("").trim();
      let htmlRender = "";
      if (error) {
        console.error("Error parsing khoj-edit block:", error);
        console.error("Content causing error:", content);
        const errorTitle = `Error: ${(error == null ? void 0 : error.message) || "Parse error"}`;
        const errorDetails = `Failed to parse edit block. Please check the JSON format and ensure all required fields are present.`;
        htmlRender = `<details class="khoj-edit-accordion error">
                    <summary>${errorTitle}</summary>
                    <div class="khoj-edit-content">
                        <p class="khoj-edit-error-message">${errorDetails}</p>
                        <pre><code class="language-md error">${diffContent}</code></pre>
                    </div>
                </details>`;
      } else if (editData && inProgress) {
        htmlRender = `<details class="khoj-edit-accordion in-progress">
                    <summary>\u{1F4C4} ${editData.file} <span class="khoj-edit-status">In Progress</span></summary>
                    <div class="khoj-edit-content">
                        <pre><code class="language-md">${diffContent}</code></pre>
                    </div>
                </details>`;
      } else if (editData) {
        const targetFile = this.findBestMatchingFile(editData.file, files);
        const displayFileName = targetFile ? `${targetFile.basename}.${targetFile.extension}` : editData.file;
        htmlRender = `<details class="khoj-edit-accordion success">
                    <summary>\u{1F4C4} ${displayFileName}</summary>
                    <div class="khoj-edit-content">
                        <div>${diffContent}</div>
                    </div>
                </details>`;
      }
      if (isComplete) {
        transformedMessage = transformedMessage.replace(`${this.EDIT_BLOCK_START}${content}${this.EDIT_BLOCK_END}`, htmlRender);
      } else {
        transformedMessage = transformedMessage.replace(`${this.EDIT_BLOCK_START}${content}`, htmlRender);
      }
    }
    return transformedMessage;
  }
  detectPartialEditBlocks(message) {
    const results = [];
    const regex = new RegExp(`${this.EDIT_BLOCK_START}([\\s\\S]*?)(?:${this.EDIT_BLOCK_END}|$)`, "g");
    let match;
    while ((match = regex.exec(message)) !== null) {
      const content = match[1];
      const isComplete = match[0].endsWith(this.EDIT_BLOCK_END);
      results.push({
        content,
        isComplete
      });
    }
    return results;
  }
};

// src/chat_view.ts
var KhojChatView = class extends KhojPaneView {
  constructor(leaf, plugin) {
    var _a;
    super(leaf, plugin);
    this.location = { timezone: Intl.DateTimeFormat().resolvedOptions().timeZone };
    this.keyPressTimeout = null;
    this.userMessages = [];
    this.currentMessageIndex = -1;
    this.voiceChatActive = false;
    this.currentUserInput = "";
    this.startingMessage = this.getLearningMoment();
    this.agents = [];
    this.currentAgent = null;
    this.fileAccessMode = "read";
    this.chatModes = [
      { value: "default", label: "Default", iconName: "target", command: "/default" },
      { value: "general", label: "General", iconName: "message-circle", command: "/general" },
      { value: "notes", label: "Notes", iconName: "file-text", command: "/notes" },
      { value: "online", label: "Online", iconName: "globe", command: "/online" },
      { value: "code", label: "Code", iconName: "code", command: "/code" },
      { value: "image", label: "Image", iconName: "image", command: "/image" },
      { value: "research", label: "Research", iconName: "microscope", command: "/research" },
      { value: "operator", label: "Operator", iconName: "laptop", command: "/operator" }
    ];
    this.editRetryCount = 0;
    this.modeDropdown = null;
    this.selectedOptionIndex = -1;
    this.isStreaming = false;
    this.maxEditRetries = 1;
    this.fileInteractions = new FileInteractions(this.app);
    this.fileAccessMode = (_a = this.setting.fileAccessMode) != null ? _a : "read";
    this.waitingForLocation = true;
    fetch("https://ipapi.co/json").then((response) => response.json()).then((data) => {
      this.location = {
        region: data.region,
        city: data.city,
        countryName: data.country_name,
        countryCode: data.country_code,
        timezone: data.timezone
      };
    }).catch((err) => {
      console.log(err);
    }).finally(() => {
      this.waitingForLocation = false;
    });
    this.scope = new import_obsidian6.Scope(this.app.scope);
    this.scope.register(["Ctrl", "Alt"], "n", (_) => this.createNewConversation(this.currentAgent));
    this.scope.register(["Ctrl", "Alt"], "o", async (_) => await this.toggleChatSessions());
    this.scope.register(["Ctrl", "Alt"], "v", (_) => this.speechToText(this.voiceChatActive ? new KeyboardEvent("keyup") : new KeyboardEvent("keydown")));
    this.scope.register(["Ctrl"], "f", (_) => new KhojSearchModal(this.app, this.setting).open());
    this.scope.register(["Ctrl"], "r", (_) => {
      this.activateView("khoj-similar-view" /* SIMILAR */);
    });
  }
  getViewType() {
    return "khoj-chat-view" /* CHAT */;
  }
  getDisplayText() {
    return "Khoj Chat";
  }
  getIcon() {
    return "message-circle";
  }
  async chat(isVoice = false) {
    await this.cancelPendingEdits();
    let input_el = this.contentEl.getElementsByClassName("khoj-chat-input")[0];
    let user_message = input_el.value.trim();
    if (user_message) {
      const selectedMode = this.chatModes.find((mode) => this.contentEl.querySelector(`#khoj-mode-${mode.value}:checked`));
      const modeMatch = this.chatModes.find((mode) => user_message.startsWith(mode.command));
      let displayMessage = user_message;
      let apiMessage = user_message;
      if (modeMatch) {
        displayMessage = user_message.replace(modeMatch.command, `[${modeMatch.label}]`);
      } else if (selectedMode) {
        displayMessage = `[${selectedMode.label}] ${user_message}`;
        apiMessage = `${selectedMode.command} ${user_message}`;
      }
      this.userMessages.push(user_message);
      this.startingMessage = this.getLearningMoment();
      input_el.placeholder = this.startingMessage;
      input_el.value = "";
      this.autoResize();
      await this.getChatResponse(apiMessage, displayMessage, isVoice);
    }
  }
  async onOpen() {
    let { contentEl } = this;
    let defaultDomains = `'self' ${this.setting.khojUrl} https://*.obsidian.md https://app.khoj.dev https://assets.khoj.dev`;
    const defaultSrc = `default-src ${defaultDomains};`;
    const scriptSrc = `script-src ${defaultDomains} 'unsafe-inline';`;
    const connectSrc = `connect-src ${this.setting.khojUrl} wss://*.obsidian.md/ https://ipapi.co/json;`;
    const styleSrc = `style-src ${defaultDomains} 'unsafe-inline';`;
    const imgSrc = `img-src * app: data:;`;
    const childSrc = `child-src 'none';`;
    const objectSrc = `object-src 'none';`;
    const csp = `${defaultSrc} ${scriptSrc} ${connectSrc} ${styleSrc} ${imgSrc} ${childSrc} ${objectSrc}`;
    await super.onOpen();
    await this.fetchAgents();
    const headerAgentSelect = this.contentEl.querySelector(".khoj-header-agent-select");
    if (headerAgentSelect && this.agents.length > 0) {
      headerAgentSelect.innerHTML = "";
      headerAgentSelect.createEl("option", {
        text: "Default Agent",
        value: "khoj"
      });
      this.agents.forEach((agent) => {
        if (agent.slug === "khoj")
          return;
        const option = headerAgentSelect.createEl("option", {
          text: agent.name,
          value: agent.slug
        });
        if (agent.description) {
          option.title = agent.description;
        }
      });
      headerAgentSelect.addEventListener("change", (event) => {
        const select = event.target;
        this.currentAgent = select.value || null;
      });
    }
    contentEl.addClass("khoj-chat");
    let chatBodyEl = contentEl.createDiv({ attr: { id: "khoj-chat-body", class: "khoj-chat-body" } });
    let inputRow = contentEl.createDiv("khoj-input-row");
    let chatSessions = inputRow.createEl("button", {
      text: "Chat Sessions",
      attr: {
        class: "khoj-input-row-button clickable-icon",
        title: "Show Conversations (Ctrl+Alt+O)"
      }
    });
    chatSessions.addEventListener("click", async (_) => {
      await this.toggleChatSessions();
    });
    (0, import_obsidian6.setIcon)(chatSessions, "history");
    let fileAccessButton = inputRow.createEl("button", {
      text: "File Access",
      attr: {
        class: "khoj-input-row-button clickable-icon",
        title: "Toggle open file access"
      }
    });
    switch (this.fileAccessMode) {
      case "none":
        (0, import_obsidian6.setIcon)(fileAccessButton, "file-x");
        fileAccessButton.title = "Toggle open file access (No Access)";
        break;
      case "write":
        (0, import_obsidian6.setIcon)(fileAccessButton, "file-edit");
        fileAccessButton.title = "Toggle open file access (Read & Write)";
        break;
      case "read":
      default:
        (0, import_obsidian6.setIcon)(fileAccessButton, "file-search");
        fileAccessButton.title = "Toggle open file access (Read Only)";
        break;
    }
    fileAccessButton.addEventListener("click", async () => {
      switch (this.fileAccessMode) {
        case "none":
          this.fileAccessMode = "read";
          (0, import_obsidian6.setIcon)(fileAccessButton, "file-search");
          fileAccessButton.title = "Toggle open file access (Read Only)";
          break;
        case "read":
          this.fileAccessMode = "write";
          (0, import_obsidian6.setIcon)(fileAccessButton, "file-edit");
          fileAccessButton.title = "Toggle open file access (Read & Write)";
          break;
        case "write":
          this.fileAccessMode = "none";
          (0, import_obsidian6.setIcon)(fileAccessButton, "file-x");
          fileAccessButton.title = "Toggle open file access (No Access)";
          break;
      }
      this.setting.fileAccessMode = this.fileAccessMode;
      await this.plugin.saveSettings();
    });
    let chatInput = inputRow.createEl("textarea", {
      attr: {
        id: "khoj-chat-input",
        autofocus: "autofocus",
        class: "khoj-chat-input option"
      }
    });
    chatInput.addEventListener("input", (_) => {
      this.onChatInput();
    });
    chatInput.addEventListener("keydown", (event) => {
      this.incrementalChat(event);
      this.handleArrowKeys(event);
    });
    this.contentEl.addEventListener("keydown", this.handleKeyDown.bind(this));
    this.contentEl.addEventListener("keyup", this.handleKeyUp.bind(this));
    let transcribe = inputRow.createEl("button", {
      text: "Transcribe",
      attr: {
        id: "khoj-transcribe",
        class: "khoj-transcribe khoj-input-row-button clickable-icon ",
        title: "Hold to Voice Chat (Ctrl+Alt+V)"
      }
    });
    transcribe.addEventListener("mousedown", (event) => {
      this.startSpeechToText(event);
    });
    transcribe.addEventListener("mouseup", async (event) => {
      await this.stopSpeechToText(event);
    });
    transcribe.addEventListener("touchstart", async (event) => {
      await this.speechToText(event);
    });
    transcribe.addEventListener("touchend", async (event) => {
      await this.speechToText(event);
    });
    transcribe.addEventListener("touchcancel", async (event) => {
      await this.speechToText(event);
    });
    (0, import_obsidian6.setIcon)(transcribe, "mic");
    let send = inputRow.createEl("button", {
      text: "Send",
      attr: {
        id: "khoj-chat-send",
        class: "khoj-chat-send khoj-input-row-button clickable-icon"
      }
    });
    (0, import_obsidian6.setIcon)(send, "arrow-up-circle");
    let sendImg = send.getElementsByClassName("lucide-arrow-up-circle")[0];
    sendImg.addEventListener("click", async (_) => {
      await this.chat();
    });
    let getChatHistorySucessfully = await this.getChatHistory(chatBodyEl);
    let placeholderText = getChatHistorySucessfully ? this.startingMessage : "Configure Khoj to enable chat";
    chatInput.placeholder = placeholderText;
    chatInput.disabled = !getChatHistorySucessfully;
    this.autoResize();
    requestAnimationFrame(() => {
      requestAnimationFrame(() => {
        this.scrollChatToBottom();
        const chatInput2 = this.contentEl.getElementsByClassName("khoj-chat-input")[0];
        chatInput2 == null ? void 0 : chatInput2.focus();
      });
    });
  }
  startSpeechToText(event, timeout = 200) {
    if (!this.keyPressTimeout) {
      this.keyPressTimeout = setTimeout(async () => {
        if (this.sendMessageTimeout) {
          clearTimeout(this.sendMessageTimeout);
          const sendButton = this.contentEl.getElementsByClassName("khoj-chat-send")[0];
          (0, import_obsidian6.setIcon)(sendButton, "arrow-up-circle");
          let sendImg = sendButton.getElementsByClassName("lucide-arrow-up-circle")[0];
          sendImg.addEventListener("click", async (_) => {
            await this.chat();
          });
          const chatInput = this.contentEl.getElementsByClassName("khoj-chat-input")[0];
          chatInput.value = "";
        }
        await this.speechToText(event);
      }, timeout);
    }
  }
  async stopSpeechToText(event) {
    if (this.mediaRecorder) {
      await this.speechToText(event);
    }
    if (this.keyPressTimeout) {
      clearTimeout(this.keyPressTimeout);
      this.keyPressTimeout = null;
    }
  }
  handleKeyDown(event) {
    if (event.key === "s" && event.getModifierState("Control"))
      this.startSpeechToText(event);
  }
  async handleKeyUp(event) {
    if (event.key === "s" && event.getModifierState("Control"))
      await this.stopSpeechToText(event);
  }
  processOnlineReferences(referenceSection, onlineContext) {
    let numOnlineReferences = 0;
    for (let subquery in onlineContext) {
      let onlineReference = onlineContext[subquery];
      if (onlineReference.organic && onlineReference.organic.length > 0) {
        numOnlineReferences += onlineReference.organic.length;
        for (let key in onlineReference.organic) {
          let reference = onlineReference.organic[key];
          let polishedReference = this.generateOnlineReference(referenceSection, reference, key);
          referenceSection.appendChild(polishedReference);
        }
      }
      if (onlineReference.knowledgeGraph && onlineReference.knowledgeGraph.length > 0) {
        numOnlineReferences += onlineReference.knowledgeGraph.length;
        for (let key in onlineReference.knowledgeGraph) {
          let reference = onlineReference.knowledgeGraph[key];
          let polishedReference = this.generateOnlineReference(referenceSection, reference, key);
          referenceSection.appendChild(polishedReference);
        }
      }
      if (onlineReference.peopleAlsoAsk && onlineReference.peopleAlsoAsk.length > 0) {
        numOnlineReferences += onlineReference.peopleAlsoAsk.length;
        for (let key in onlineReference.peopleAlsoAsk) {
          let reference = onlineReference.peopleAlsoAsk[key];
          let polishedReference = this.generateOnlineReference(referenceSection, reference, key);
          referenceSection.appendChild(polishedReference);
        }
      }
      if (onlineReference.webpages && onlineReference.webpages.length > 0) {
        numOnlineReferences += onlineReference.webpages.length;
        for (let key in onlineReference.webpages) {
          let reference = onlineReference.webpages[key];
          let polishedReference = this.generateOnlineReference(referenceSection, reference, key);
          referenceSection.appendChild(polishedReference);
        }
      }
    }
    return numOnlineReferences;
  }
  generateOnlineReference(messageEl, reference, index) {
    let title = reference.title || reference.link;
    let link = reference.link;
    let snippet = reference.snippet;
    let question = reference.question ? `<b>Question:</b> ${reference.question}<br><br>` : "";
    let referenceButton = messageEl.createEl("button");
    let linkElement = referenceButton.createEl("a");
    linkElement.setAttribute("href", link);
    linkElement.setAttribute("target", "_blank");
    linkElement.setAttribute("rel", "noopener noreferrer");
    linkElement.classList.add("reference-link");
    linkElement.setAttribute("title", title);
    linkElement.textContent = title;
    referenceButton.id = `ref-${index}`;
    referenceButton.classList.add("reference-button");
    referenceButton.classList.add("collapsed");
    referenceButton.tabIndex = 0;
    referenceButton.addEventListener("click", function() {
      if (this.classList.contains("collapsed")) {
        this.classList.remove("collapsed");
        this.classList.add("expanded");
        this.innerHTML = linkElement.outerHTML + `<br><br>${question + snippet}`;
      } else {
        this.classList.add("collapsed");
        this.classList.remove("expanded");
        this.innerHTML = linkElement.outerHTML;
      }
    });
    return referenceButton;
  }
  generateReference(messageEl, referenceJson, index) {
    let reference = referenceJson.hasOwnProperty("compiled") ? referenceJson.compiled : referenceJson;
    let referenceFile = referenceJson.hasOwnProperty("file") ? referenceJson.file : null;
    const mdFiles = this.app.vault.getMarkdownFiles();
    const pdfFiles = this.app.vault.getFiles().filter((file) => file.extension === "pdf");
    reference = reference.split("\n").slice(1).join("\n");
    let escaped_ref = reference.replace(/"/g, "&quot;");
    let referenceButton = messageEl.createEl("button");
    if (referenceFile) {
      const linkToEntry = getLinkToEntry(mdFiles.concat(pdfFiles), referenceFile, reference);
      const linkElement = referenceButton.createEl("span");
      linkElement.setAttribute("title", escaped_ref);
      linkElement.textContent = referenceFile;
      if (linkElement && linkToEntry) {
        linkElement.classList.add("reference-link");
        linkElement.addEventListener("click", (event) => {
          event.stopPropagation();
          this.app.workspace.openLinkText(linkToEntry, "");
        });
      }
    }
    let referenceText = referenceButton.createDiv();
    referenceText.textContent = escaped_ref;
    referenceButton.id = `ref-${index}`;
    referenceButton.classList.add("reference-button");
    referenceButton.classList.add("collapsed");
    referenceButton.tabIndex = 0;
    referenceButton.addEventListener("click", function() {
      if (this.classList.contains("collapsed")) {
        this.classList.remove("collapsed");
        this.classList.add("expanded");
      } else {
        this.classList.add("collapsed");
        this.classList.remove("expanded");
      }
    });
    return referenceButton;
  }
  textToSpeech(message, event = null) {
    let loader = document.createElement("span");
    loader.classList.add("loader");
    let speechButton;
    let speechIcon;
    if (event === null) {
      let speechButtons = document.getElementsByClassName("speech-button");
      speechButton = speechButtons[speechButtons.length - 1];
      let speechIcons = document.getElementsByClassName("speech-icon");
      speechIcon = speechIcons[speechIcons.length - 1];
    } else {
      speechButton = event.currentTarget;
      speechIcon = event.target;
    }
    speechButton.appendChild(loader);
    speechButton.disabled = true;
    const context = new AudioContext();
    let textToSpeechApi = `${this.setting.khojUrl}/api/chat/speech?text=${encodeURIComponent(message)}`;
    fetch(textToSpeechApi, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "Authorization": `Bearer ${this.setting.khojApiKey}`
      }
    }).then((response) => response.arrayBuffer()).then((arrayBuffer) => context.decodeAudioData(arrayBuffer)).then((audioBuffer) => {
      const source = context.createBufferSource();
      source.buffer = audioBuffer;
      source.connect(context.destination);
      source.start(0);
      source.onended = function() {
        speechButton.removeChild(loader);
        speechButton.disabled = false;
      };
    }).catch((err) => {
      console.error("Error playing speech:", err);
      speechButton.removeChild(loader);
      speechButton.disabled = false;
    });
  }
  formatHTMLMessage(message, raw = false, willReplace = true) {
    message = message.replace(/<s>\[INST\].+(<\/s>)?/g, "");
    const { content, header } = this.processTrainOfThought(message);
    message = content;
    if (!raw) {
      message = this.transformEditBlocks(message);
    }
    message = DOMPurify.sanitize(message);
    let chatMessageBodyTextEl = this.contentEl.createDiv();
    chatMessageBodyTextEl.innerHTML = this.markdownTextToSanitizedHtml(message, this);
    if (willReplace === true) {
      this.renderActionButtons(message, chatMessageBodyTextEl);
    }
    return chatMessageBodyTextEl;
  }
  markdownTextToSanitizedHtml(markdownText, component) {
    let virtualChatMessageBodyTextEl = document.createElement("div");
    import_obsidian6.MarkdownRenderer.render(this.app, markdownText, virtualChatMessageBodyTextEl, "", component);
    virtualChatMessageBodyTextEl.innerHTML = virtualChatMessageBodyTextEl.innerHTML.replace(/<img(?:(?!src=["'](app:|data:|https:\/\/generated\.khoj\.dev)).)*?>/gis, "");
    return DOMPurify.sanitize(virtualChatMessageBodyTextEl.innerHTML);
  }
  processTrainOfThought(message) {
    let extractedHeader = message.match(/\*\*(.*)\*\*/);
    let header = extractedHeader ? extractedHeader[1] : "";
    let content = message;
    let jsonMessage = null;
    try {
      const jsonMatch = message.match(/\{.*("action": "screenshot"|"type": "screenshot"|"image": "data:image\/.*").*\}/);
      if (jsonMatch) {
        jsonMessage = JSON.parse(jsonMatch[0]);
        const screenshotHtmlString = `<img src="${jsonMessage.image}" alt="State of environment" class="max-w-full" />`;
        content = content.replace(`:
**Action**: ${jsonMatch[0]}`, `

- ${jsonMessage.text}
${screenshotHtmlString}`);
      }
    } catch (e) {
      console.error("Failed to parse screenshot data", e);
    }
    return { content, header };
  }
  renderMessageWithReferences(chatEl, message, sender, turnId, context, onlineContext, dt, intentType, inferredQueries, conversationId, images, excalidrawDiagram, mermaidjsDiagram) {
    if (!message)
      return;
    let chatMessageEl;
    if ((intentType == null ? void 0 : intentType.includes("text-to-image")) || intentType === "excalidraw" || images && images.length > 0 || mermaidjsDiagram || excalidrawDiagram) {
      let imageMarkdown = this.generateImageMarkdown(message, intentType != null ? intentType : "", inferredQueries, conversationId, images, excalidrawDiagram, mermaidjsDiagram);
      chatMessageEl = this.renderMessage({
        chatBodyEl: chatEl,
        message: imageMarkdown,
        sender,
        dt,
        turnId
      });
    } else {
      chatMessageEl = this.renderMessage({
        chatBodyEl: chatEl,
        message,
        sender,
        dt,
        turnId
      });
    }
    if ((context == null || context.length == 0) && (onlineContext == null || onlineContext && Object.keys(onlineContext).length == 0)) {
      return;
    }
    let references = {};
    if (!!context)
      references["notes"] = context;
    if (!!onlineContext)
      references["online"] = onlineContext;
    let chatMessageBodyEl = chatMessageEl.getElementsByClassName("khoj-chat-message-text")[0];
    chatMessageBodyEl.appendChild(this.createReferenceSection(references));
  }
  generateImageMarkdown(message, intentType, inferredQueries, conversationId, images, excalidrawDiagram, mermaidjsDiagram) {
    let imageMarkdown = "";
    if (intentType === "text-to-image") {
      imageMarkdown = `![](data:image/png;base64,${message})`;
    } else if (intentType === "text-to-image2") {
      imageMarkdown = `![](${message})`;
    } else if (intentType === "text-to-image-v3") {
      imageMarkdown = `![](${message})`;
    } else if (intentType === "excalidraw" || excalidrawDiagram) {
      const domain = this.setting.khojUrl.endsWith("/") ? this.setting.khojUrl : `${this.setting.khojUrl}/`;
      const redirectMessage = `Hey, I'm not ready to show you diagrams yet here. But you can view it in ${domain}chat?conversationId=${conversationId}`;
      imageMarkdown = redirectMessage;
    } else if (mermaidjsDiagram) {
      imageMarkdown = "```mermaid\n" + mermaidjsDiagram + "\n```";
    } else if (images && images.length > 0) {
      imageMarkdown += images.map((image) => `![](${image})`).join("\n\n");
      imageMarkdown += message;
    }
    if ((images == null ? void 0 : images.length) === 0 && inferredQueries) {
      imageMarkdown += "\n\n**Inferred Query**:";
      for (let inferredQuery of inferredQueries) {
        imageMarkdown += `

${inferredQuery}`;
      }
    }
    return imageMarkdown;
  }
  renderMessage({ chatBodyEl, message, sender, dt, turnId, raw = false, willReplace = true, isSystemMessage = false }) {
    let message_time = this.formatDate(dt != null ? dt : new Date());
    let chatMessageEl = chatBodyEl.createDiv({
      attr: {
        "data-meta": message_time,
        class: `khoj-chat-message ${sender}`,
        ...turnId && { "data-turnid": turnId }
      }
    });
    let chatMessageBodyEl = chatMessageEl.createDiv();
    chatMessageBodyEl.addClasses(["khoj-chat-message-text", sender]);
    let chatMessageBodyTextEl = chatMessageBodyEl.createDiv();
    if (sender === "you") {
      message = message.replace(/<SYSTEM>.*?<\/SYSTEM>/s, "");
    }
    message = DOMPurify.sanitize(message);
    if (raw) {
      chatMessageBodyTextEl.innerHTML = message;
    } else {
      chatMessageBodyTextEl.innerHTML = this.markdownTextToSanitizedHtml(message, this);
    }
    if (willReplace === true) {
      this.renderActionButtons(message, chatMessageBodyTextEl, isSystemMessage);
    }
    chatMessageEl.style.userSelect = "text";
    this.scrollChatToBottom();
    return chatMessageEl;
  }
  createKhojResponseDiv(dt) {
    let messageTime = this.formatDate(dt != null ? dt : new Date());
    let chatBodyEl = this.contentEl.getElementsByClassName("khoj-chat-body")[0];
    let chatMessageEl = chatBodyEl.createDiv({
      attr: {
        "data-meta": messageTime,
        class: `khoj-chat-message khoj`
      }
    });
    this.scrollChatToBottom();
    return chatMessageEl;
  }
  async renderIncrementalMessage(htmlElement, additionalMessage) {
    this.chatMessageState.rawResponse += additionalMessage;
    let sanitizedResponse = DOMPurify.sanitize(this.chatMessageState.rawResponse);
    const transformedResponse = this.transformEditBlocks(sanitizedResponse);
    const tempElement = document.createElement("div");
    tempElement.innerHTML = this.markdownTextToSanitizedHtml(transformedResponse, this);
    htmlElement.innerHTML = tempElement.innerHTML;
    this.renderActionButtons(this.chatMessageState.rawResponse, htmlElement);
    this.scrollChatToBottom();
  }
  renderActionButtons(message, chatMessageBodyTextEl, isSystemMessage = false) {
    var _a;
    let copyButton = this.contentEl.createEl("button");
    copyButton.classList.add("chat-action-button");
    copyButton.title = "Copy Message to Clipboard";
    (0, import_obsidian6.setIcon)(copyButton, "copy-plus");
    copyButton.addEventListener("click", createCopyParentText(message));
    let pasteToFile = this.contentEl.createEl("button");
    pasteToFile.classList.add("chat-action-button");
    pasteToFile.title = "Paste Message to File";
    (0, import_obsidian6.setIcon)(pasteToFile, "clipboard-paste");
    pasteToFile.addEventListener("click", (event) => {
      pasteTextAtCursor(createCopyParentText(message, "clipboard-paste")(event));
    });
    let editButton = null;
    if (!isSystemMessage && chatMessageBodyTextEl.closest(".khoj-chat-message.you")) {
      editButton = this.contentEl.createEl("button");
      editButton.classList.add("chat-action-button");
      editButton.title = "Edit Message";
      (0, import_obsidian6.setIcon)(editButton, "edit-3");
      editButton.addEventListener("click", async () => {
        const messageEl = chatMessageBodyTextEl.closest(".khoj-chat-message");
        if (messageEl) {
          const allMessages = Array.from(this.contentEl.getElementsByClassName("khoj-chat-message"));
          const currentIndex = allMessages.indexOf(messageEl);
          const messagesToDelete = allMessages.slice(currentIndex);
          let messageContent = message;
          const emojiRegex = /^[^\p{L}\p{N}]+\s*/u;
          messageContent = messageContent.replace(emojiRegex, "");
          const chatInput = this.contentEl.querySelector(".khoj-chat-input");
          if (chatInput) {
            chatInput.value = messageContent;
            chatInput.focus();
          }
          for (let i = messagesToDelete.length - 1; i >= 0; i--) {
            messagesToDelete[i].remove();
          }
          (async () => {
            for (const msgToDelete of messagesToDelete) {
              await this.deleteMessage(msgToDelete, true, false);
            }
          })();
        }
      });
    }
    let deleteButton = null;
    if (!isSystemMessage) {
      deleteButton = this.contentEl.createEl("button");
      deleteButton.classList.add("chat-action-button");
      deleteButton.title = "Delete Message";
      (0, import_obsidian6.setIcon)(deleteButton, "trash-2");
      deleteButton.addEventListener("click", () => {
        const messageEl = chatMessageBodyTextEl.closest(".khoj-chat-message");
        if (messageEl) {
          if (confirm("Are you sure you want to delete this message?")) {
            this.deleteMessage(messageEl);
          }
        }
      });
    }
    chatMessageBodyTextEl.append(copyButton, pasteToFile);
    if (editButton) {
      chatMessageBodyTextEl.append(editButton);
    }
    if (deleteButton) {
      chatMessageBodyTextEl.append(deleteButton);
    }
    if ((_a = this.setting.userInfo) == null ? void 0 : _a.is_active) {
      let speechButton = this.contentEl.createEl("button");
      speechButton.classList.add("chat-action-button", "speech-button");
      speechButton.title = "Listen to Message";
      (0, import_obsidian6.setIcon)(speechButton, "speech");
      speechButton.addEventListener("click", (event) => this.textToSpeech(message, event));
      chatMessageBodyTextEl.append(speechButton);
    }
  }
  formatDate(date) {
    let time_string = date.toLocaleTimeString("en-IN", { hour: "2-digit", minute: "2-digit", hour12: false });
    let date_string = date.toLocaleString("en-IN", { year: "numeric", month: "short", day: "2-digit" }).replace(/-/g, " ");
    return `${time_string}, ${date_string}`;
  }
  getLearningMoment() {
    const modifierKey = import_obsidian6.Platform.isMacOS ? "\u2318" : "^";
    const learningMoments = [
      "Type '/' to select response mode."
    ];
    if (this.userMessages.length > 0) {
      learningMoments.push(`Load previous messages with ${modifierKey}+\u2191/\u2193`);
    }
    return learningMoments[Math.floor(Math.random() * learningMoments.length)];
  }
  async createNewConversation(agentSlug) {
    let chatBodyEl = this.contentEl.getElementsByClassName("khoj-chat-body")[0];
    chatBodyEl.innerHTML = "";
    chatBodyEl.dataset.conversationId = "";
    chatBodyEl.dataset.conversationTitle = "";
    this.userMessages = [];
    this.startingMessage = this.getLearningMoment();
    const chatInput = this.contentEl.querySelector(".khoj-chat-input");
    if (chatInput) {
      chatInput.placeholder = this.startingMessage;
    }
    try {
      let endpoint = `${this.setting.khojUrl}/api/chat/sessions`;
      agentSlug = agentSlug || this.currentAgent;
      if (agentSlug) {
        endpoint += `?agent_slug=${encodeURIComponent(agentSlug)}`;
      }
      const response = await fetch(endpoint, {
        method: "POST",
        headers: {
          "Authorization": `Bearer ${this.setting.khojApiKey}`,
          "Content-Type": "application/json"
        },
        body: JSON.stringify({})
      });
      if (response.ok) {
        const sessionInfo = await response.json();
        chatBodyEl.dataset.conversationId = sessionInfo.conversation_id;
        this.currentAgent = agentSlug || null;
        const agentSelect = this.contentEl.querySelector(".khoj-header-agent-select");
        if (agentSelect) {
          agentSelect.value = this.currentAgent || "";
        }
      } else {
        console.error("Failed to create session:", response.statusText);
      }
    } catch (error) {
      console.error("Error creating session:", error);
    }
    this.renderMessage({ chatBodyEl, message: "Hey, what's up?", sender: "khoj", isSystemMessage: true });
  }
  async toggleChatSessions(forceShow = false) {
    var _a;
    this.userMessages = [];
    let chatBodyEl = this.contentEl.getElementsByClassName("khoj-chat-body")[0];
    if (!forceShow && ((_a = this.contentEl.getElementsByClassName("side-panel")) == null ? void 0 : _a.length) > 0) {
      chatBodyEl.innerHTML = "";
      return this.getChatHistory(chatBodyEl);
    }
    chatBodyEl.innerHTML = "";
    const sidePanelEl = chatBodyEl.createDiv("side-panel");
    const newConversationEl = sidePanelEl.createDiv("new-conversation");
    const conversationHeaderTitleEl = newConversationEl.createDiv("conversation-header-title");
    conversationHeaderTitleEl.textContent = "Conversations";
    const newConversationButtonEl = newConversationEl.createEl("button");
    newConversationButtonEl.classList.add("new-conversation-button");
    newConversationButtonEl.classList.add("side-panel-button");
    newConversationButtonEl.addEventListener("click", (_) => this.createNewConversation(this.currentAgent));
    (0, import_obsidian6.setIcon)(newConversationButtonEl, "plus");
    newConversationButtonEl.innerHTML += "New";
    newConversationButtonEl.title = "New Conversation (Ctrl+Alt+N)";
    const existingConversationsEl = sidePanelEl.createDiv("existing-conversations");
    const conversationListEl = existingConversationsEl.createDiv("conversation-list");
    const conversationListBodyHeaderEl = conversationListEl.createDiv("conversation-list-header");
    const conversationListBodyEl = conversationListEl.createDiv("conversation-list-body");
    const chatSessionsUrl = `${this.setting.khojUrl}/api/chat/sessions?client=obsidian`;
    const headers = { "Authorization": `Bearer ${this.setting.khojApiKey}` };
    try {
      let response = await fetch(chatSessionsUrl, { method: "GET", headers });
      let responseJson = await response.json();
      let conversationId = chatBodyEl.dataset.conversationId;
      if (responseJson.length > 0) {
        conversationListBodyHeaderEl.style.display = "block";
        for (let key in responseJson) {
          let conversation = responseJson[key];
          let conversationSessionEl = this.contentEl.createEl("div");
          let incomingConversationId = conversation["conversation_id"];
          conversationSessionEl.classList.add("conversation-session");
          if (incomingConversationId == conversationId) {
            conversationSessionEl.classList.add("selected-conversation");
          }
          const conversationTitle = conversation["slug"].split("<SYSTEM>")[0].trim() || `New conversation \u{1F331}`;
          const conversationSessionTitleEl = conversationSessionEl.createDiv("conversation-session-title");
          conversationSessionTitleEl.textContent = conversationTitle;
          conversationSessionTitleEl.addEventListener("click", () => {
            chatBodyEl.innerHTML = "";
            chatBodyEl.dataset.conversationId = incomingConversationId;
            chatBodyEl.dataset.conversationTitle = conversationTitle;
            this.getChatHistory(chatBodyEl);
          });
          let conversationMenuEl = this.contentEl.createEl("div");
          conversationMenuEl = this.addConversationMenu(conversationMenuEl, conversationSessionEl, conversationTitle, conversationSessionTitleEl, chatBodyEl, incomingConversationId, incomingConversationId == conversationId);
          conversationSessionEl.appendChild(conversationMenuEl);
          conversationListBodyEl.appendChild(conversationSessionEl);
        }
      }
    } catch (err) {
      return false;
    }
    return true;
  }
  addConversationMenu(conversationMenuEl, conversationSessionEl, conversationTitle, conversationSessionTitleEl, chatBodyEl, incomingConversationId, selectedConversation) {
    conversationMenuEl.classList.add("conversation-menu");
    const headers = { "Authorization": `Bearer ${this.setting.khojApiKey}` };
    let editConversationTitleButtonEl = this.contentEl.createEl("button");
    (0, import_obsidian6.setIcon)(editConversationTitleButtonEl, "edit");
    editConversationTitleButtonEl.title = "Rename";
    editConversationTitleButtonEl.classList.add("edit-title-button", "three-dot-menu-button-item", "clickable-icon");
    if (selectedConversation)
      editConversationTitleButtonEl.classList.add("selected-conversation");
    editConversationTitleButtonEl.addEventListener("click", (event) => {
      event.stopPropagation();
      let conversationMenuChildren = conversationMenuEl.children;
      let totalItems = conversationMenuChildren.length;
      for (let i = totalItems - 1; i >= 0; i--) {
        conversationMenuChildren[i].remove();
      }
      let editConversationTitleInputEl = this.contentEl.createEl("input");
      editConversationTitleInputEl.classList.add("conversation-title-input");
      editConversationTitleInputEl.value = conversationTitle;
      editConversationTitleInputEl.addEventListener("click", function(event2) {
        event2.stopPropagation();
      });
      editConversationTitleInputEl.addEventListener("keydown", function(event2) {
        if (event2.key === "Enter") {
          event2.preventDefault();
          editConversationTitleSaveButtonEl.click();
        }
      });
      let editConversationTitleSaveButtonEl = this.contentEl.createEl("button");
      conversationSessionTitleEl.replaceWith(editConversationTitleInputEl);
      editConversationTitleSaveButtonEl.innerHTML = "Save";
      editConversationTitleSaveButtonEl.classList.add("three-dot-menu-button-item", "clickable-icon");
      if (selectedConversation)
        editConversationTitleSaveButtonEl.classList.add("selected-conversation");
      editConversationTitleSaveButtonEl.addEventListener("click", (event2) => {
        event2.stopPropagation();
        let newTitle = editConversationTitleInputEl.value;
        if (newTitle != null) {
          let editURL = `/api/chat/title?client=web&conversation_id=${incomingConversationId}&title=${newTitle}`;
          fetch(`${this.setting.khojUrl}${editURL}`, { method: "PATCH", headers }).then((response) => response.ok ? response.json() : Promise.reject(response)).then((data) => {
            conversationSessionTitleEl2.textContent = newTitle;
          }).catch((err) => {
            return;
          });
          const conversationSessionTitleEl2 = conversationSessionEl.createDiv("conversation-session-title");
          conversationSessionTitleEl2.textContent = newTitle;
          conversationSessionTitleEl2.addEventListener("click", () => {
            chatBodyEl.innerHTML = "";
            chatBodyEl.dataset.conversationId = incomingConversationId;
            chatBodyEl.dataset.conversationTitle = conversationTitle;
            this.getChatHistory(chatBodyEl);
          });
          let newConversationMenuEl = this.contentEl.createEl("div");
          newConversationMenuEl = this.addConversationMenu(newConversationMenuEl, conversationSessionEl, newTitle, conversationSessionTitleEl2, chatBodyEl, incomingConversationId, selectedConversation);
          conversationMenuEl.replaceWith(newConversationMenuEl);
          editConversationTitleInputEl.replaceWith(conversationSessionTitleEl2);
        }
      });
      conversationMenuEl.appendChild(editConversationTitleSaveButtonEl);
    });
    conversationMenuEl.appendChild(editConversationTitleButtonEl);
    let deleteConversationButtonEl = this.contentEl.createEl("button");
    (0, import_obsidian6.setIcon)(deleteConversationButtonEl, "trash");
    deleteConversationButtonEl.title = "Delete";
    deleteConversationButtonEl.classList.add("delete-conversation-button", "three-dot-menu-button-item", "clickable-icon");
    if (selectedConversation)
      deleteConversationButtonEl.classList.add("selected-conversation");
    deleteConversationButtonEl.addEventListener("click", () => {
      let confirmation = confirm("Are you sure you want to delete this chat session?");
      if (!confirmation)
        return;
      let deleteURL = `/api/chat/history?client=obsidian&conversation_id=${incomingConversationId}`;
      fetch(`${this.setting.khojUrl}${deleteURL}`, { method: "DELETE", headers }).then((response) => response.ok ? response.json() : Promise.reject(response)).then((data) => {
        chatBodyEl.innerHTML = "";
        chatBodyEl.dataset.conversationId = "";
        chatBodyEl.dataset.conversationTitle = "";
        this.toggleChatSessions(true);
      }).catch((err) => {
        return;
      });
    });
    conversationMenuEl.appendChild(deleteConversationButtonEl);
    return conversationMenuEl;
  }
  async getChatHistory(chatBodyEl) {
    var _a, _b, _c;
    let chatUrl = `${this.setting.khojUrl}/api/chat/history?client=obsidian`;
    if (chatBodyEl.dataset.conversationId) {
      chatUrl += `&conversation_id=${chatBodyEl.dataset.conversationId}`;
    }
    console.debug("Fetching chat history from:", chatUrl);
    try {
      let response = await fetch(chatUrl, {
        method: "GET",
        headers: { "Authorization": `Bearer ${this.setting.khojApiKey}` }
      });
      let responseJson = await response.json();
      console.debug("Chat history response:", responseJson);
      chatBodyEl.dataset.conversationId = responseJson.conversation_id;
      if (responseJson.detail) {
        let setupMsg = "Hi \u{1F44B}\u{1F3FE}, to start chatting add available chat models options via [the Django Admin panel](/server/admin) on the Server";
        this.renderMessage({
          chatBodyEl,
          message: setupMsg,
          sender: "khoj",
          isSystemMessage: true
        });
        return false;
      } else if (responseJson.response) {
        chatBodyEl.dataset.conversationId = responseJson.response.conversation_id;
        chatBodyEl.dataset.conversationTitle = responseJson.response.slug || `New conversation \u{1F331}`;
        if ((_a = responseJson.response.agent) == null ? void 0 : _a.slug) {
          console.debug("Found agent in conversation history:", responseJson.response.agent);
          this.currentAgent = responseJson.response.agent.slug;
          const agentSelect = this.contentEl.querySelector(".khoj-header-agent-select");
          if (agentSelect && this.currentAgent) {
            agentSelect.value = this.currentAgent;
            console.log("Updated agent selector to:", this.currentAgent);
          }
        }
        let chatLogs = ((_b = responseJson.response) == null ? void 0 : _b.conversation_id) ? (_c = responseJson.response.chat) != null ? _c : [] : responseJson.response;
        chatLogs.forEach((chatLog) => {
          var _a2, _b2, _c2;
          if (chatLog.by === "you") {
            chatLog.message = this.convertCommandsToEmojis(chatLog.message);
          }
          if (chatLog.by === "khoj") {
            chatLog.message = this.transformEditBlocks(chatLog.message);
          }
          this.renderMessageWithReferences(chatBodyEl, chatLog.message, chatLog.by, chatLog.turnId, chatLog.context, chatLog.onlineContext, new Date(chatLog.created), (_a2 = chatLog.intent) == null ? void 0 : _a2.type, (_b2 = chatLog.intent) == null ? void 0 : _b2["inferred-queries"], (_c2 = chatBodyEl.dataset.conversationId) != null ? _c2 : "", chatLog.images, chatLog.excalidrawDiagram, chatLog.mermaidjsDiagram);
          if (chatLog.by === "you") {
            this.userMessages.push(chatLog.message);
          }
        });
        this.startingMessage = this.getLearningMoment();
        const chatInput = this.contentEl.querySelector(".khoj-chat-input");
        if (chatInput) {
          chatInput.placeholder = this.startingMessage;
        }
      }
    } catch (err) {
      let errorMsg = "Unable to get response from Khoj server \u2764\uFE0F\u200D\u{1FA79}. Ensure server is running or contact developers for help at [team@khoj.dev](mailto:team@khoj.dev) or in [Discord](https://discord.gg/BDgyabRM6e)";
      this.renderMessage({
        chatBodyEl,
        message: errorMsg,
        sender: "khoj",
        isSystemMessage: true
      });
      return false;
    }
    return true;
  }
  convertMessageChunkToJson(rawChunk) {
    if ((rawChunk == null ? void 0 : rawChunk.startsWith("{")) && (rawChunk == null ? void 0 : rawChunk.endsWith("}"))) {
      try {
        let jsonChunk = JSON.parse(rawChunk);
        if (!jsonChunk.type)
          jsonChunk = { type: "message", data: jsonChunk };
        return jsonChunk;
      } catch (e) {
        return { type: "message", data: rawChunk };
      }
    } else if (rawChunk.length > 0) {
      return { type: "message", data: rawChunk };
    }
    return { type: "", data: "" };
  }
  async processMessageChunk(rawChunk) {
    var _a, _b, _c;
    const chunk = this.convertMessageChunkToJson(rawChunk);
    if (!chunk || !chunk.type)
      return;
    if (chunk.type === "start_llm_response") {
      this.isStreaming = true;
      const chatInput = this.contentEl.getElementsByClassName("khoj-chat-input")[0];
      if (chatInput) {
        chatInput.style.overflowY = "hidden";
      }
    } else if (chunk.type === "status") {
      const statusMessage = chunk.data;
      this.handleStreamResponse(this.chatMessageState.newResponseTextEl, statusMessage, this.chatMessageState.loadingEllipsis, false);
    } else if (chunk.type === "generated_assets") {
      const generatedAssets = chunk.data;
      const imageData = this.handleImageResponse(generatedAssets, this.chatMessageState.rawResponse);
      this.chatMessageState.generatedAssets = imageData;
      this.handleStreamResponse(this.chatMessageState.newResponseTextEl, imageData, this.chatMessageState.loadingEllipsis, false);
    } else if (chunk.type === "end_llm_response") {
      this.isStreaming = false;
      const chatInput = this.contentEl.getElementsByClassName("khoj-chat-input")[0];
      if (chatInput) {
        this.autoResize();
      }
    } else if (chunk.type === "end_response") {
      this.isStreaming = false;
      const chatInput = this.contentEl.getElementsByClassName("khoj-chat-input")[0];
      if (chatInput) {
        this.autoResize();
      }
      if (this.fileAccessMode === "write") {
        const editBlocks = this.parseEditBlocks(this.chatMessageState.rawResponse);
        if (editBlocks.length > 0) {
          const firstBlock = editBlocks[0];
          if (firstBlock.hasError) {
            if (this.editRetryCount < this.maxEditRetries) {
              await this.handleEditRetry(firstBlock);
              return;
            } else {
              console.warn("[Khoj] Max edit retries reached. Aborting further retries.");
            }
          } else {
            this.editRetryCount = 0;
            await this.applyEditBlocks(editBlocks);
          }
        } else {
          this.editRetryCount = 0;
        }
      }
      if (this.chatMessageState.isVoice && ((_a = this.setting.userInfo) == null ? void 0 : _a.is_active) && this.setting.autoVoiceResponse)
        this.textToSpeech(this.chatMessageState.rawResponse);
      this.finalizeChatBodyResponse(this.chatMessageState.references, this.chatMessageState.newResponseTextEl, this.chatMessageState.turnId);
      const liveQuery = this.chatMessageState.rawQuery;
      this.chatMessageState = {
        newResponseTextEl: null,
        newResponseEl: null,
        loadingEllipsis: null,
        references: {},
        rawResponse: "",
        rawQuery: liveQuery,
        isVoice: false,
        generatedAssets: "",
        turnId: "",
        editBlocks: [],
        editRetryCount: 0,
        parentRetryCount: 0
      };
    } else if (chunk.type === "references") {
      this.chatMessageState.references = { "notes": chunk.data.context, "online": chunk.data.onlineContext };
    } else if (chunk.type === "message") {
      const chunkData = chunk.data;
      if (typeof chunkData === "object" && chunkData !== null) {
        this.handleJsonResponse(chunkData);
      } else if (typeof chunkData === "string" && ((_b = chunkData.trim()) == null ? void 0 : _b.startsWith("{")) && ((_c = chunkData.trim()) == null ? void 0 : _c.endsWith("}"))) {
        try {
          const jsonData = JSON.parse(chunkData.trim());
          this.handleJsonResponse(jsonData);
        } catch (e) {
          this.chatMessageState.rawResponse += chunkData;
          this.handleStreamResponse(this.chatMessageState.newResponseTextEl, this.chatMessageState.rawResponse + this.chatMessageState.generatedAssets, this.chatMessageState.loadingEllipsis);
        }
      } else {
        this.chatMessageState.rawResponse += chunkData;
        this.handleStreamResponse(this.chatMessageState.newResponseTextEl, this.chatMessageState.rawResponse + this.chatMessageState.generatedAssets, this.chatMessageState.loadingEllipsis);
      }
    } else if (chunk.type === "metadata") {
      const { turnId } = chunk.data;
      if (turnId) {
        this.chatMessageState.turnId = turnId;
      }
    }
  }
  handleJsonResponse(jsonData) {
    if (jsonData.image || jsonData.detail || jsonData.images || jsonData.mermaidjsDiagram) {
      this.chatMessageState.rawResponse = this.handleImageResponse(jsonData, this.chatMessageState.rawResponse);
    } else if (jsonData.response) {
      this.chatMessageState.rawResponse = jsonData.response;
    }
    if (this.chatMessageState.newResponseTextEl) {
      this.chatMessageState.newResponseTextEl.innerHTML = "";
      this.chatMessageState.newResponseTextEl.appendChild(this.formatHTMLMessage(this.chatMessageState.rawResponse));
    }
  }
  async readChatStream(response) {
    if (response.body == null)
      return;
    const reader = response.body.getReader();
    const decoder = new TextDecoder();
    const eventDelimiter = "\u2403\u{1F51A}\u2417";
    let buffer = "";
    while (true) {
      const { value, done } = await reader.read();
      if (done) {
        this.processMessageChunk(buffer);
        buffer = "";
        break;
      }
      const chunk = decoder.decode(value, { stream: true });
      console.debug("Raw Chunk:", chunk);
      buffer += chunk;
      let newEventIndex;
      while ((newEventIndex = buffer.indexOf(eventDelimiter)) !== -1) {
        const event = buffer.slice(0, newEventIndex);
        buffer = buffer.slice(newEventIndex + eventDelimiter.length);
        if (event)
          this.processMessageChunk(event);
      }
    }
  }
  async getChatResponse(query, displayQuery, isVoice = false, displayUserMessage = true) {
    if (!query || query === "")
      return;
    let chatBodyEl = this.contentEl.getElementsByClassName("khoj-chat-body")[0];
    if (displayUserMessage) {
      this.renderMessage({ chatBodyEl, message: displayQuery || query, sender: "you" });
    }
    let conversationId = chatBodyEl.dataset.conversationId;
    if (!conversationId) {
      try {
        const requestBody = {
          ...this.currentAgent && { agent_slug: this.currentAgent }
        };
        const response2 = await fetch(`${this.setting.khojUrl}/api/chat/sessions`, {
          method: "POST",
          headers: {
            "Authorization": `Bearer ${this.setting.khojApiKey}`,
            "Content-Type": "application/json"
          },
          body: JSON.stringify(requestBody)
        });
        if (response2.ok) {
          const data = await response2.json();
          conversationId = data.conversation_id;
          chatBodyEl.dataset.conversationId = conversationId;
        } else {
          console.error("Failed to create session:", response2.statusText);
          return;
        }
      } catch (error) {
        console.error("Error creating session:", error);
        return;
      }
    }
    const openFilesContent = await this.getOpenFilesContent();
    const modeMatch = this.chatModes.find((mode) => query.startsWith(mode.command));
    const modeCommand = modeMatch ? query.substring(0, modeMatch.command.length) : "";
    const queryWithoutMode = modeMatch ? query.substring(modeMatch.command.length).trim() : query;
    const finalQuery = modeCommand + (modeCommand ? " " : "") + queryWithoutMode + openFilesContent;
    const chatUrl = `${this.setting.khojUrl}/api/chat?client=obsidian`;
    const body = {
      q: finalQuery,
      n: this.setting.resultsCount,
      stream: true,
      conversation_id: conversationId,
      ...this.currentAgent && { agent_slug: this.currentAgent },
      ...!!this.location && this.location.city && { city: this.location.city },
      ...!!this.location && this.location.region && { region: this.location.region },
      ...!!this.location && this.location.countryName && { country: this.location.countryName },
      ...!!this.location && this.location.countryCode && { country_code: this.location.countryCode },
      ...!!this.location && this.location.timezone && { timezone: this.location.timezone }
    };
    let newResponseEl = this.createKhojResponseDiv();
    let newResponseTextEl = newResponseEl.createDiv();
    newResponseTextEl.classList.add("khoj-chat-message-text", "khoj");
    let loadingEllipsis = this.createLoadingEllipse();
    newResponseTextEl.appendChild(loadingEllipsis);
    this.chatMessageState = {
      newResponseEl,
      newResponseTextEl,
      loadingEllipsis,
      references: {},
      rawQuery: query,
      rawResponse: "",
      isVoice,
      generatedAssets: "",
      turnId: "",
      editBlocks: [],
      editRetryCount: 0
    };
    let response = await fetch(chatUrl, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "Authorization": `Bearer ${this.setting.khojApiKey}`
      },
      body: JSON.stringify(body)
    });
    try {
      if (response.body === null)
        throw new Error("Response body is null");
      await this.readChatStream(response);
    } catch (err) {
      console.error(`Khoj chat response failed with
${err}`);
      let errorMsg = "Sorry, unable to get response from Khoj backend \u2764\uFE0F\u200D\u{1FA79}. Retry or contact developers for help at <a href=mailto:'team@khoj.dev'>team@khoj.dev</a> or <a href='https://discord.gg/BDgyabRM6e'>on Discord</a>";
      newResponseTextEl.textContent = errorMsg;
    }
  }
  flashStatusInChatInput(message) {
    let chatInput = this.contentEl.getElementsByClassName("khoj-chat-input")[0];
    let originalPlaceholder = chatInput.placeholder;
    chatInput.placeholder = message;
    setTimeout(() => {
      chatInput.placeholder = originalPlaceholder;
    }, 2e3);
  }
  async clearConversationHistory() {
    let chatBody = this.contentEl.getElementsByClassName("khoj-chat-body")[0];
    let response = await (0, import_obsidian6.request)({
      url: `${this.setting.khojUrl}/api/chat/history?client=obsidian`,
      method: "DELETE",
      headers: { "Authorization": `Bearer ${this.setting.khojApiKey}` }
    });
    try {
      let result = JSON.parse(response);
      if (result.status !== "ok") {
        throw new Error("Failed to clear conversation history");
      } else {
        let getChatHistoryStatus = await this.getChatHistory(chatBody);
        if (getChatHistoryStatus)
          chatBody.innerHTML = "";
        let statusMsg = getChatHistoryStatus ? result.message : "Failed to clear conversation history";
        this.flashStatusInChatInput(statusMsg);
      }
    } catch (err) {
      this.flashStatusInChatInput("Failed to clear conversation history");
    }
  }
  async speechToText(event) {
    var _a, _b;
    event.preventDefault();
    const transcribeButton = this.contentEl.getElementsByClassName("khoj-transcribe")[0];
    const chatInput = this.contentEl.getElementsByClassName("khoj-chat-input")[0];
    const sendButton = this.contentEl.getElementsByClassName("khoj-chat-send")[0];
    const generateRequestBody = async (audioBlob, boundary_string) => {
      const boundary = `------${boundary_string}`;
      const chunks = [];
      chunks.push(new TextEncoder().encode(`${boundary}\r
`));
      chunks.push(new TextEncoder().encode(`Content-Disposition: form-data; name="file"; filename="blob"\r
Content-Type: "application/octet-stream"\r
\r
`));
      chunks.push(await audioBlob.arrayBuffer());
      chunks.push(new TextEncoder().encode("\r\n"));
      await Promise.all(chunks);
      chunks.push(new TextEncoder().encode(`${boundary}--\r
`));
      return await new Blob(chunks).arrayBuffer();
    };
    const sendToServer = async (audioBlob) => {
      const boundary_string = `Boundary${Math.random().toString(36).slice(2)}`;
      const requestBody = await generateRequestBody(audioBlob, boundary_string);
      const response = await (0, import_obsidian6.requestUrl)({
        url: `${this.setting.khojUrl}/api/transcribe?client=obsidian`,
        method: "POST",
        headers: { "Authorization": `Bearer ${this.setting.khojApiKey}` },
        contentType: `multipart/form-data; boundary=----${boundary_string}`,
        body: requestBody
      });
      let noSpeechText = [
        "Thanks for watching!",
        "Thanks for watching.",
        "Thank you for watching!",
        "Thank you for watching.",
        "You",
        "Bye."
      ];
      let noSpeech = false;
      if (response.status === 200) {
        console.log(response);
        noSpeech = noSpeechText.includes(response.json.text.trimStart());
        if (!noSpeech)
          chatInput.value += response.json.text.trimStart();
        this.autoResize();
      } else if (response.status === 501) {
        throw new Error("\u26D4\uFE0F Configure speech-to-text model on server.");
      } else if (response.status === 422) {
        throw new Error("\u26D4\uFE0F Audio file to large to process.");
      } else {
        throw new Error("\u26D4\uFE0F Failed to transcribe audio.");
      }
      if (chatInput.value.length === 0 || noSpeech)
        return;
      (0, import_obsidian6.setIcon)(sendButton, "stop-circle");
      let stopSendButtonImg = sendButton.getElementsByClassName("lucide-stop-circle")[0];
      stopSendButtonImg.addEventListener("click", (_) => {
        this.cancelSendMessage();
      });
      stopSendButtonImg.getElementsByTagName("circle")[0].style.animation = "countdown 3s linear 1 forwards";
      stopSendButtonImg.getElementsByTagName("circle")[0].style.color = "var(--icon-color-active)";
      this.sendMessageTimeout = setTimeout(() => {
        (0, import_obsidian6.setIcon)(sendButton, "arrow-up-circle");
        let sendImg = sendButton.getElementsByClassName("lucide-arrow-up-circle")[0];
        sendImg.addEventListener("click", async (_) => {
          await this.chat();
        });
        this.chat(true);
      }, 3e3);
    };
    const handleRecording = (stream) => {
      const audioChunks = [];
      const recordingConfig = { mimeType: "audio/webm" };
      this.mediaRecorder = new MediaRecorder(stream, recordingConfig);
      this.mediaRecorder.addEventListener("dataavailable", function(event2) {
        if (event2.data.size > 0)
          audioChunks.push(event2.data);
      });
      this.mediaRecorder.addEventListener("stop", async function() {
        const audioBlob = new Blob(audioChunks, { type: "audio/webm" });
        await sendToServer(audioBlob);
      });
      this.mediaRecorder.start();
      transcribeButton.classList.add("loading-encircle");
    };
    if (!this.mediaRecorder || this.mediaRecorder.state === "inactive" || event.type === "touchstart" || event.type === "mousedown" || event.type === "keydown") {
      this.voiceChatActive = true;
      (_a = navigator.mediaDevices.getUserMedia({ audio: true })) == null ? void 0 : _a.then(handleRecording).catch((e) => {
        this.flashStatusInChatInput("\u26D4\uFE0F Failed to access microphone");
      });
    } else if (((_b = this.mediaRecorder) == null ? void 0 : _b.state) === "recording" || event.type === "touchend" || event.type === "touchcancel" || event.type === "mouseup" || event.type === "keyup") {
      this.voiceChatActive = false;
      this.mediaRecorder.stop();
      this.mediaRecorder.stream.getTracks().forEach((track) => track.stop());
      this.mediaRecorder = void 0;
      transcribeButton.classList.remove("loading-encircle");
      (0, import_obsidian6.setIcon)(transcribeButton, "mic");
    }
  }
  cancelSendMessage() {
    clearTimeout(this.sendMessageTimeout);
    let sendButton = this.contentEl.getElementsByClassName("khoj-chat-send")[0];
    (0, import_obsidian6.setIcon)(sendButton, "arrow-up-circle");
    let sendImg = sendButton.getElementsByClassName("lucide-arrow-up-circle")[0];
    sendImg.addEventListener("click", async (_) => {
      await this.chat();
    });
  }
  incrementalChat(event) {
    if (this.modeDropdown && this.modeDropdown.style.display !== "none" && event.key === "Enter") {
      event.preventDefault();
      const options = this.modeDropdown.querySelectorAll(".khoj-mode-dropdown-option");
      const visibleOptions = Array.from(options).filter((option) => option.style.display !== "none");
      if (this.selectedOptionIndex >= 0 && this.selectedOptionIndex < visibleOptions.length) {
        const selectedOption = visibleOptions[this.selectedOptionIndex];
        const index = parseInt(selectedOption.getAttribute("data-index") || "0");
        const chatInput2 = this.contentEl.getElementsByClassName("khoj-chat-input")[0];
        chatInput2.value = this.chatModes[index].command + " ";
        chatInput2.focus();
        this.currentUserInput = chatInput2.value;
        this.hideModeDropdown();
      } else if (visibleOptions.length === 1) {
        const onlyOption = visibleOptions[0];
        const index = parseInt(onlyOption.getAttribute("data-index") || "0");
        const chatInput2 = this.contentEl.getElementsByClassName("khoj-chat-input")[0];
        chatInput2.value = this.chatModes[index].command + " ";
        chatInput2.focus();
        this.currentUserInput = chatInput2.value;
        this.hideModeDropdown();
      }
      return;
    }
    const chatInput = this.contentEl.getElementsByClassName("khoj-chat-input")[0];
    const trimmedValue = chatInput.value.trim();
    const isOnlyModeCommand = this.chatModes.some((mode) => trimmedValue === mode.command || trimmedValue === mode.command + " ");
    if (event.key === "Enter" && !event.shiftKey) {
      if (!trimmedValue || isOnlyModeCommand) {
        event.preventDefault();
        return;
      }
      event.preventDefault();
      this.chat();
    }
  }
  onChatInput() {
    const chatInput = this.contentEl.getElementsByClassName("khoj-chat-input")[0];
    chatInput.value = chatInput.value.trimStart();
    this.currentMessageIndex = -1;
    this.currentUserInput = chatInput.value;
    if (chatInput.value.startsWith("/")) {
      this.showModeDropdown(chatInput);
      this.selectedOptionIndex = -1;
    } else if (this.modeDropdown) {
      this.hideModeDropdown();
    }
    this.autoResize();
  }
  autoResize() {
    const chatInput = this.contentEl.getElementsByClassName("khoj-chat-input")[0];
    if (this.isStreaming) {
      return;
    }
    chatInput.style.height = "auto";
    const maxHeight = 400;
    const newHeight = Math.min(chatInput.scrollHeight, maxHeight);
    chatInput.style.height = newHeight + "px";
    if (chatInput.scrollHeight > maxHeight) {
      chatInput.style.overflowY = "auto";
    } else {
      chatInput.style.overflowY = "hidden";
    }
    if (this.modeDropdown && this.modeDropdown.style.display !== "none") {
      const inputRect = chatInput.getBoundingClientRect();
      const containerRect = this.contentEl.getBoundingClientRect();
      this.modeDropdown.style.left = `${inputRect.left - containerRect.left}px`;
      this.modeDropdown.style.top = `${inputRect.top - containerRect.top - 4}px`;
      this.modeDropdown.style.width = `${inputRect.width}px`;
    }
  }
  scrollChatToBottom() {
    const chat_body_el = this.contentEl.getElementsByClassName("khoj-chat-body")[0];
    if (!!chat_body_el)
      chat_body_el.scrollTop = chat_body_el.scrollHeight;
  }
  createLoadingEllipse() {
    let loadingEllipsis = this.contentEl.createEl("div");
    loadingEllipsis.classList.add("lds-ellipsis");
    let firstEllipsis = this.contentEl.createEl("div");
    firstEllipsis.classList.add("lds-ellipsis-item");
    let secondEllipsis = this.contentEl.createEl("div");
    secondEllipsis.classList.add("lds-ellipsis-item");
    let thirdEllipsis = this.contentEl.createEl("div");
    thirdEllipsis.classList.add("lds-ellipsis-item");
    let fourthEllipsis = this.contentEl.createEl("div");
    fourthEllipsis.classList.add("lds-ellipsis-item");
    loadingEllipsis.appendChild(firstEllipsis);
    loadingEllipsis.appendChild(secondEllipsis);
    loadingEllipsis.appendChild(thirdEllipsis);
    loadingEllipsis.appendChild(fourthEllipsis);
    return loadingEllipsis;
  }
  handleStreamResponse(newResponseElement, rawResponse, loadingEllipsis, replace = true) {
    if (!newResponseElement)
      return;
    if (newResponseElement.getElementsByClassName("lds-ellipsis").length > 0 && loadingEllipsis)
      newResponseElement.removeChild(loadingEllipsis);
    newResponseElement.innerHTML = "";
    const messageEl = this.formatHTMLMessage(rawResponse, false, true);
    messageEl.classList.add("khoj-message-new-content");
    newResponseElement.appendChild(messageEl);
    setTimeout(() => {
      newResponseElement.classList.remove("khoj-message-new-content");
    }, 300);
  }
  handleImageResponse(imageJson, rawResponse) {
    var _a, _b;
    if (imageJson.image) {
      const inferredQuery = (_b = (_a = imageJson.inferredQueries) == null ? void 0 : _a[0]) != null ? _b : "generated image";
      if (imageJson.intentType === "text-to-image") {
        rawResponse += `![generated_image](data:image/png;base64,${imageJson.image})`;
      } else if (imageJson.intentType === "text-to-image2") {
        rawResponse += `![generated_image](${imageJson.image})`;
      } else if (imageJson.intentType === "text-to-image-v3") {
        rawResponse = `![generated_image](${imageJson.image})`;
      } else if (imageJson.intentType === "excalidraw") {
        const domain = this.setting.khojUrl.endsWith("/") ? this.setting.khojUrl : `${this.setting.khojUrl}/`;
        const redirectMessage = `Hey, I'm not ready to show you diagrams yet here. But you can view it in ${domain}`;
        rawResponse += redirectMessage;
      }
      if (inferredQuery) {
        rawResponse += `

**Inferred Query**:

${inferredQuery}`;
      }
    } else if (imageJson.images) {
      imageJson.images.forEach((image) => {
        rawResponse += `![generated_image](${image})

`;
      });
    } else if (imageJson.excalidrawDiagram) {
      const domain = this.setting.khojUrl.endsWith("/") ? this.setting.khojUrl : `${this.setting.khojUrl}/`;
      const redirectMessage = `Hey, I'm not ready to show you diagrams yet here. But you can view it in ${domain}`;
      rawResponse += redirectMessage;
    } else if (imageJson.mermaidjsDiagram) {
      rawResponse += imageJson.mermaidjsDiagram;
    }
    if (imageJson.detail)
      rawResponse += imageJson.detail;
    return rawResponse;
  }
  finalizeChatBodyResponse(references, newResponseElement, turnId) {
    var _a, _b, _c;
    if (!!newResponseElement && references != null && Object.keys(references).length > 0) {
      newResponseElement.appendChild(this.createReferenceSection(references));
    }
    if (!!newResponseElement && turnId) {
      (_a = newResponseElement.parentElement) == null ? void 0 : _a.setAttribute("data-turnid", turnId);
      (_c = (_b = newResponseElement.parentElement) == null ? void 0 : _b.previousElementSibling) == null ? void 0 : _c.setAttribute("data-turnid", turnId);
    }
    this.scrollChatToBottom();
    let chatInput = this.contentEl.getElementsByClassName("khoj-chat-input")[0];
    if (chatInput)
      chatInput.removeAttribute("disabled");
  }
  createReferenceSection(references) {
    let referenceSection = this.contentEl.createEl("div");
    referenceSection.classList.add("reference-section");
    referenceSection.classList.add("collapsed");
    let numReferences = 0;
    if (references.hasOwnProperty("notes")) {
      numReferences += references["notes"].length;
      references["notes"].forEach((reference, index) => {
        let polishedReference = this.generateReference(referenceSection, reference, index);
        referenceSection.appendChild(polishedReference);
      });
    }
    if (references.hasOwnProperty("online")) {
      numReferences += this.processOnlineReferences(referenceSection, references["online"]);
    }
    let referenceExpandButton = this.contentEl.createEl("button");
    referenceExpandButton.classList.add("reference-expand-button");
    referenceExpandButton.innerHTML = numReferences == 1 ? "1 reference" : `${numReferences} references`;
    referenceExpandButton.addEventListener("click", function() {
      if (referenceSection.classList.contains("collapsed")) {
        referenceSection.classList.remove("collapsed");
        referenceSection.classList.add("expanded");
      } else {
        referenceSection.classList.add("collapsed");
        referenceSection.classList.remove("expanded");
      }
    });
    let referencesDiv = this.contentEl.createEl("div");
    referencesDiv.classList.add("references");
    referencesDiv.appendChild(referenceExpandButton);
    referencesDiv.appendChild(referenceSection);
    return referencesDiv;
  }
  handleArrowKeys(event) {
    const chatInput = this.contentEl.getElementsByClassName("khoj-chat-input")[0];
    if (this.modeDropdown && this.modeDropdown.style.display !== "none") {
      const options = this.modeDropdown.querySelectorAll(".khoj-mode-dropdown-option");
      const visibleOptions = Array.from(options).filter((option) => option.style.display !== "none");
      if (visibleOptions.length === 0) {
        this.hideModeDropdown();
        return;
      }
      switch (event.key) {
        case "ArrowDown":
          event.preventDefault();
          if (this.selectedOptionIndex < 0) {
            this.selectedOptionIndex = 0;
          } else {
            this.selectedOptionIndex = Math.min(this.selectedOptionIndex + 1, visibleOptions.length - 1);
          }
          this.highlightVisibleOption(visibleOptions);
          break;
        case "ArrowUp":
          event.preventDefault();
          if (this.selectedOptionIndex < 0) {
            this.selectedOptionIndex = visibleOptions.length - 1;
          } else {
            this.selectedOptionIndex = Math.max(this.selectedOptionIndex - 1, 0);
          }
          this.highlightVisibleOption(visibleOptions);
          break;
        case "Enter":
          break;
        case "Escape":
          event.preventDefault();
          this.hideModeDropdown();
          break;
      }
      if (event.key === "ArrowUp" || event.key === "ArrowDown") {
        return;
      }
    }
  }
  highlightVisibleOption(visibleOptions) {
    var _a;
    const allOptions = (_a = this.modeDropdown) == null ? void 0 : _a.querySelectorAll(".khoj-mode-dropdown-option");
    if (!allOptions)
      return;
    allOptions.forEach((option) => {
      option.classList.remove("khoj-mode-dropdown-option-selected");
    });
    if (this.selectedOptionIndex >= 0 && this.selectedOptionIndex < visibleOptions.length) {
      const selectedOption = visibleOptions[this.selectedOptionIndex];
      selectedOption.classList.add("khoj-mode-dropdown-option-selected");
      if (this.modeDropdown) {
        const container = this.modeDropdown;
        if (selectedOption.offsetTop < container.scrollTop) {
          container.scrollTop = selectedOption.offsetTop;
        } else if (selectedOption.offsetTop + selectedOption.offsetHeight > container.scrollTop + container.offsetHeight) {
          container.scrollTop = selectedOption.offsetTop + selectedOption.offsetHeight - container.offsetHeight;
        }
      }
    }
  }
  async deleteMessage(messageEl, skipPaired = false, skipBackend = false) {
    const messageContainer = messageEl.closest(".khoj-chat-message");
    if (!messageContainer)
      return;
    let pairedMessageContainer = null;
    if (!skipPaired) {
      const messages = Array.from(document.getElementsByClassName("khoj-chat-message"));
      const currentIndex = messages.indexOf(messageContainer);
      if (messageContainer.classList.contains("you") && currentIndex < messages.length - 1) {
        pairedMessageContainer = messages[currentIndex + 1];
      } else if (messageContainer.classList.contains("khoj") && currentIndex > 0) {
        pairedMessageContainer = messages[currentIndex - 1];
      }
    }
    messageContainer.classList.add("deleting");
    if (pairedMessageContainer) {
      pairedMessageContainer.classList.add("deleting");
    }
    setTimeout(async () => {
      const turnId = messageContainer.getAttribute("data-turnid");
      messageContainer.remove();
      if (pairedMessageContainer) {
        pairedMessageContainer.remove();
      }
      if (!skipBackend && turnId) {
        const chatBodyEl = this.contentEl.getElementsByClassName("khoj-chat-body")[0];
        const conversationId = chatBodyEl.dataset.conversationId;
        if (!conversationId)
          return;
        try {
          const response = await fetch(`${this.setting.khojUrl}/api/chat/conversation/message`, {
            method: "DELETE",
            headers: {
              "Content-Type": "application/json",
              "Authorization": `Bearer ${this.setting.khojApiKey}`
            },
            body: JSON.stringify({
              conversation_id: conversationId,
              turn_id: turnId
            })
          });
          if (!response.ok) {
            console.error("Failed to delete message from backend:", await response.text());
            this.flashStatusInChatInput("Failed to delete message");
          }
        } catch (error) {
          console.error("Error deleting message:", error);
          this.flashStatusInChatInput("Error deleting message");
        }
      }
    }, 300);
  }
  async fetchAgents() {
    try {
      const response = await fetch(`${this.setting.khojUrl}/api/agents`, {
        headers: {
          "Authorization": `Bearer ${this.setting.khojApiKey}`
        }
      });
      if (response.ok) {
        this.agents = await response.json();
      } else {
        console.error("Failed to fetch agents:", response.statusText);
      }
    } catch (error) {
      console.error("Error fetching agents:", error);
    }
  }
  async getOpenFilesContent() {
    return this.fileInteractions.getOpenFilesContent(this.fileAccessMode);
  }
  parseEditBlocks(message) {
    return this.fileInteractions.parseEditBlocks(message);
  }
  async applyEditBlocks(editBlocks) {
    if (editBlocks.length === 0)
      return;
    const { editResults, fileBackups } = await this.fileInteractions.applyEditBlocks(editBlocks, (blockToRetry) => {
      if (this.editRetryCount < this.maxEditRetries) {
        this.handleEditRetry(blockToRetry);
      }
    });
    const chatBodyEl = this.contentEl.getElementsByClassName("khoj-chat-body")[0];
    const lastMessage = chatBodyEl.lastElementChild;
    if (lastMessage) {
      const buttonsContainer = lastMessage.createDiv({ cls: "edit-confirmation-buttons" });
      const statusContainer = buttonsContainer.createDiv({ cls: "edit-status-container" });
      const statusSummary = statusContainer.createDiv({ cls: "edit-status-summary" });
      const successCount = editResults.filter((r) => r.success).length;
      if (successCount === editResults.length) {
        statusSummary.innerHTML = `All edits applied successfully`;
        statusSummary.addClass("success");
      } else if (successCount === 0) {
        statusSummary.innerHTML = `No edits were applied`;
        statusSummary.addClass("error");
      } else {
        statusSummary.innerHTML = `${successCount}/${editResults.length} edits applied successfully`;
        statusSummary.addClass(successCount > 0 ? "success" : "error");
      }
      if (editResults.some((r) => !r.success)) {
        const errorDetails = editResults.filter((r) => !r.success).map((r) => {
          if (r.error && r.error.includes("Other edits in the group failed")) {
            return `\u2022 ${r.block.note}: Not applied due to atomic validation failure`;
          }
          return `\u2022 ${r.block.note}: ${r.error}`;
        }).join("\n");
        statusSummary.title = `Failed edits:
${errorDetails}`;
      }
      const applyButton = buttonsContainer.createEl("button", {
        text: "Apply",
        cls: ["edit-confirm-button", "edit-apply-button"]
      });
      const cancelButton = buttonsContainer.createEl("button", {
        text: "Cancel",
        cls: ["edit-confirm-button", "edit-cancel-button"]
      });
      buttonsContainer.scrollIntoView({ behavior: "smooth", block: "center" });
      this.setupConfirmationButtons(applyButton, cancelButton, fileBackups, lastMessage, buttonsContainer);
    }
  }
  setupConfirmationButtons(applyButton, cancelButton, fileBackups, lastMessage, buttonsContainer) {
    applyButton.addEventListener("click", async () => {
      try {
        for (const [filePath, originalContent] of fileBackups) {
          const file = this.app.vault.getAbstractFileByPath(filePath);
          if (file && file instanceof import_obsidian6.TFile) {
            const currentContent = await this.app.vault.read(file);
            let finalContent = currentContent;
            finalContent = finalContent.replace(/~~[^~]*~~\n?(?=~~)/g, "");
            finalContent = finalContent.replace(/~~[^~]*~~/g, "");
            finalContent = finalContent.replace(/==/g, "");
            await this.app.vault.modify(file, finalContent);
          }
        }
        const successMessage = lastMessage.createDiv({ cls: "edit-status-message success" });
        successMessage.textContent = "Changes applied successfully";
        setTimeout(() => successMessage.remove(), 3e3);
      } catch (error) {
        console.error("Error applying changes:", error);
        const errorMessage = lastMessage.createDiv({ cls: "edit-status-message error" });
        errorMessage.textContent = "Error applying changes";
        setTimeout(() => errorMessage.remove(), 3e3);
      } finally {
        buttonsContainer.remove();
      }
    });
    cancelButton.addEventListener("click", async () => {
      try {
        for (const [filePath, originalContent] of fileBackups) {
          const file = this.app.vault.getAbstractFileByPath(filePath);
          if (file && file instanceof import_obsidian6.TFile) {
            await this.app.vault.modify(file, originalContent);
          }
        }
        const successMessage = lastMessage.createDiv({ cls: "edit-status-message success" });
        successMessage.textContent = "Changes cancelled successfully";
        setTimeout(() => successMessage.remove(), 3e3);
      } catch (error) {
        console.error("Error cancelling changes:", error);
        const errorMessage = lastMessage.createDiv({ cls: "edit-status-message error" });
        errorMessage.textContent = "Error cancelling changes";
        setTimeout(() => errorMessage.remove(), 3e3);
      } finally {
        buttonsContainer.remove();
      }
    });
  }
  convertCommandsToEmojis(message) {
    const modeMatch = this.chatModes.find((mode) => message.startsWith(mode.command));
    if (modeMatch) {
      return message.replace(modeMatch.command, `[${modeMatch.label}]`);
    }
    return message;
  }
  async applyPendingEdits() {
    const chatBodyEl = this.contentEl.getElementsByClassName("khoj-chat-body")[0];
    const lastMessage = chatBodyEl.lastElementChild;
    if (!lastMessage)
      return;
    const buttonsContainer = lastMessage.querySelector(".edit-confirmation-buttons");
    if (!buttonsContainer)
      return;
    const applyButton = buttonsContainer.querySelector(".edit-apply-button");
    if (applyButton instanceof HTMLElement) {
      applyButton.click();
    }
  }
  async cancelPendingEdits() {
    const chatBodyEl = this.contentEl.getElementsByClassName("khoj-chat-body")[0];
    const lastMessage = chatBodyEl.lastElementChild;
    if (!lastMessage)
      return;
    const buttonsContainer = lastMessage.querySelector(".edit-confirmation-buttons");
    if (!buttonsContainer)
      return;
    const cancelButton = buttonsContainer.querySelector(".edit-cancel-button");
    if (cancelButton instanceof HTMLElement) {
      cancelButton.click();
    }
  }
  transformEditBlocks(message) {
    return this.fileInteractions.transformEditBlocks(message);
  }
  async handleEditRetry(errorBlock) {
    var _a, _b, _c, _d;
    this.editRetryCount++;
    let errorDetails = "";
    if (((_a = errorBlock.error) == null ? void 0 : _a.type) === "missing_field") {
      errorDetails = `Missing required fields: ${errorBlock.error.message}
`;
      errorDetails += `Please include all required fields:
${errorBlock.error.details}
`;
    } else if (((_b = errorBlock.error) == null ? void 0 : _b.type) === "invalid_format") {
      errorDetails = `The JSON format is invalid: ${errorBlock.error.message}
`;
      errorDetails += "Please check the syntax and provide a valid JSON edit block.\n";
    } else {
      errorDetails = `Error: ${((_c = errorBlock.error) == null ? void 0 : _c.message) || "Unknown error"}
`;
      if ((_d = errorBlock.error) == null ? void 0 : _d.details) {
        errorDetails += `Details: ${errorBlock.error.details}
`;
      }
    }
    const chatBodyEl = this.contentEl.getElementsByClassName("khoj-chat-body")[0];
    const retryContainer = chatBodyEl.createDiv({ cls: "khoj-retry-container" });
    const retryBadge = retryContainer.createDiv({ cls: "khoj-retry-badge" });
    const retryIcon = retryBadge.createSpan({ cls: "retry-icon" });
    (0, import_obsidian6.setIcon)(retryIcon, "refresh-cw");
    retryBadge.createSpan({ text: "Try again to apply changes" });
    retryBadge.createSpan({
      cls: "retry-count",
      text: `Attempt ${this.editRetryCount}/${this.maxEditRetries}`
    });
    retryBadge.setAttribute("aria-label", errorDetails);
    const hoverEditor = this.app.plugins.plugins["obsidian-hover-editor"];
    if (hoverEditor) {
      new hoverEditor.HoverPopover(this.app, retryBadge, errorDetails);
    }
    retryBadge.scrollIntoView({ behavior: "smooth", block: "center" });
    const retryPrompt = `/general I noticed some issues with the edit block. Please fix the following and provide a corrected version (retry ${this.editRetryCount}/${this.maxEditRetries}):

${errorDetails}

Please provide a new edit block that fixes these issues. Make sure to follow the exact format required.`;
    await this.getChatResponse(retryPrompt, "", false, false);
  }
  showModeDropdown(inputEl) {
    if (!this.modeDropdown) {
      this.modeDropdown = this.contentEl.createDiv({
        cls: "khoj-mode-dropdown"
      });
      const inputRect = inputEl.getBoundingClientRect();
      const containerRect = this.contentEl.getBoundingClientRect();
      this.modeDropdown.style.position = "absolute";
      this.modeDropdown.style.left = `${inputRect.left - containerRect.left}px`;
      this.modeDropdown.style.top = `${inputRect.top - containerRect.top - 4}px`;
      this.modeDropdown.style.width = `${inputRect.width}px`;
      this.modeDropdown.style.zIndex = "1000";
      this.modeDropdown.style.transform = "translateY(-100%)";
      this.chatModes.forEach((mode, index) => {
        const option = this.modeDropdown.createDiv({
          cls: "khoj-mode-dropdown-option",
          attr: {
            "data-index": index.toString(),
            "data-command": mode.command
          }
        });
        const emojiSpan = option.createSpan({
          cls: "khoj-mode-dropdown-emoji"
        });
        (0, import_obsidian6.setIcon)(emojiSpan, mode.iconName);
        option.createSpan({
          cls: "khoj-mode-dropdown-label",
          text: ` ${mode.label} `
        });
        option.createSpan({
          cls: "khoj-mode-dropdown-command",
          text: `(${mode.command})`
        });
        option.addEventListener("click", () => {
          inputEl.value = mode.command + " ";
          inputEl.focus();
          this.currentUserInput = inputEl.value;
          this.hideModeDropdown();
        });
      });
      document.addEventListener("click", (e) => {
        if (this.modeDropdown && !this.modeDropdown.contains(e.target) && e.target !== inputEl) {
          this.hideModeDropdown();
        }
      });
    } else {
      this.modeDropdown.style.display = "block";
    }
    this.filterDropdownOptions(inputEl.value);
  }
  filterDropdownOptions(inputValue) {
    if (!this.modeDropdown)
      return;
    const options = this.modeDropdown.querySelectorAll(".khoj-mode-dropdown-option");
    let visibleOptionsCount = 0;
    options.forEach((option) => {
      const command = option.getAttribute("data-command") || "";
      if (inputValue.startsWith("/") && inputValue.length > 1) {
        if (command.toLowerCase().startsWith(inputValue.toLowerCase())) {
          option.style.display = "flex";
          visibleOptionsCount++;
        } else {
          option.style.display = "none";
        }
      } else {
        option.style.display = "flex";
        visibleOptionsCount++;
      }
    });
    if (visibleOptionsCount === 0) {
      this.hideModeDropdown();
    }
    this.selectedOptionIndex = -1;
  }
  hideModeDropdown() {
    if (this.modeDropdown) {
      this.modeDropdown.style.display = "none";
      this.selectedOptionIndex = -1;
    }
  }
};

// src/similar_view.ts
var import_obsidian7 = require("obsidian");
init_utils();
var KhojSimilarView = class extends KhojPaneView {
  constructor(leaf, plugin) {
    super(leaf, plugin);
    this.currentController = null;
    this.isLoading = false;
    this.currentFile = null;
    this.component = this;
  }
  getViewType() {
    return "khoj-similar-view" /* SIMILAR */;
  }
  getDisplayText() {
    return "Khoj Similar Documents";
  }
  getIcon() {
    return "search";
  }
  async onOpen() {
    await super.onOpen();
    const { contentEl } = this;
    const mainContainerEl = contentEl.createDiv({ cls: "khoj-similar-container" });
    const searchContainerEl = mainContainerEl.createDiv({ cls: "khoj-similar-search-container" });
    this.searchInputEl = searchContainerEl.createEl("input", {
      cls: "khoj-similar-search-input",
      attr: {
        type: "text",
        placeholder: "Search or use current file"
      }
    });
    const refreshButtonEl = searchContainerEl.createEl("button", {
      cls: "khoj-similar-refresh-button"
    });
    (0, import_obsidian7.setIcon)(refreshButtonEl, "refresh-cw");
    refreshButtonEl.createSpan({ text: "Refresh" });
    refreshButtonEl.addEventListener("click", () => {
      this.updateSimilarDocuments();
    });
    this.resultsContainerEl = mainContainerEl.createDiv({ cls: "khoj-similar-results" });
    this.loadingEl = mainContainerEl.createDiv({ cls: "search-loading" });
    const spinnerEl = this.loadingEl.createDiv({ cls: "search-loading-spinner" });
    this.loadingEl.style.position = "absolute";
    this.loadingEl.style.top = "50%";
    this.loadingEl.style.left = "50%";
    this.loadingEl.style.transform = "translate(-50%, -50%)";
    this.loadingEl.style.zIndex = "1000";
    this.loadingEl.style.display = "none";
    this.registerFileActiveHandler();
    this.searchInputEl.addEventListener("keydown", (e) => {
      if (e.key === "Enter") {
        this.getSimilarDocuments(this.searchInputEl.value);
      }
    });
    this.updateSimilarDocuments();
  }
  registerFileActiveHandler() {
    if (this.fileWatcher) {
      this.app.workspace.off("file-open", this.fileWatcher);
    }
    this.fileWatcher = this.app.workspace.on("file-open", (file) => {
      if (file) {
        this.currentFile = file;
        this.updateSimilarDocuments();
      }
    });
    this.register(() => {
      this.app.workspace.off("file-open", this.fileWatcher);
    });
  }
  async updateSimilarDocuments() {
    const file = this.app.workspace.getActiveFile();
    if (!file) {
      this.updateUI("no-file");
      return;
    }
    if (file.extension !== "md") {
      this.updateUI("unsupported-file");
      return;
    }
    this.currentFile = file;
    const content = await this.app.vault.read(file);
    await this.getSimilarDocuments(content);
  }
  async getSimilarDocuments(query) {
    if (!query.trim()) {
      this.isLoading = false;
      this.updateLoadingState();
      this.updateUI("empty-query");
      return [];
    }
    this.isLoading = true;
    this.updateLoadingState();
    this.updateUI("loading");
    if (this.currentController) {
      this.currentController.abort();
    }
    try {
      this.currentController = new AbortController();
      let encodedQuery = encodeURIComponent(query);
      let searchUrl = `${this.setting.khojUrl}/api/search?q=${encodedQuery}&n=${this.setting.resultsCount}&r=true&client=obsidian`;
      let headers = {
        "Authorization": `Bearer ${this.setting.khojApiKey}`
      };
      const response = await fetch(searchUrl, {
        headers,
        signal: this.currentController.signal
      });
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      const data = await response.json();
      let results = data.filter((result) => {
        if (this.currentFile && result.additional.file.endsWith(this.currentFile.path)) {
          return false;
        }
        return true;
      }).map((result) => {
        return {
          entry: result.entry,
          file: result.additional.file,
          inVault: this.isFileInVault(result.additional.file)
        };
      }).sort((a, b) => {
        if (a.inVault === b.inVault)
          return 0;
        return a.inVault ? -1 : 1;
      });
      this.isLoading = false;
      this.updateLoadingState();
      this.renderResults(results);
      return results;
    } catch (error) {
      if (error.name === "AbortError") {
        return [];
      }
      console.error("Search error:", error);
      this.isLoading = false;
      this.updateLoadingState();
      this.updateUI("error", error.message);
      return [];
    }
  }
  isFileInVault(filePath) {
    const files = this.app.vault.getFiles();
    return files.some((file) => filePath.endsWith(file.path));
  }
  renderResults(results) {
    this.resultsContainerEl.empty();
    if (results.length === 0) {
      this.updateUI("no-results");
      return;
    }
    this.resultsContainerEl.createEl("div", {
      cls: "khoj-results-count",
      text: `Found ${results.length} similar document${results.length > 1 ? "s" : ""}`
    });
    const resultsListEl = this.resultsContainerEl.createEl("div", { cls: "khoj-similar-results-list" });
    results.forEach(async (result) => {
      const resultEl = resultsListEl.createEl("div", { cls: "khoj-similar-result-item" });
      let os_path_separator = result.file.includes("\\") ? "\\" : "/";
      let filename = result.file.split(os_path_separator).pop();
      const headerEl = resultEl.createEl("div", { cls: "khoj-similar-result-header" });
      const fileEl = headerEl.createEl("div", {
        cls: `khoj-result-file ${result.inVault ? "in-vault" : "not-in-vault"}`
      });
      fileEl.setText(filename != null ? filename : "");
      if (!result.inVault) {
        fileEl.createSpan({
          text: " (not in vault)",
          cls: "khoj-result-file-status"
        });
      }
      const moreContextButton = headerEl.createEl("button", {
        cls: "khoj-more-context-button"
      });
      moreContextButton.createSpan({ text: "More context" });
      (0, import_obsidian7.setIcon)(moreContextButton.createSpan(), "chevron-down");
      const contentEl = resultEl.createEl("div", {
        cls: "khoj-result-entry khoj-similar-content-hidden"
      });
      let contentToRender = "";
      result.entry = result.entry.replace(/---[\n\r][\s\S]*---[\n\r]/, "");
      const lines_to_render = 8;
      let entry_snipped_indicator = result.entry.split("\n").length > lines_to_render ? " **...**" : "";
      let snipped_entry = result.entry.split("\n").slice(0, lines_to_render).join("\n");
      contentToRender = `${snipped_entry}${entry_snipped_indicator}`;
      await import_obsidian7.MarkdownRenderer.renderMarkdown(contentToRender, contentEl, result.file, this.component);
      moreContextButton.addEventListener("click", (e) => {
        e.stopPropagation();
        if (contentEl.classList.contains("khoj-similar-content-hidden")) {
          contentEl.classList.remove("khoj-similar-content-hidden");
          contentEl.classList.add("khoj-similar-content-visible");
          moreContextButton.empty();
          moreContextButton.createSpan({ text: "Less context" });
          (0, import_obsidian7.setIcon)(moreContextButton.createSpan(), "chevron-up");
        } else {
          contentEl.classList.remove("khoj-similar-content-visible");
          contentEl.classList.add("khoj-similar-content-hidden");
          moreContextButton.empty();
          moreContextButton.createSpan({ text: "More context" });
          (0, import_obsidian7.setIcon)(moreContextButton.createSpan(), "chevron-down");
        }
      });
      resultEl.addEventListener("click", (e) => {
        if (e.target === moreContextButton || moreContextButton.contains(e.target)) {
          return;
        }
        this.openResult(result);
      });
    });
  }
  async openResult(result) {
    if (!result.inVault) {
      new import_obsidian7.Notice("This file is not in your vault");
      return;
    }
    const mdFiles = this.app.vault.getMarkdownFiles();
    const binaryFiles = this.app.vault.getFiles().filter((file) => supportedBinaryFileTypes.includes(file.extension));
    let linkToEntry = getLinkToEntry(mdFiles.concat(binaryFiles), result.file, result.entry);
    if (linkToEntry) {
      this.app.workspace.openLinkText(linkToEntry, "");
    }
  }
  updateLoadingState() {
    this.loadingEl.style.display = this.isLoading ? "block" : "none";
  }
  updateUI(state, message) {
    if (state !== "loading") {
      this.resultsContainerEl.empty();
    }
    const messageEl = this.resultsContainerEl.createEl("div", { cls: "khoj-similar-message" });
    switch (state) {
      case "loading":
        break;
      case "no-file":
        messageEl.setText("No file is currently open. Open a markdown file to see similar documents.");
        break;
      case "unsupported-file":
        messageEl.setText("This file type is not supported. Only markdown files are supported.");
        break;
      case "no-results":
        messageEl.setText("No similar documents found.");
        break;
      case "error":
        messageEl.setText(`Error: ${message || "Failed to fetch similar documents"}`);
        break;
      case "empty-query":
        messageEl.setText("Please enter a search query or open a markdown file.");
        break;
    }
  }
};
KhojSimilarView.iconName = "search";

// src/main.ts
init_utils();
var Khoj = class extends import_obsidian8.Plugin {
  async onload() {
    await this.loadSettings();
    this.addCommand({
      id: "search",
      name: "Search",
      hotkeys: [{ modifiers: ["Ctrl", "Alt"], key: "S" }],
      callback: () => {
        new KhojSearchModal(this.app, this.settings).open();
      }
    });
    this.addCommand({
      id: "similar",
      name: "Find similar notes",
      hotkeys: [{ modifiers: ["Ctrl", "Alt"], key: "F" }],
      editorCallback: () => {
        this.activateView("khoj-similar-view" /* SIMILAR */);
      }
    });
    this.addCommand({
      id: "chat",
      name: "Chat",
      callback: () => {
        this.activateView("khoj-chat-view" /* CHAT */);
      }
    });
    this.addCommand({
      id: "similar-view",
      name: "Open Similar Documents View",
      callback: () => {
        this.activateView("khoj-similar-view" /* SIMILAR */);
      }
    });
    this.addCommand({
      id: "new-chat",
      name: "New Chat",
      hotkeys: [{ modifiers: ["Ctrl", "Alt"], key: "N" }],
      callback: async () => {
        await this.activateView("khoj-chat-view" /* CHAT */);
        setTimeout(() => {
          const chatView = this.app.workspace.getActiveViewOfType(KhojChatView);
          if (chatView) {
            chatView.createNewConversation();
          }
        }, 100);
      }
    });
    this.addCommand({
      id: "conversation-history",
      name: "Show Conversation History",
      hotkeys: [{ modifiers: ["Ctrl", "Alt"], key: "O" }],
      callback: () => {
        this.activateView("khoj-chat-view" /* CHAT */).then(() => {
          const chatView = this.app.workspace.getActiveViewOfType(KhojChatView);
          if (chatView) {
            chatView.toggleChatSessions();
          }
        });
      }
    });
    this.addCommand({
      id: "voice-capture",
      name: "Start Voice Capture",
      hotkeys: [{ modifiers: ["Ctrl", "Alt"], key: "V" }],
      callback: () => {
        this.activateView("khoj-chat-view" /* CHAT */).then(() => {
          const chatView = this.app.workspace.getActiveViewOfType(KhojChatView);
          if (chatView) {
            const toggleEvent = chatView.voiceChatActive ? "keyup" : "keydown";
            chatView.speechToText(new KeyboardEvent(toggleEvent));
          }
        });
      }
    });
    this.addCommand({
      id: "sync",
      name: "Sync new changes",
      callback: async () => {
        this.settings.lastSync = await updateContentIndex(this.app.vault, this.settings, this.settings.lastSync, false, true);
      }
    });
    this.addCommand({
      id: "apply-edits",
      name: "Apply pending edits",
      hotkeys: [{ modifiers: ["Ctrl", "Shift"], key: "Enter" }],
      callback: () => {
        const chatView = this.app.workspace.getActiveViewOfType(KhojChatView);
        if (chatView) {
          chatView.applyPendingEdits();
        }
      }
    });
    this.addCommand({
      id: "cancel-edits",
      name: "Cancel pending edits",
      hotkeys: [{ modifiers: ["Ctrl", "Shift"], key: "Backspace" }],
      callback: () => {
        const chatView = this.app.workspace.getActiveViewOfType(KhojChatView);
        if (chatView) {
          chatView.cancelPendingEdits();
        }
      }
    });
    this.registerView("khoj-chat-view" /* CHAT */, (leaf) => new KhojChatView(leaf, this));
    this.registerView("khoj-similar-view" /* SIMILAR */, (leaf) => new KhojSimilarView(leaf, this));
    this.addRibbonIcon("message-circle", "Khoj", (_) => {
      this.activateView("khoj-chat-view" /* CHAT */);
    });
    this.addSettingTab(new KhojSettingTab(this.app, this));
    this.startSyncTimer();
  }
  startSyncTimer() {
    if (this.indexingTimer) {
      clearInterval(this.indexingTimer);
    }
    this.indexingTimer = setInterval(async () => {
      if (this.settings.autoConfigure) {
        this.settings.lastSync = await updateContentIndex(this.app.vault, this.settings, this.settings.lastSync);
      }
    }, this.settings.syncInterval * 60 * 1e3);
  }
  restartSyncTimer() {
    this.startSyncTimer();
  }
  async loadSettings() {
    this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
    ({ connectedToBackend: this.settings.connectedToBackend } = await canConnectToBackend(this.settings.khojUrl, this.settings.khojApiKey, true));
  }
  async saveSettings() {
    await this.saveData(this.settings);
  }
  async onunload() {
    if (this.indexingTimer)
      clearInterval(this.indexingTimer);
    this.unload();
  }
  async activateView(viewType, existingLeaf) {
    const { workspace } = this.app;
    let leafToUse = null;
    if (existingLeaf && existingLeaf.view && (existingLeaf.view.getViewType() === "khoj-chat-view" /* CHAT */ || existingLeaf.view.getViewType() === "khoj-similar-view" /* SIMILAR */) && existingLeaf.view.getViewType() !== viewType) {
      leafToUse = existingLeaf;
      await leafToUse.setViewState({ type: viewType, active: true });
    } else {
      const leaves = workspace.getLeavesOfType(viewType);
      if (leaves.length > 0) {
        leafToUse = leaves[0];
      } else {
        leafToUse = existingLeaf && !(existingLeaf.view instanceof KhojPaneView) ? existingLeaf : workspace.getRightLeaf(false);
        if (leafToUse) {
          await leafToUse.setViewState({ type: viewType, active: true });
        } else {
          console.error("Khoj: Could not get a leaf to activate view.");
          return;
        }
      }
    }
    if (leafToUse) {
      workspace.revealLeaf(leafToUse);
      if (viewType === "khoj-chat-view" /* CHAT */) {
        const chatView = leafToUse.view;
        if (chatView instanceof KhojChatView) {
          const chatInput = chatView.containerEl.querySelector(".khoj-chat-input");
          chatInput == null ? void 0 : chatInput.focus();
        }
      }
    }
  }
};
/*! @license DOMPurify 3.2.6 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.2.6/LICENSE */

/* nosourcemap */