digital-garden/.obsidian/plugins/khoj/main.js

2691 lines
122 KiB
JavaScript

/*
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 = "<remove></remove>" + dirty;
} else {
const matches = stringMatch(dirty, /^[\r\n\t ]+/);
leadingWhitespace = matches && matches[0];
}
if (PARSER_MEDIA_TYPE === "application/xhtml+xml" && NAMESPACE === HTML_NAMESPACE) {
dirty = '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' + dirty + "</body></html>";
}
const dirtyPayload = trustedTypesPolicy ? trustedTypesPolicy.createHTML(dirty) : dirty;
if (NAMESPACE === HTML_NAMESPACE) {
try {
doc = new DOMParser().parseFromString(dirtyPayload, PARSER_MEDIA_TYPE);
} catch (_) {
}
}
if (!doc || !doc.documentElement) {
doc = implementation.createDocument(NAMESPACE, "template", null);
try {
doc.documentElement.innerHTML = IS_EMPTY_INPUT ? emptyHTML : dirtyPayload;
} catch (_) {
}
}
const body = doc.body || doc.documentElement;
if (dirty && leadingWhitespace) {
body.insertBefore(document2.createTextNode(leadingWhitespace), body.childNodes[0] || null);
}
if (NAMESPACE === HTML_NAMESPACE) {
return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? "html" : "body")[0];
}
return WHOLE_DOCUMENT ? doc.documentElement : body;
};
const _createNodeIterator = function _createNodeIterator2(root) {
return createNodeIterator.call(root.ownerDocument || root, root, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_PROCESSING_INSTRUCTION | NodeFilter.SHOW_CDATA_SECTION, null);
};
const _isClobbered = function _isClobbered2(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 = "<!DOCTYPE " + body.ownerDocument.doctype.name + ">\n" + serializedHTML;
}
if (SAFE_FOR_TEMPLATES) {
arrayForEach([MUSTACHE_EXPR2, ERB_EXPR2, TMPLIT_EXPR2], (expr) => {
serializedHTML = stringReplace(serializedHTML, expr, " ");
});
}
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(serializedHTML) : serializedHTML;
};
DOMPurify2.setConfig = function() {
let cfg = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {};
_parseConfig(cfg);
SET_CONFIG = true;
};
DOMPurify2.clearConfig = function() {
CONFIG = null;
SET_CONFIG = false;
};
DOMPurify2.isValidAttribute = function(tag, attr, value) {
if (!CONFIG) {
_parseConfig({});
}
const lcTag = transformCaseFunc(tag);
const lcName = transformCaseFunc(attr);
return _isValidAttribute(lcTag, lcName, value);
};
DOMPurify2.addHook = function(entryPoint, hookFunction) {
if (typeof hookFunction !== "function") {
return;
}
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 ? `<b>Question:</b> ${reference.question}<br><br>` : "";
let referenceButton = messageEl.createEl("button");
let linkElement = referenceButton.createEl("a");
linkElement.setAttribute("href", link);
linkElement.setAttribute("target", "_blank");
linkElement.setAttribute("rel", "noopener noreferrer");
linkElement.classList.add("reference-link");
linkElement.setAttribute("title", title);
linkElement.textContent = title;
referenceButton.id = `ref-${index}`;
referenceButton.classList.add("reference-button");
referenceButton.classList.add("collapsed");
referenceButton.tabIndex = 0;
referenceButton.addEventListener("click", function() {
if (this.classList.contains("collapsed")) {
this.classList.remove("collapsed");
this.classList.add("expanded");
this.innerHTML = linkElement.outerHTML + `<br><br>${question + snippet}`;
} else {
this.classList.add("collapsed");
this.classList.remove("expanded");
this.innerHTML = linkElement.outerHTML;
}
});
return referenceButton;
}
generateReference(messageEl, referenceJson, index) {
let reference = referenceJson.hasOwnProperty("compiled") ? referenceJson.compiled : referenceJson;
let referenceFile = referenceJson.hasOwnProperty("file") ? referenceJson.file : null;
const mdFiles = this.app.vault.getMarkdownFiles();
const pdfFiles = this.app.vault.getFiles().filter((file) => file.extension === "pdf");
reference = reference.split("\n").slice(1).join("\n");
let escaped_ref = reference.replace(/"/g, "&quot;");
let referenceButton = messageEl.createEl("button");
if (referenceFile) {
const linkToEntry = getLinkToEntry(mdFiles.concat(pdfFiles), referenceFile, reference);
const linkElement = referenceButton.createEl("span");
linkElement.setAttribute("title", escaped_ref);
linkElement.textContent = referenceFile;
if (linkElement && linkToEntry) {
linkElement.classList.add("reference-link");
linkElement.addEventListener("click", (event) => {
event.stopPropagation();
this.app.workspace.openLinkText(linkToEntry, "");
});
}
}
let referenceText = referenceButton.createDiv();
referenceText.textContent = escaped_ref;
referenceButton.id = `ref-${index}`;
referenceButton.classList.add("reference-button");
referenceButton.classList.add("collapsed");
referenceButton.tabIndex = 0;
referenceButton.addEventListener("click", function() {
if (this.classList.contains("collapsed")) {
this.classList.remove("collapsed");
this.classList.add("expanded");
} else {
this.classList.add("collapsed");
this.classList.remove("expanded");
}
});
return referenceButton;
}
textToSpeech(message, event = null) {
let loader = document.createElement("span");
loader.classList.add("loader");
let speechButton;
let speechIcon;
if (event === null) {
let speechButtons = document.getElementsByClassName("speech-button");
speechButton = speechButtons[speechButtons.length - 1];
let speechIcons = document.getElementsByClassName("speech-icon");
speechIcon = speechIcons[speechIcons.length - 1];
} else {
speechButton = event.currentTarget;
speechIcon = event.target;
}
speechButton.appendChild(loader);
speechButton.disabled = true;
const context = new AudioContext();
let textToSpeechApi = `${this.setting.khojUrl}/api/chat/speech?text=${encodeURIComponent(message)}`;
fetch(textToSpeechApi, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${this.setting.khojApiKey}`
}
}).then((response) => response.arrayBuffer()).then((arrayBuffer) => context.decodeAudioData(arrayBuffer)).then((audioBuffer) => {
const source = context.createBufferSource();
source.buffer = audioBuffer;
source.connect(context.destination);
source.start(0);
source.onended = function() {
speechButton.removeChild(loader);
speechButton.disabled = false;
};
}).catch((err) => {
console.error("Error playing speech:", err);
speechButton.removeChild(loader);
speechButton.disabled = false;
});
}
formatHTMLMessage(message, raw = false, willReplace = true) {
message = message.replace(/<s>\[INST\].+(<\/s>)?/g, "");
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(/<img(?:(?!src=["'](app:|data:|https:\/\/generated\.khoj\.dev)).)*?>/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 += `&region=${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 <a href=mailto:'team@khoj.dev'>team@khoj.dev</a> or <a href='https://discord.gg/BDgyabRM6e'>on Discord</a>";
newResponseTextEl.textContent = errorMsg;
}
}
flashStatusInChatInput(message) {
let chatInput = this.contentEl.getElementsByClassName("khoj-chat-input")[0];
let originalPlaceholder = chatInput.placeholder;
chatInput.placeholder = message;
setTimeout(() => {
chatInput.placeholder = originalPlaceholder;
}, 2e3);
}
async clearConversationHistory() {
let chatBody = this.contentEl.getElementsByClassName("khoj-chat-body")[0];
let response = await (0, import_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 */