/* 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 __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); // node_modules/dompurify/dist/purify.js var require_purify = __commonJS({ "node_modules/dompurify/dist/purify.js"(exports, module2) { (function(global, factory) { typeof exports === "object" && typeof module2 !== "undefined" ? module2.exports = factory() : typeof define === "function" && define.amd ? define(factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, global.DOMPurify = factory()); })(exports, function() { "use strict"; const { entries, setPrototypeOf, isFrozen, getPrototypeOf, getOwnPropertyDescriptor } = Object; let { freeze, seal, create } = Object; let { 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); }; } const arrayForEach = unapply(Array.prototype.forEach); const arrayPop = unapply(Array.prototype.pop); const arrayPush = unapply(Array.prototype.push); const stringToLowerCase = unapply(String.prototype.toLowerCase); const stringToString = unapply(String.prototype.toString); const stringMatch = unapply(String.prototype.match); const stringReplace = unapply(String.prototype.replace); const stringIndexOf = unapply(String.prototype.indexOf); const stringTrim = unapply(String.prototype.trim); const objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty); const regExpTest = unapply(RegExp.prototype.test); const typeErrorCreate = unconstruct(TypeError); function unapply(func) { return function(thisArg) { 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; } const 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"]); const 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"]); const 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"]); const 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"]); const 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"]); const mathMlDisallowed = freeze(["maction", "maligngroup", "malignmark", "mlongdiv", "mscarries", "mscarry", "msgroup", "mstack", "msline", "msrow", "semantics", "annotation", "annotation-xml", "mprescripts", "none"]); const text = freeze(["#text"]); const 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"]); const svg = freeze(["accent-height", "accumulate", "additive", "alignment-baseline", "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", "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", "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", "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", "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"]); const 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"]); const xml = freeze(["xlink:href", "xml:id", "xlink:title", "xml:space", "xmlns:xlink"]); const MUSTACHE_EXPR = seal(/\{\{[\w\W]*|[\w\W]*\}\}/gm); const ERB_EXPR = seal(/<%[\w\W]*|[\w\W]*%>/gm); const TMPLIT_EXPR = seal(/\${[\w\W]*}/gm); const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]/); const ARIA_ATTR = seal(/^aria-[\-\w]+$/); const IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i); const IS_SCRIPT_OR_DATA = seal(/^(?:\w+script|data):/i); const ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g); const DOCTYPE_NAME = seal(/^html$/i); const CUSTOM_ELEMENT = seal(/^[a-z][.\w]*(-[.\w]+)+$/i); var EXPRESSIONS = /* @__PURE__ */ Object.freeze({ __proto__: null, MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR, DATA_ATTR, ARIA_ATTR, IS_ALLOWED_URI, IS_SCRIPT_OR_DATA, ATTR_WHITESPACE, DOCTYPE_NAME, CUSTOM_ELEMENT }); const 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 }; const getGlobal = function getGlobal2() { return typeof window === "undefined" ? null : window; }; const _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; } }; function createDOMPurify() { let window2 = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : getGlobal(); const DOMPurify2 = (root) => createDOMPurify(root); DOMPurify2.version = "3.1.5"; DOMPurify2.removed = []; if (!window2 || !window2.document || window2.document.nodeType !== NODE_TYPE.document) { 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 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 = {}; 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 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) : {}; FORBID_ATTR = objectHasOwnProperty(cfg, "FORBID_ATTR") ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : {}; 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; 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 MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ["mi", "mo", "mn", "ms", "mtext"]); const HTML_INTEGRATION_POINTS = addToSet({}, ["foreignobject", "annotation-xml"]); const COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, ["title", "style", "font", "a", "script"]); 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 { node.parentNode.removeChild(node); } catch (_) { node.remove(); } }; const _removeAttribute = function _removeAttribute2(name, node) { try { arrayPush(DOMPurify2.removed, { attribute: node.getAttributeNode(name), from: node }); } catch (_) { arrayPush(DOMPurify2.removed, { attribute: null, from: node }); } node.removeAttribute(name); if (name === "is" && !ALLOWED_ATTR[name]) { if (RETURN_DOM || RETURN_DOM_FRAGMENT) { try { _forceRemove(node); } catch (_) { } } else { try { node.setAttribute(name, ""); } catch (_) { } } } }; const _initDocument = function _initDocument2(dirty) { let doc = null; let leadingWhitespace = null; if (FORCE_BODY) { dirty = "" + dirty; } else { const matches = stringMatch(dirty, /^[\r\n\t ]+/); leadingWhitespace = matches && matches[0]; } if (PARSER_MEDIA_TYPE === "application/xhtml+xml" && NAMESPACE === HTML_NAMESPACE) { dirty = '' + dirty + ""; } 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(elm) { return elm instanceof HTMLFormElement && (typeof elm.nodeName !== "string" || typeof elm.textContent !== "string" || typeof elm.removeChild !== "function" || !(elm.attributes instanceof NamedNodeMap) || typeof elm.removeAttribute !== "function" || typeof elm.setAttribute !== "function" || typeof elm.namespaceURI !== "string" || typeof elm.insertBefore !== "function" || typeof elm.hasChildNodes !== "function"); }; const _isNode = function _isNode2(object) { return typeof Node === "function" && object instanceof Node; }; const _executeHook = function _executeHook2(entryPoint, currentNode, data) { if (!hooks[entryPoint]) { return; } arrayForEach(hooks[entryPoint], (hook) => { hook.call(DOMPurify2, currentNode, data, CONFIG); }); }; const _sanitizeElements = function _sanitizeElements2(currentNode) { let content = null; _executeHook("beforeSanitizeElements", currentNode, null); if (_isClobbered(currentNode)) { _forceRemove(currentNode); return true; } const tagName = transformCaseFunc(currentNode.nodeName); _executeHook("uponSanitizeElement", currentNode, { tagName, allowedTags: ALLOWED_TAGS }); if (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; } } _executeHook("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) { _executeHook("beforeSanitizeAttributes", currentNode, null); const { attributes } = currentNode; if (!attributes) { return; } const hookEvent = { attrName: "", attrValue: "", keepAttr: true, allowedAttributes: ALLOWED_ATTR }; let l = attributes.length; while (l--) { const attr = attributes[l]; const { name, namespaceURI, value: attrValue } = attr; const lcName = transformCaseFunc(name); let value = name === "value" ? attrValue : stringTrim(attrValue); hookEvent.attrName = lcName; hookEvent.attrValue = value; hookEvent.keepAttr = true; hookEvent.forceKeepAttr = void 0; _executeHook("uponSanitizeAttribute", currentNode, hookEvent); value = hookEvent.attrValue; if (hookEvent.forceKeepAttr) { continue; } _removeAttribute(name, currentNode); if (!hookEvent.keepAttr) { continue; } if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(/\/>/i, value)) { _removeAttribute(name, currentNode); continue; } if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|title)/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)) { continue; } if (SANITIZE_NAMED_PROPS && (lcName === "id" || lcName === "name")) { _removeAttribute(name, currentNode); value = SANITIZE_NAMED_PROPS_PREFIX + value; } 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; } } } } try { if (namespaceURI) { currentNode.setAttributeNS(namespaceURI, name, value); } else { currentNode.setAttribute(name, value); } if (_isClobbered(currentNode)) { _forceRemove(currentNode); } else { arrayPop(DOMPurify2.removed); } } catch (_) { } } _executeHook("afterSanitizeAttributes", currentNode, null); }; const _sanitizeShadowDOM = function _sanitizeShadowDOM2(fragment) { let shadowNode = null; const shadowIterator = _createNodeIterator(fragment); _executeHook("beforeSanitizeShadowDOM", fragment, null); while (shadowNode = shadowIterator.nextNode()) { _executeHook("uponSanitizeShadowNode", shadowNode, null); if (_sanitizeElements(shadowNode)) { continue; } if (shadowNode.content instanceof DocumentFragment) { _sanitizeShadowDOM2(shadowNode.content); } _sanitizeAttributes(shadowNode); } _executeHook("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()) { if (_sanitizeElements(currentNode)) { continue; } if (currentNode.content instanceof DocumentFragment) { _sanitizeShadowDOM(currentNode.content); } _sanitizeAttributes(currentNode); } 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 = "\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; } hooks[entryPoint] = hooks[entryPoint] || []; arrayPush(hooks[entryPoint], hookFunction); }; DOMPurify2.removeHook = function(entryPoint) { if (hooks[entryPoint]) { return arrayPop(hooks[entryPoint]); } }; DOMPurify2.removeHooks = function(entryPoint) { if (hooks[entryPoint]) { hooks[entryPoint] = []; } }; DOMPurify2.removeAllHooks = function() { hooks = {}; }; return DOMPurify2; } var purify = createDOMPurify(); return purify; }); } }); // src/main.ts var main_exports = {}; __export(main_exports, { default: () => Khoj }); module.exports = __toCommonJS(main_exports); var import_obsidian6 = require("obsidian"); // src/settings.ts var import_obsidian2 = require("obsidian"); // src/utils.ts var import_obsidian = require("obsidian"); 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 "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"; } } var supportedImageFilesTypes = ["png", "jpg", "jpeg"]; var supportedBinaryFileTypes = ["pdf"].concat(supportedImageFilesTypes); var supportedFileTypes = ["md", "markdown"].concat(supportedBinaryFileTypes); async function updateContentIndex(vault, setting, lastSync, regenerate = false) { var _a; console.log(`Khoj: Updating Khoj content index...`); const files = vault.getFiles().filter((file) => supportedFileTypes.includes(file.extension)); let countOfFilesToIndex = 0; let countOfFilesToDelete = 0; lastSync = lastSync.size > 0 ? lastSync : /* @__PURE__ */ new Map(); const fileData = []; 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); fileData.push({ blob: new Blob([fileContent], { type: mimeType }), path: file.path }); } let filesToDelete = []; for (const lastSyncedFile of lastSync.keys()) { if (!files.includes(lastSyncedFile)) { countOfFilesToDelete++; let fileObj = new Blob([""], { type: filenameToMimeType(lastSyncedFile) }); fileData.push({ blob: fileObj, path: lastSyncedFile.path }); filesToDelete.push(lastSyncedFile); } } let responses = []; let error_message = null; for (let i = 0; i < fileData.length; i += 1e3) { const filesGroup = fileData.slice(i, i + 1e3); const formData = new FormData(); const method = regenerate ? "PUT" : "PATCH"; filesGroup.forEach((fileItem) => { formData.append("files", fileItem.blob, fileItem.path); }); const response = await fetch(`${setting.khojUrl}/api/content?client=obsidian`, { method, headers: { "Authorization": `Bearer ${setting.khojApiKey}` }, body: formData }); if (!response.ok) { if (response.status === 429) { error_message = `\u2757\uFE0FFailed to sync your content with Khoj server. Requests were throttled. Upgrade your subscription or try again later.`; break; } else if (response.status === 404) { error_message = `\u2757\uFE0FCould not connect to Khoj server. Ensure you can connect to it.`; break; } else { error_message = `\u2757\uFE0FFailed to sync your content with Khoj server. Raise issue on Khoj Discord or Github Error: ${response.statusText}`; } } else { responses.push(await response.text()); } } 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_obsidian.Notice(error_message); } else { console.log(`\u2705 Refreshed Khoj content index. Updated: ${countOfFilesToIndex} files, Deleted: ${countOfFilesToDelete} files.`); } return lastSync; } 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_obsidian.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_obsidian.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_obsidian.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 Signed in to Khoj`; else return `\u2705 Signed in to Khoj as ${userEmail}`; } async function populateHeaderPane(headerEl, setting) { 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 titleEl = headerEl.createDiv(); titleEl.className = "khoj-logo"; titleEl.textContent = "KHOJ"; const nav = headerEl.createEl("nav"); nav.className = "khoj-nav"; const chatLink = nav.createEl("a"); chatLink.id = "chat-nav"; chatLink.className = "khoj-nav chat-nav"; const chatIcon = chatLink.createEl("span"); chatIcon.className = "khoj-nav-icon khoj-nav-icon-chat"; (0, import_obsidian.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"; 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"; const similarIcon = searchLink.createEl("span"); similarIcon.id = "similar-nav-icon"; similarIcon.className = "khoj-nav-icon khoj-nav-icon-similar"; (0, import_obsidian.setIcon)(similarIcon, "webhook"); const similarText = searchLink.createEl("span"); similarText.className = "khoj-nav-item-text"; similarText.textContent = "Similar"; similarLink.appendChild(similarIcon); similarLink.appendChild(similarText); nav.appendChild(chatLink); nav.appendChild(searchLink); nav.appendChild(similarLink); headerEl.appendChild(titleEl); headerEl.appendChild(nav); } 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_obsidian.setIcon)(button, "copy-check"); setTimeout(() => { (0, import_obsidian.setIcon)(button, originalButton); }, 1e3); }).catch((error) => { console.error("Error copying text to clipboard:", error); const originalButtonText = button.innerHTML; button.innerHTML = "\u26D4\uFE0F"; setTimeout(() => { button.innerHTML = originalButtonText; (0, import_obsidian.setIcon)(button, originalButton); }, 2e3); }); return textContent; } function createCopyParentText(message, originalButton = "copy-plus") { return function(event) { return copyParentText(event, message, originalButton); }; } 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; } } // src/settings.ts var DEFAULT_SETTINGS = { resultsCount: 6, khojUrl: "https://app.khoj.dev", khojApiKey: "", connectedToBackend: false, autoConfigure: true, lastSync: /* @__PURE__ */ new Map(), userInfo: null }; var KhojSettingTab = class extends import_obsidian2.PluginSettingTab { constructor(app, plugin) { super(app, plugin); this.plugin = plugin; } display() { var _a; const { containerEl } = this; containerEl.empty(); let backendStatusEl = containerEl.createEl("small", { text: getBackendStatusMessage(this.plugin.settings.connectedToBackend, (_a = this.plugin.settings.userInfo) == null ? void 0 : _a.email, this.plugin.settings.khojUrl, this.plugin.settings.khojApiKey) }); let backendStatusMessage = ""; new import_obsidian2.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)); await this.plugin.saveSettings(); backendStatusEl.setText(backendStatusMessage); })); new import_obsidian2.Setting(containerEl).setName("Khoj API Key").setDesc("Use Khoj Cloud with your 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)); await this.plugin.saveSettings(); backendStatusEl.setText(backendStatusMessage); })); new import_obsidian2.Setting(containerEl).setName("Results Count").setDesc("The number of results to show in search and use for chat.").addSlider((slider) => slider.setLimits(1, 10, 1).setValue(this.plugin.settings.resultsCount).setDynamicTooltip().onChange(async (value) => { this.plugin.settings.resultsCount = value; await this.plugin.saveSettings(); })); new import_obsidian2.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(); })); let indexVaultSetting = new import_obsidian2.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); this.plugin.settings.lastSync = await updateContentIndex(this.app.vault, this.plugin.settings, this.plugin.settings.lastSync, true); new import_obsidian2.Notice("\u2705 Updated Khoj index."); window.clearInterval(progress_indicator); button.setButtonText("Update"); button.setCta(); indexVaultSetting = indexVaultSetting.setDisabled(false); })); } }; // src/search_modal.ts var import_obsidian3 = require("obsidian"); var KhojSearchModal = class extends import_obsidian3.SuggestModal { constructor(app, setting, find_similar_notes = false) { super(app); this.rerank = false; this.query = ""; this.app = app; this.setting = setting; this.find_similar_notes = find_similar_notes; this.inputEl.hidden = this.find_similar_notes; 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_obsidian3.Platform.isMacOS ? "cmd \u21B5" : "ctrl \u21B5", purpose: "to rerank" }, { command: "esc", purpose: "to dismiss" } ]; this.setInstructions(modalInstructions); this.setPlaceholder("Search with Khoj..."); } 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 getSuggestions(query) { 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}` }; let response = await (0, import_obsidian3.request)({ url: `${searchUrl}`, headers }); let results = JSON.parse(response).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) => { return { entry: result.entry, file: result.additional.file }; }); this.query = query; return results; } async renderSuggestion(result, el) { var _a; let lines_to_render = 8; let os_path_separator = result.file.includes("\\") ? "\\" : "/"; let filename = result.file.split(os_path_separator).pop(); el.createEl("div", { cls: "khoj-result-file" }).setText(filename != null ? filename : ""); 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) { 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_obsidian3.MarkdownRenderer.renderMarkdown(resultToRender, result_el, result.file, null); } async onChooseSuggestion(result, _) { 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/chat_view.ts var import_obsidian5 = require("obsidian"); var DOMPurify = __toESM(require_purify()); // src/pane_view.ts var import_obsidian4 = require("obsidian"); var KhojPaneView = class extends import_obsidian4.ItemView { constructor(leaf, setting) { super(leaf); this.setting = setting; } async onOpen() { var _a, _b, _c, _d, _e; let { contentEl } = this; let headerEl = contentEl.createDiv({ attr: { id: "khoj-header", class: "khoj-header" } }); await populateHeaderPane(headerEl, this.setting); (_a = headerEl.getElementsByClassName("chat-nav")[0]) == null ? void 0 : _a.classList.add("khoj-nav-selected"); (_b = headerEl.getElementsByClassName("chat-nav")[0]) == null ? void 0 : _b.addEventListener("click", (_) => { this.activateView("khoj-chat-view" /* CHAT */); }); (_c = headerEl.getElementsByClassName("search-nav")[0]) == null ? void 0 : _c.addEventListener("click", (_) => { new KhojSearchModal(this.app, this.setting).open(); }); (_d = headerEl.getElementsByClassName("similar-nav")[0]) == null ? void 0 : _d.addEventListener("click", (_) => { new KhojSearchModal(this.app, this.setting, true).open(); }); let similarNavSvgEl = (_e = headerEl.getElementsByClassName("khoj-nav-icon-similar")[0]) == null ? void 0 : _e.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 var KhojChatView = class extends KhojPaneView { constructor(leaf, setting) { super(leaf, setting); this.keyPressTimeout = null; this.userMessages = []; this.currentMessageIndex = -1; this.currentUserInput = ""; this.startingMessage = "Message"; this.scope = new import_obsidian5.Scope(this.app.scope); this.scope.register(["Ctrl"], "n", (_) => this.createNewConversation()); this.scope.register(["Ctrl"], "o", async (_) => await this.toggleChatSessions()); this.scope.register(["Ctrl"], "f", (_) => new KhojSearchModal(this.app, this.setting).open()); this.scope.register(["Ctrl"], "r", (_) => new KhojSearchModal(this.app, this.setting, true).open()); 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, timezone: data.timezone }; }).catch((err) => { console.log(err); }).finally(() => { this.waitingForLocation = false; }); } getViewType() { return "khoj-chat-view" /* CHAT */; } getDisplayText() { return "Khoj Chat"; } getIcon() { return "message-circle"; } async chat(isVoice = false) { let input_el = this.contentEl.getElementsByClassName("khoj-chat-input")[0]; let user_message = input_el.value.trim(); if (user_message) { this.userMessages.push(user_message); const modifierKey = import_obsidian5.Platform.isMacOS ? "\u2318" : "^"; this.startingMessage = `(${modifierKey}+\u2191/\u2193) for prev messages`; input_el.placeholder = this.startingMessage; } input_el.value = ""; this.autoResize(); await this.getChatResponse(user_message, isVoice); } async onOpen() { let { contentEl } = this; contentEl.addClass("khoj-chat"); super.onOpen(); 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}`; 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 (^O)" } }); chatSessions.addEventListener("click", async (_) => { await this.toggleChatSessions(); }); (0, import_obsidian5.setIcon)(chatSessions, "history"); 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: "Start Voice Chat (^S)" } }); 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_obsidian5.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_obsidian5.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; 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_obsidian5.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 ? `Question: ${reference.question}

` : ""; 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 + `

${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, """); 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(/\[INST\].+(<\/s>)?/g, ""); 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_obsidian5.MarkdownRenderer.renderMarkdown(markdownText, virtualChatMessageBodyTextEl, "", component); virtualChatMessageBodyTextEl.innerHTML = virtualChatMessageBodyTextEl.innerHTML.replace(//gis, ""); return DOMPurify.sanitize(virtualChatMessageBodyTextEl.innerHTML); } renderMessageWithReferences(chatEl, message, sender, context, onlineContext, dt, intentType, inferredQueries) { if (!message) return; let chatMessageEl; if (intentType == null ? void 0 : intentType.includes("text-to-image")) { let imageMarkdown = this.generateImageMarkdown(message, intentType, inferredQueries); chatMessageEl = this.renderMessage(chatEl, imageMarkdown, sender, dt); } else { chatMessageEl = this.renderMessage(chatEl, message, sender, dt); } 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) { 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 = `![](data:image/webp;base64,${message})`; } if (inferredQueries) { imageMarkdown += "\n\n**Inferred Query**:"; for (let inferredQuery of inferredQueries) { imageMarkdown += ` ${inferredQuery}`; } } return imageMarkdown; } renderMessage(chatBodyEl, message, sender, dt, raw = false, willReplace = true) { let message_time = this.formatDate(dt != null ? dt : new Date()); let emojified_sender = sender == "khoj" ? "\u{1F3EE} Khoj" : "\u{1F914} You"; let chatMessageEl = chatBodyEl.createDiv({ attr: { "data-meta": `${emojified_sender} at ${message_time}`, class: `khoj-chat-message ${sender}` } }); let chatMessageBodyEl = chatMessageEl.createDiv(); chatMessageBodyEl.addClasses(["khoj-chat-message-text", sender]); let chatMessageBodyTextEl = chatMessageBodyEl.createDiv(); message = DOMPurify.sanitize(message); if (raw) { chatMessageBodyTextEl.innerHTML = message; } else { chatMessageBodyTextEl.innerHTML = this.markdownTextToSanitizedHtml(message, this); } if (willReplace === true) { this.renderActionButtons(message, chatMessageBodyTextEl); } 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": `\u{1F3EE} Khoj at ${messageTime}`, class: `khoj-chat-message khoj` } }); this.scrollChatToBottom(); return chatMessageEl; } async renderIncrementalMessage(htmlElement, additionalMessage) { this.chatMessageState.rawResponse += additionalMessage; htmlElement.innerHTML = ""; this.chatMessageState.rawResponse = DOMPurify.sanitize(this.chatMessageState.rawResponse); htmlElement.innerHTML = this.markdownTextToSanitizedHtml(this.chatMessageState.rawResponse, this); this.renderActionButtons(this.chatMessageState.rawResponse, htmlElement); this.scrollChatToBottom(); } renderActionButtons(message, chatMessageBodyTextEl) { var _a; let copyButton = this.contentEl.createEl("button"); copyButton.classList.add("chat-action-button"); copyButton.title = "Copy Message to Clipboard"; (0, import_obsidian5.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_obsidian5.setIcon)(pasteToFile, "clipboard-paste"); pasteToFile.addEventListener("click", (event) => { pasteTextAtCursor(createCopyParentText(message, "clipboard-paste")(event)); }); let speechButton = null; if ((_a = this.setting.userInfo) == null ? void 0 : _a.is_active) { speechButton = this.contentEl.createEl("button"); speechButton.classList.add("chat-action-button", "speech-button"); speechButton.title = "Listen to Message"; (0, import_obsidian5.setIcon)(speechButton, "speech"); speechButton.addEventListener("click", (event) => this.textToSpeech(message, event)); } chatMessageBodyTextEl.append(copyButton, pasteToFile); if (speechButton) { 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}`; } createNewConversation() { let chatBodyEl = this.contentEl.getElementsByClassName("khoj-chat-body")[0]; chatBodyEl.innerHTML = ""; chatBodyEl.dataset.conversationId = ""; chatBodyEl.dataset.conversationTitle = ""; this.userMessages = []; this.startingMessage = "Message"; const chatInput = this.contentEl.querySelector(".khoj-chat-input"); if (chatInput) { chatInput.placeholder = this.startingMessage; } this.renderMessage(chatBodyEl, "Hey \u{1F44B}\u{1F3FE}, what's up?", "khoj"); } 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 = this.contentEl.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()); (0, import_obsidian5.setIcon)(newConversationButtonEl, "plus"); newConversationButtonEl.innerHTML += "New"; newConversationButtonEl.title = "New Conversation (^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"] || `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); chatBodyEl.appendChild(sidePanelEl); } } } 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_obsidian5.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_obsidian5.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; let chatUrl = `${this.setting.khojUrl}/api/chat/history?client=obsidian`; if (chatBodyEl.dataset.conversationId) { chatUrl += `&conversation_id=${chatBodyEl.dataset.conversationId}`; } try { let response = await fetch(chatUrl, { method: "GET", headers: { "Authorization": `Bearer ${this.setting.khojApiKey}` } }); let responseJson = await response.json(); 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, setupMsg, "khoj", void 0); return false; } else if (responseJson.response) { chatBodyEl.dataset.conversationId = responseJson.response.conversation_id; chatBodyEl.dataset.conversationTitle = responseJson.response.slug || `New conversation \u{1F331}`; let chatLogs = ((_a = responseJson.response) == null ? void 0 : _a.conversation_id) ? (_b = responseJson.response.chat) != null ? _b : [] : responseJson.response; chatLogs.forEach((chatLog) => { var _a2, _b2; this.renderMessageWithReferences(chatBodyEl, chatLog.message, chatLog.by, 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"]); if (chatLog.by === "you") { this.userMessages.push(chatLog.message); } }); const modifierKey = import_obsidian5.Platform.isMacOS ? "\u2318" : "^"; this.startingMessage = this.userMessages.length > 0 ? `(${modifierKey}+\u2191/\u2193) for prev messages` : "Message"; 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, errorMsg, "khoj", void 0); 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: "" }; } processMessageChunk(rawChunk) { var _a, _b, _c; const chunk = this.convertMessageChunkToJson(rawChunk); console.debug("Chunk:", chunk); if (!chunk || !chunk.type) return; if (chunk.type === "status") { console.log(`status: ${chunk.data}`); const statusMessage = chunk.data; this.handleStreamResponse(this.chatMessageState.newResponseTextEl, statusMessage, this.chatMessageState.loadingEllipsis, false); } else if (chunk.type === "start_llm_response") { console.log("Started streaming", new Date()); } else if (chunk.type === "end_llm_response") { console.log("Stopped streaming", new Date()); if (this.chatMessageState.isVoice && ((_a = this.setting.userInfo) == null ? void 0 : _a.is_active)) this.textToSpeech(this.chatMessageState.rawResponse); this.finalizeChatBodyResponse(this.chatMessageState.references, this.chatMessageState.newResponseTextEl); const liveQuery = this.chatMessageState.rawQuery; this.chatMessageState = { newResponseTextEl: null, newResponseEl: null, loadingEllipsis: null, references: {}, rawResponse: "", rawQuery: liveQuery, isVoice: false }; } 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.loadingEllipsis); } } else { this.chatMessageState.rawResponse += chunkData; this.handleStreamResponse(this.chatMessageState.newResponseTextEl, this.chatMessageState.rawResponse, this.chatMessageState.loadingEllipsis); } } } handleJsonResponse(jsonData) { if (jsonData.image || jsonData.detail) { 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, isVoice = false) { if (!query || query === "") return; let chatBodyEl = this.contentEl.getElementsByClassName("khoj-chat-body")[0]; this.renderMessage(chatBodyEl, query, "you"); let conversationId = chatBodyEl.dataset.conversationId; if (!conversationId) { let chatUrl2 = `${this.setting.khojUrl}/api/chat/sessions?client=obsidian`; let response2 = await fetch(chatUrl2, { method: "POST", headers: { "Authorization": `Bearer ${this.setting.khojApiKey}` } }); let data = await response2.json(); conversationId = data.conversation_id; chatBodyEl.dataset.conversationId = conversationId; } let encodedQuery = encodeURIComponent(query); let chatUrl = `${this.setting.khojUrl}/api/chat?q=${encodedQuery}&conversation_id=${conversationId}&n=${this.setting.resultsCount}&stream=true&client=obsidian`; if (!!this.location) chatUrl += `®ion=${this.location.region}&city=${this.location.city}&country=${this.location.countryName}&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 }; let response = await fetch(chatUrl, { method: "GET", headers: { "Content-Type": "text/plain", "Authorization": `Bearer ${this.setting.khojApiKey}` } }); 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 team@khoj.dev or on Discord"; 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_obsidian5.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_obsidian5.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_obsidian5.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_obsidian5.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") { (_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.mediaRecorder.stop(); this.mediaRecorder.stream.getTracks().forEach((track) => track.stop()); this.mediaRecorder = void 0; transcribeButton.classList.remove("loading-encircle"); (0, import_obsidian5.setIcon)(transcribeButton, "mic"); } } cancelSendMessage() { clearTimeout(this.sendMessageTimeout); let sendButton = this.contentEl.getElementsByClassName("khoj-chat-send")[0]; (0, import_obsidian5.setIcon)(sendButton, "arrow-up-circle"); let sendImg = sendButton.getElementsByClassName("lucide-arrow-up-circle")[0]; sendImg.addEventListener("click", async (_) => { await this.chat(); }); } incrementalChat(event) { if (!event.shiftKey && event.key === "Enter") { 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; this.autoResize(); } autoResize() { const chatInput = this.contentEl.getElementsByClassName("khoj-chat-input")[0]; const scrollTop = chatInput.scrollTop; chatInput.style.height = "0"; const scrollHeight = chatInput.scrollHeight + 8; chatInput.style.height = Math.min(scrollHeight, 200) + "px"; chatInput.scrollTop = scrollTop; this.scrollChatToBottom(); } 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); if (replace) newResponseElement.innerHTML = ""; newResponseElement.appendChild(this.formatHTMLMessage(rawResponse, false, replace)); if (!replace && loadingEllipsis) newResponseElement.appendChild(loadingEllipsis); this.scrollChatToBottom(); } 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 = `![](data:image/webp;base64,${imageJson.image})`; } if (inferredQuery) { rawResponse += ` **Inferred Query**: ${inferredQuery}`; } } if (imageJson.detail) rawResponse += imageJson.detail; return rawResponse; } finalizeChatBodyResponse(references, newResponseElement) { if (!!newResponseElement && references != null && Object.keys(references).length > 0) { newResponseElement.appendChild(this.createReferenceSection(references)); } 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 = event.target; const isModKey = import_obsidian5.Platform.isMacOS ? event.metaKey : event.ctrlKey; if (isModKey && event.key === "ArrowUp") { event.preventDefault(); if (this.currentMessageIndex < this.userMessages.length - 1) { this.currentMessageIndex++; chatInput.value = this.userMessages[this.userMessages.length - 1 - this.currentMessageIndex]; } } else if (isModKey && event.key === "ArrowDown") { event.preventDefault(); if (this.currentMessageIndex > 0) { this.currentMessageIndex--; chatInput.value = this.userMessages[this.userMessages.length - 1 - this.currentMessageIndex]; } else if (this.currentMessageIndex === 0) { this.currentMessageIndex = -1; chatInput.value = this.currentUserInput; } } } }; // src/main.ts var Khoj = class extends import_obsidian6.Plugin { async onload() { await this.loadSettings(); this.addCommand({ id: "search", name: "Search", callback: () => { new KhojSearchModal(this.app, this.settings).open(); } }); this.addCommand({ id: "similar", name: "Find similar notes", editorCallback: () => { new KhojSearchModal(this.app, this.settings, true).open(); } }); this.addCommand({ id: "chat", name: "Chat", callback: () => { this.activateView("khoj-chat-view" /* CHAT */); } }); this.registerView("khoj-chat-view" /* CHAT */, (leaf) => new KhojChatView(leaf, this.settings)); this.addRibbonIcon("message-circle", "Khoj", (_) => { this.activateView("khoj-chat-view" /* CHAT */); }); this.addSettingTab(new KhojSettingTab(this.app, this)); this.indexingTimer = setInterval(async () => { if (this.settings.autoConfigure) { this.settings.lastSync = await updateContentIndex(this.app.vault, this.settings, this.settings.lastSync); } }, 60 * 60 * 1e3); } 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() { this.saveData(this.settings); } async onunload() { if (this.indexingTimer) clearInterval(this.indexingTimer); this.unload(); } async activateView(viewType) { var _a; 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) { const activeKhojLeaf = (_a = workspace.getActiveViewOfType(KhojPaneView)) == null ? void 0 : _a.leaf; if (activeKhojLeaf === leaf) jumpToPreviousView(); else { workspace.revealLeaf(leaf); if (viewType === "khoj-chat-view" /* CHAT */) { let chatView = leaf.view; let chatInput = chatView.contentEl.getElementsByClassName("khoj-chat-input")[0]; if (chatInput) chatInput.focus(); } } } } }; /*! @license DOMPurify 3.1.5 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.1.5/LICENSE */