/*! smart-connections-obsidian v4.1.6 | (c) 2025 🌴 Brian (Brian Petro) */ 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( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // node_modules/obsidian-smart-env/node_modules/base64-js/index.js var require_base64_js = __commonJS({ "node_modules/obsidian-smart-env/node_modules/base64-js/index.js"(exports2) { "use strict"; exports2.byteLength = byteLength; exports2.toByteArray = toByteArray; exports2.fromByteArray = fromByteArray; var lookup = []; var revLookup = []; var Arr = typeof Uint8Array !== "undefined" ? Uint8Array : Array; var code = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; for (i = 0, len = code.length; i < len; ++i) { lookup[i] = code[i]; revLookup[code.charCodeAt(i)] = i; } var i; var len; revLookup["-".charCodeAt(0)] = 62; revLookup["_".charCodeAt(0)] = 63; function getLens(b64) { var len2 = b64.length; if (len2 % 4 > 0) { throw new Error("Invalid string. Length must be a multiple of 4"); } var validLen = b64.indexOf("="); if (validLen === -1) validLen = len2; var placeHoldersLen = validLen === len2 ? 0 : 4 - validLen % 4; return [validLen, placeHoldersLen]; } function byteLength(b64) { var lens = getLens(b64); var validLen = lens[0]; var placeHoldersLen = lens[1]; return (validLen + placeHoldersLen) * 3 / 4 - placeHoldersLen; } function _byteLength(b64, validLen, placeHoldersLen) { return (validLen + placeHoldersLen) * 3 / 4 - placeHoldersLen; } function toByteArray(b64) { var tmp; var lens = getLens(b64); var validLen = lens[0]; var placeHoldersLen = lens[1]; var arr = new Arr(_byteLength(b64, validLen, placeHoldersLen)); var curByte = 0; var len2 = placeHoldersLen > 0 ? validLen - 4 : validLen; var i2; for (i2 = 0; i2 < len2; i2 += 4) { tmp = revLookup[b64.charCodeAt(i2)] << 18 | revLookup[b64.charCodeAt(i2 + 1)] << 12 | revLookup[b64.charCodeAt(i2 + 2)] << 6 | revLookup[b64.charCodeAt(i2 + 3)]; arr[curByte++] = tmp >> 16 & 255; arr[curByte++] = tmp >> 8 & 255; arr[curByte++] = tmp & 255; } if (placeHoldersLen === 2) { tmp = revLookup[b64.charCodeAt(i2)] << 2 | revLookup[b64.charCodeAt(i2 + 1)] >> 4; arr[curByte++] = tmp & 255; } if (placeHoldersLen === 1) { tmp = revLookup[b64.charCodeAt(i2)] << 10 | revLookup[b64.charCodeAt(i2 + 1)] << 4 | revLookup[b64.charCodeAt(i2 + 2)] >> 2; arr[curByte++] = tmp >> 8 & 255; arr[curByte++] = tmp & 255; } return arr; } function tripletToBase64(num) { return lookup[num >> 18 & 63] + lookup[num >> 12 & 63] + lookup[num >> 6 & 63] + lookup[num & 63]; } function encodeChunk(uint8, start, end) { var tmp; var output = []; for (var i2 = start; i2 < end; i2 += 3) { tmp = (uint8[i2] << 16 & 16711680) + (uint8[i2 + 1] << 8 & 65280) + (uint8[i2 + 2] & 255); output.push(tripletToBase64(tmp)); } return output.join(""); } function fromByteArray(uint8) { var tmp; var len2 = uint8.length; var extraBytes = len2 % 3; var parts = []; var maxChunkLength = 16383; for (var i2 = 0, len22 = len2 - extraBytes; i2 < len22; i2 += maxChunkLength) { parts.push(encodeChunk(uint8, i2, i2 + maxChunkLength > len22 ? len22 : i2 + maxChunkLength)); } if (extraBytes === 1) { tmp = uint8[len2 - 1]; parts.push( lookup[tmp >> 2] + lookup[tmp << 4 & 63] + "==" ); } else if (extraBytes === 2) { tmp = (uint8[len2 - 2] << 8) + uint8[len2 - 1]; parts.push( lookup[tmp >> 10] + lookup[tmp >> 4 & 63] + lookup[tmp << 2 & 63] + "=" ); } return parts.join(""); } } }); // src/main.js var main_exports = {}; __export(main_exports, { default: () => SmartConnectionsPlugin }); module.exports = __toCommonJS(main_exports); var import_obsidian54 = __toESM(require("obsidian"), 1); // node_modules/obsidian-smart-env/smart_env.js var import_obsidian40 = require("obsidian"); // node_modules/obsidian-smart-env/node_modules/smart-events/adapters/_adapter.js var WILDCARD_KEY = "*"; var SmartEventsAdapter = class { constructor(instance) { this.instance = instance; this.handlers = /* @__PURE__ */ Object.create(null); } /** * Register an event handler. * When event_key is '*', the handler subscribes to all events. * Handlers receive (event, event_key). * @param {string} event_key * @param {Function} event_callback */ on(event_key, event_callback) { } /** * Register a one-time handler. * When event_key is '*', the handler fires once on the next emitted event of any key. * Handlers receive (event, event_key). * @param {string} event_key * @param {Function} event_callback */ once(event_key, event_callback) { } /** * Remove an event handler. * When event_key is '*', removes from the wildcard list only. * @param {string} event_key * @param {Function} event_callback */ off(event_key, event_callback) { } /** * Emit an event. * event_key must not be '*'. * @param {string} event_key * @param {Object} event */ emit(event_key, event) { } }; // node_modules/obsidian-smart-env/node_modules/smart-events/adapters/default.js var DefaultEventsAdapter = class extends SmartEventsAdapter { constructor(instance) { super(instance); this._next_id = 1; } on(event_key, event_callback = () => { }) { const key = event_key === WILDCARD_KEY ? WILDCARD_KEY : event_key; const list = this.handlers[key] || (this.handlers[key] = []); const entry = { id: this._next_id++, cb: event_callback }; list.push(entry); return () => this.off_entry(key, entry.id); } once(event_key, event_callback = () => { }) { const key = event_key === WILDCARD_KEY ? WILDCARD_KEY : event_key; const list = this.handlers[key] || (this.handlers[key] = []); const entry = { id: this._next_id++, cb: null }; const wrapper = (event, emitted_key) => { this.off_entry(key, entry.id); event_callback(event, emitted_key); }; entry.cb = wrapper; list.push(entry); return () => this.off_entry(key, entry.id); } /** * Public removal by function reference. * Removes a single matching registration for the given function. * Preference is to remove the most-recent registration (LIFO) when duplicates exist. */ off(event_key, event_callback) { const list = this.handlers[event_key]; if (!list || !event_callback) return; for (let i = list.length - 1; i >= 0; i--) { if (list[i].cb === event_callback) { list.splice(i, 1); break; } } } /** * Internal precise removal by entry id. * Used by unsubscribe closures returned from on/once. */ off_entry(event_key, entry_id) { const list = this.handlers[event_key]; if (!list) return; const idx = list.findIndex((e) => e.id === entry_id); if (idx !== -1) list.splice(idx, 1); } emit(event_key, event = {}) { if (event_key === WILDCARD_KEY) { throw new Error('emit("*") is not allowed; "*" is reserved for wildcard listeners.'); } const specific_list = this.handlers[event_key]; const wildcard_list = this.handlers[WILDCARD_KEY]; if (!specific_list && !wildcard_list) return; const call_specific = specific_list ? [...specific_list] : []; const call_wildcard = wildcard_list ? [...wildcard_list] : []; for (let i = 0; i < call_specific.length; i++) { call_specific[i].cb(event, event_key); } for (let i = 0; i < call_wildcard.length; i++) { call_wildcard[i].cb(event, event_key); } } }; // node_modules/obsidian-smart-env/node_modules/smart-events/smart_events.js var SmartEvents = class _SmartEvents { constructor(env, opts = {}) { env.create_env_getter(this); this.opts = opts; } static create(env, opts = {}) { const smart_events = new _SmartEvents(env, opts); if (!Object.getOwnPropertyDescriptor(env, "events")) { Object.defineProperty(env, "events", { get: () => smart_events }); } return smart_events; } get adapter() { if (!this._adapter) { this._adapter = this.opts.adapter_class ? new this.opts.adapter_class(this) : new DefaultEventsAdapter(this); } return this._adapter; } on(event_key, event_callback = (event) => { }) { return this.adapter.on(event_key, event_callback); } once(event_key, event_callback = (event) => { }) { return this.adapter.once(event_key, event_callback); } off(event_key, event_callback = (event) => { }) { return this.adapter.off(event_key, event_callback); } /** * Emit an event. * @param {string} event_key * @param {Record} [event] * @returns {void} */ emit(event_key, event = {}) { const payload = { ...event }; if (payload.at === void 0) { payload.at = Date.now(); } Object.freeze(payload); return this.adapter.emit(event_key, payload); } }; // node_modules/obsidian-smart-env/node_modules/smart-environment/components/settings.js async function build_html(env, opts = {}) { const env_settings_html = Object.entries(env.settings_config).map(([setting_key, setting_config]) => { if (!setting_config.setting) setting_config.setting = setting_key; return this.render_setting_html(setting_config); }).join("\n"); const env_collections_containers_html = Object.entries(env.collections).map(([collection_key, collection]) => { return `
`; }).join("\n"); const html = `
${env_settings_html} ${env_collections_containers_html}
`; return html; } async function render(env, opts = {}) { const html = await build_html.call(this, env, opts); const frag = this.create_doc_fragment(html); return await post_process.call(this, env, frag, opts); } async function post_process(env, frag, opts = {}) { await this.render_setting_components(frag, { scope: env }); const env_collections_containers = frag.querySelectorAll("[data-smart-settings]"); for (const env_collections_container of env_collections_containers) { const collection_key = env_collections_container.dataset.smartSettings; const collection = env[collection_key]; await collection.render_settings(env_collections_container); } return frag; } // node_modules/obsidian-smart-env/node_modules/smart-settings/smart_settings.js var SmartSettings = class { /** * Creates an instance of SmartEnvSettings. * @param {Object} main - The main object to contain the instance (smart_settings) and getter (settings) * @param {Object} [opts={}] - Configuration options. */ constructor(main, opts = {}) { this.main = main; this.opts = opts; this._fs = null; this._settings = {}; this._saved = false; this.save_timeout = null; this.save_delay_ms = typeof opts.save_delay_ms === "number" ? opts.save_delay_ms : 1e3; } static async create(main, opts = {}) { const smart_settings = new this(main, opts); await smart_settings.load(); main.smart_settings = smart_settings; Object.defineProperty(main, "settings", { get() { return smart_settings.settings; }, set(settings) { smart_settings.settings = settings; } }); return smart_settings; } static create_sync(main, opts = {}) { const smart_settings = new this(main, opts); smart_settings.load_sync(); main.smart_settings = smart_settings; Object.defineProperty(main, "settings", { get() { return smart_settings.settings; }, set(settings) { smart_settings.settings = settings; } }); return smart_settings; } /** * Gets the current settings, wrapped with an observer to handle changes. * @returns {Proxy} A proxy object that observes changes to the settings. */ get settings() { return observe_object(this._settings, (change) => { this.emit_settings_changed(change); this.schedule_save(); }); } /** * Sets the current settings. * @param {Object} settings - The new settings to apply. */ set settings(settings) { this._settings = settings; } schedule_save() { if (this.save_timeout) clearTimeout(this.save_timeout); this.save_timeout = setTimeout(() => { this.save(this._settings); this.save_timeout = null; }, this.save_delay_ms); } emit_settings_changed(change) { const events_bus = this.resolve_events_bus(); if (!events_bus?.emit) return; events_bus.emit("settings:changed", build_settings_changed_event(change)); } resolve_events_bus() { if (this.opts.events) return this.opts.events; if (typeof this.opts.emit === "function") { return { emit: this.opts.emit }; } if (this.main?.events) return this.main.events; if (this.main?.env?.events) return this.main.env.events; return null; } async save(settings = this._settings) { if (typeof this.opts.save === "function") await this.opts.save(settings); else await this.main.save_settings(settings); } async load() { if (typeof this.opts.load === "function") this._settings = await this.opts.load(); else this._settings = await this.main.load_settings(); } load_sync() { if (typeof this.opts.load === "function") this._settings = this.opts.load(); else this._settings = this.main.load_settings(); } }; function observe_object(obj, on_change) { const proxy_cache = /* @__PURE__ */ new WeakMap(); const proxy_targets = /* @__PURE__ */ new WeakMap(); const wrap_value = (value, path) => { if (!is_observable(value)) return value; if (proxy_targets.has(value)) return value; if (proxy_cache.has(value)) return proxy_cache.get(value); const proxy = create_proxy(value, path); proxy_cache.set(value, proxy); proxy_targets.set(proxy, value); return proxy; }; const create_proxy = (target, path) => new Proxy(target, { set(target2, property, value) { const property_path = [...path, property]; const previous_snapshot = snapshot_value(target2[property]); const next_snapshot = snapshot_value(value); target2[property] = wrap_value(value, property_path); if (has_changed(previous_snapshot, next_snapshot)) { on_change({ type: "set", path: property_path, value: next_snapshot, previous_value: previous_snapshot }); } return true; }, get(target2, property) { const result = target2[property]; return wrap_value(result, [...path, property]); }, deleteProperty(target2, property) { if (!Object.prototype.hasOwnProperty.call(target2, property)) { return true; } const property_path = [...path, property]; const previous_snapshot = snapshot_value(target2[property]); delete target2[property]; on_change({ type: "delete", path: property_path, previous_value: previous_snapshot }); return true; } }); return wrap_value(obj, []); } function build_settings_changed_event(change) { const path = Array.isArray(change.path) ? change.path : []; return { type: change.type, path, path_string: path.join("."), value: change.value, previous_value: change.previous_value }; } function snapshot_value(value) { if (!is_observable(value)) { return value; } if (typeof structuredClone === "function") { try { return structuredClone(value); } catch (error) { } } try { return JSON.parse(JSON.stringify(value)); } catch (error) { return value; } } function has_changed(previous_snapshot, next_snapshot) { return serialize_value(previous_snapshot) !== serialize_value(next_snapshot); } function serialize_value(value) { if (value === void 0) return "undefined"; if (Number.isNaN(value)) return "number:NaN"; if (value === Infinity) return "number:Infinity"; if (value === -Infinity) return "number:-Infinity"; if (!is_observable(value)) { return `${typeof value}:${String(value)}`; } try { return `object:${JSON.stringify(value)}`; } catch (error) { return `object:${String(value)}`; } } function is_observable(value) { return typeof value === "object" && value !== null; } // node_modules/obsidian-smart-env/node_modules/smart-utils/deep_merge.js function deep_merge(target = {}, source = {}) { for (const key in source) { if (!Object.prototype.hasOwnProperty.call(source, key)) continue; if (is_plain_object(source[key]) && is_plain_object(target[key])) { deep_merge(target[key], source[key]); } else { target[key] = source[key]; } } return target; } function is_plain_object(o) { return o && typeof o === "object" && !Array.isArray(o); } // node_modules/obsidian-smart-env/node_modules/smart-utils/camel_case_to_snake_case.js function camel_case_to_snake_case(str = "") { return str.replace(/([A-Z])/g, (m) => `_${m.toLowerCase()}`).replace(/^_/, "").replace(/2$/, ""); } // node_modules/obsidian-smart-env/node_modules/smart-environment/utils/normalize_opts.js function normalize_opts(opts) { if (!opts.collections) opts.collections = {}; if (!opts.modules) opts.modules = {}; if (!opts.items) opts.items = {}; Object.entries(opts.collections).forEach(([key, val]) => { if (typeof val === "function") { opts.collections[key] = { class: val }; } const new_key = camel_case_to_snake_case(key); if (new_key !== key) { opts.collections[new_key] = opts.collections[key]; delete opts.collections[key]; } if (!opts.collections[new_key].collection_key) opts.collections[new_key].collection_key = new_key; if (val.item_type) { opts.items[val.item_type.key || camel_case_to_snake_case(val.item_type.name)] = { class: val.item_type }; } }); Object.entries(opts.modules).forEach(([key, val]) => { if (typeof val === "function") { opts.modules[key] = { class: val }; } const new_key = camel_case_to_snake_case(key); if (new_key !== key) { opts.modules[new_key] = opts.modules[key]; delete opts.modules[key]; } }); if (!opts.item_types) opts.item_types = {}; if (!opts.items) opts.items = {}; Object.entries(opts.item_types).forEach(([key, val]) => { if (typeof val === "function") { const new_key = camel_case_to_snake_case(key); opts.items[new_key] = { class: val, actions: {}, ...opts.items[new_key] || {} }; } }); return opts; } // node_modules/obsidian-smart-env/node_modules/smart-environment/utils/deep_clone_config.js function is_plain_object2(value) { if (!value || typeof value !== "object") return false; const proto = Object.getPrototypeOf(value); return proto === Object.prototype || proto === null; } function deep_clone_config(input) { if (Array.isArray(input)) { return input.map((item) => deep_clone_config(item)); } if (is_plain_object2(input)) { const output = {}; for (const [k, v] of Object.entries(input)) { output[k] = deep_clone_config(v); } return output; } return input; } // node_modules/obsidian-smart-env/node_modules/smart-environment/utils/compare_versions.js function compare_versions(new_value, cur_value) { const a = normalize_version_value(new_value); const b = normalize_version_value(cur_value); const len = Math.max(a.parts.length, b.parts.length); for (let i = 0; i < len; i++) { const av = a.parts[i] !== void 0 ? a.parts[i] : 0; const bv = b.parts[i] !== void 0 ? b.parts[i] : 0; if (av > bv) return 1; if (av < bv) return -1; } if (a.type === b.type) return 0; if (a.type === "semver" && b.type !== "semver") return 1; if (b.type === "semver" && a.type !== "semver") return -1; if (a.type === "number" && b.type === "none") { return a.parts[0] === 0 ? 0 : 1; } if (b.type === "number" && a.type === "none") { return b.parts[0] === 0 ? 0 : -1; } return 0; } function normalize_version_value(value) { if (value === null || value === void 0) { return { type: "none", parts: [0, 0, 0] }; } if (typeof value === "number") { if (!Number.isFinite(value)) { return { type: "none", parts: [0, 0, 0] }; } const major = Math.floor(value); const minor = Math.floor((value - major) * 10); return { type: "number", parts: [major, minor, 0] }; } if (typeof value === "string") { const trimmed = value.trim(); if (!trimmed) { return { type: "none", parts: [0, 0, 0] }; } const raw_parts = trimmed.split("."); const parts = raw_parts.map((part) => { const match = part.match(/^\d+/); if (!match) return 0; const num = Number.parseInt(match[0], 10); return Number.isNaN(num) ? 0 : num; }); while (parts.length < 3) { parts.push(0); } return { type: "semver", parts }; } return { type: "none", parts: [0, 0, 0] }; } // node_modules/obsidian-smart-env/node_modules/smart-environment/utils/is_plain_object.js function is_plain_object3(o) { if (o === null) return false; if (typeof o !== "object") return false; if (Array.isArray(o)) return false; if (o instanceof Function) return false; if (o instanceof Date) return false; return Object.getPrototypeOf(o) === Object.prototype; } // node_modules/obsidian-smart-env/node_modules/smart-environment/utils/deep_merge_no_overwrite.js function deep_merge_no_overwrite(target, source, path = []) { if (!is_plain_object3(target) || !is_plain_object3(source)) { return target; } if (path.includes(source)) { return target; } path.push(source); for (const key of Object.keys(source)) { if (!Object.prototype.hasOwnProperty.call(source, key)) { continue; } const val = source[key]; if (Array.isArray(target[key]) && Array.isArray(val)) { for (const item of val) { if (typeof item === "function") { const item_name = item.name; const has_same_fn = target[key].some( (el) => typeof el === "function" && el.name === item_name ); if (!has_same_fn) { target[key].push(item); } } else if (item === null || ["string", "number", "boolean", "undefined"].includes(typeof item)) { if (!target[key].includes(item)) { target[key].push(item); } } else { target[key].push(item); } } } else if (is_plain_object3(val)) { if (!is_plain_object3(target[key])) { target[key] = {}; } deep_merge_no_overwrite(target[key], val, [...path]); } else if (!Object.prototype.hasOwnProperty.call(target, key)) { target[key] = val; } } return target; } // node_modules/obsidian-smart-env/node_modules/smart-environment/utils/merge_env_config.js function merge_env_config(target, incoming) { const CUR_VER = target.version; const NEW_VER = incoming.version; for (const [key, value] of Object.entries(incoming)) { if (key === "collections" && value && typeof value === "object") { if (!target.collections) target.collections = {}; for (const [col_key, col_def] of Object.entries(value)) { const existing_def = target.collections[col_key]; if (!existing_def) { target.collections[col_key] = { ...col_def }; continue; } const new_version_raw = col_def && col_def.version !== void 0 ? col_def.version : col_def?.class?.version; const cur_version_raw = existing_def && existing_def.version !== void 0 ? existing_def.version : existing_def?.class?.version; const cmp = compare_versions(new_version_raw, cur_version_raw); if (cmp > 0) { const replaced = { ...col_def }; deep_merge_no_overwrite(replaced, existing_def); target.collections[col_key] = replaced; } else { deep_merge_no_overwrite(existing_def, col_def); } } continue; } if (["actions", "collections", "components", "item_types", "modules"].includes(key) && value && typeof value === "object") { if (!target[key]) target[key] = {}; for (const [comp_key, comp_def] of Object.entries(value)) { if (!target[key][comp_key]) { target[key][comp_key] = { ...comp_def }; continue; } const target_comp = target[key][comp_key]; const incoming_ver = comp_def && comp_def.version; const target_ver = target_comp && target_comp.version; const cmp = compare_versions(incoming_ver, target_ver); if (cmp > 0) { target[key][comp_key] = comp_def; target[key][comp_key].version = incoming_ver || -1; } else { deep_merge_no_overwrite(target_comp, comp_def); } } continue; } if (Array.isArray(value)) { if (Array.isArray(target[key])) { if (value.length > 0 && (typeof value[0] === "string" || typeof value[0] === "number" || typeof value[0] === "boolean")) { target[key] = Array.from(/* @__PURE__ */ new Set([...target[key], ...value])); } else { target[key] = [...target[key], ...value]; } } else { if (value.length > 0 && (typeof value[0] === "string" || typeof value[0] === "number" || typeof value[0] === "boolean")) { target[key] = Array.from(new Set(value)); } else { target[key] = [...value]; } } } else if (value && typeof value === "object") { if (!target[key]) target[key] = {}; deep_merge_no_overwrite(target[key], value); } else { target[key] = value; } } return target; } // node_modules/obsidian-smart-env/node_modules/smart-environment/migrations/exclusion_settings.js function migrate_exclusion_settings_2025_08_22(settings = {}) { const { file_exclusions, folder_exclusions: folder_exclusions2, excluded_headings } = settings; if (file_exclusions !== void 0 || folder_exclusions2 !== void 0 || excluded_headings !== void 0) { settings.smart_sources = settings.smart_sources || {}; if (file_exclusions !== void 0) { if (file_exclusions.length && file_exclusions !== "Untitled" && (!settings.smart_sources?.file_exclusions?.length || settings.smart_sources?.file_exclusions === "Untitled")) { settings.smart_sources.file_exclusions = file_exclusions; } } if (folder_exclusions2 !== void 0) { if (folder_exclusions2.length && !settings.smart_sources.folder_exclusions?.length) { settings.smart_sources.folder_exclusions = folder_exclusions2; } } if (excluded_headings !== void 0) { if (excluded_headings.length && !settings.smart_sources.excluded_headings?.length) { settings.smart_sources.excluded_headings = excluded_headings; } } } delete settings.file_exclusions; delete settings.folder_exclusions; delete settings.excluded_headings; return settings; } // node_modules/obsidian-smart-env/node_modules/smart-environment/smart_env.js var ROOT_SCOPE = typeof globalThis !== "undefined" ? globalThis : Function("return this")(); var SmartEnv = class { static version = "2.2.4"; scope_name = "smart_env"; static global_ref = ROOT_SCOPE; global_ref = this.constructor.global_ref; constructor(opts = {}) { this.state = "init"; this._components = {}; this.collections = {}; this.load_timeout = null; this._collections_version_signature = null; this._events = SmartEvents.create(this, build_events_opts(this.config?.modules?.smart_events)); if (opts.primary_main_key) this.primary_main_key = opts.primary_main_key; } /** * Builds or returns the cached configuration object. * The cache is invalidated automatically whenever the β€œversion signature” * of any collection class changes (controlled by its static `version`). * * @returns {Object} the merged, up-to-date environment config */ get config() { const signature = this.compute_collections_version_signature(); if (this._config && signature === this._collections_version_signature) { return this._config; } this._collections_version_signature = signature; this._config = {}; const sorted_configs = Object.entries(this.smart_env_configs).sort(([main_key]) => { if (!this.primary_main_key) return 0; return main_key === this.primary_main_key ? -1 : 0; }); for (const [key, rec] of sorted_configs) { if (!rec?.main) { console.warn(`SmartEnv: '${key}' unloaded, skipping`); delete this.smart_env_configs[key]; continue; } if (!rec?.opts) { console.warn(`SmartEnv: '${key}' opts missing, skipping`); continue; } merge_env_config( this._config, deep_clone_config(normalize_opts(rec.opts)) ); } return this._config; } /** * Produces a deterministic string representing the current versions of every * collection class across all mains. When any collection ships a higher * `static version`, the signature changes – automatically invalidating the * cached `config`. * * @returns {string} pipe-delimited version signature */ compute_collections_version_signature() { const list = []; for (const rec of Object.values(this.smart_env_configs)) { const { opts } = rec || {}; if (!opts) continue; for (const [collection_key, def] of Object.entries(opts.collections || {})) { const cls = def?.class; const v = typeof cls?.version === "number" ? cls.version : 0; list.push(`${collection_key}:${v}`); } } return list.sort().join("|"); } // ======================================================================== // ── GLOBAL HELPERS / STATIC API ── // ======================================================================== get env_start_wait_time() { if (typeof this.config.env_start_wait_time === "number") return this.config.env_start_wait_time; return 5e3; } static get global_env() { return this.global_ref.smart_env; } static set global_env(env) { this.global_ref.smart_env = env; } static get mains() { return Object.keys(this.global_ref.smart_env_configs || {}); } get mains() { return Object.keys(this.global_ref.smart_env_configs || {}); } static get should_reload() { if (!this.global_env) return true; if (this.global_env.state === "loaded") return true; if (typeof this.global_env?.constructor?.version === "undefined") return true; if (compare_versions(this.version, this.global_env.constructor?.version) > 0) { console.warn( "SmartEnv: Reloading environment because of version mismatch", `${this.version} > ${this.global_env.constructor.version}` ); return true; } return false; } static get smart_env_configs() { if (!this.global_ref.smart_env_configs) this.global_ref.smart_env_configs = {}; return this.global_ref.smart_env_configs; } get smart_env_configs() { if (!this.global_ref.smart_env_configs) this.global_ref.smart_env_configs = {}; return this.global_ref.smart_env_configs; } /** * Serializes all collection data in the environment into a plain object. * @returns {object} */ to_json() { return Object.fromEntries( Object.entries(this).filter(([, val]) => typeof val?.collection_key !== "undefined").map(([key, collection]) => [key, collection_to_plain(collection)]) ); } /** * Waits for either a specific main to be registered in the environment, * or (if `opts.main` is not specified) waits for environment collections to load. * @param {object} opts * @param {object} [opts.main] - if set, the function waits until that main is found. * @returns {Promise} Resolves with the environment instance */ static wait_for(opts = {}) { return new Promise((resolve) => { if (opts.main) { const interval = setInterval(() => { if (this.global_env && this.global_env[opts.main]) { clearInterval(interval); resolve(this.global_env); } }, 1e3); } else { const interval = setInterval(() => { if (this.global_env && this.global_env.state === "loaded") { clearInterval(interval); resolve(this.global_env); } }, 100); } }); } /** * Creates or updates a SmartEnv instance. * - If a global environment exists and is an older version or lacks 'init_main', it is replaced. * @param {Object} main - The main object to be added to the SmartEnv instance. * @param {Object} [env_config] - Options for configuring the SmartEnv instance. * @returns {SmartEnv} The SmartEnv instance. * @throws {TypeError} If an invalid main object is provided. * @throws {Error} If there's an error creating or updating the SmartEnv instance. */ static async create(main, env_config) { if (!main || typeof main !== "object") { throw new TypeError("SmartEnv: Invalid main object provided"); } if (!env_config) throw new Error("SmartEnv.create: 'env_config' parameter is required."); env_config.version = this.version; this.add_main(main, env_config); if (this.should_reload) { const opts = {}; if (this.global_env && compare_versions(this.version, this.global_env.constructor?.version || 0) > 0) { opts.primary_main_key = camel_case_to_snake_case(main.constructor.name); } if (this.global_env?.load_timeout) clearTimeout(this.global_env.load_timeout); this.global_env = new this(opts); const g = this.global_ref; if (!g.all_envs) g.all_envs = []; g.all_envs.push(this.global_env); } clearTimeout(this.global_env.load_timeout); this.global_env.load_timeout = setTimeout(async () => { await this.global_env.load(); this.global_env.load_timeout = null; }, this.global_env.env_start_wait_time); return this.global_env; } static add_main(main, env_config = null) { if (this.global_env) { this.global_env._config = null; this.global_env._collections_version_signature = null; } const main_key = camel_case_to_snake_case(main.constructor.name); this.smart_env_configs[main_key] = { main, opts: env_config }; this.create_env_getter(main); } /** * Creates a dynamic environment getter on any instance object. * The returned 'env' property will yield the global `smart_env`. * @param {Object} instance_to_receive_getter */ static create_env_getter(instance_to_receive_getter) { Object.defineProperty(instance_to_receive_getter, "env", { configurable: true, get: () => this.global_env }); } create_env_getter(instance_to_receive_getter) { this.constructor.create_env_getter(instance_to_receive_getter); } async load() { this.state = "loading"; await this.fs.load_files(); if (!this.settings) await SmartSettings.create(this); if (this.config.default_settings) { deep_merge_no_overwrite(this.settings, this.config.default_settings); } migrate_exclusion_settings_2025_08_22(this.settings); this.smart_settings.save(); await this.init_collections(); for (const [main_key, { main, opts }] of Object.entries(this.smart_env_configs)) { this[main_key] = main; } await this.ready_to_load_collections(); await this.load_collections(); this.state = "loaded"; } /** * Initializes collection classes if they have an 'init' function. * @param {Object} [config=this.config] */ async init_collections(config = this.config) { for (const key of Object.keys(config.collections || {})) { const _class = config.collections[key]?.class; if (!_class) continue; if (_class.default_settings) { deep_merge_no_overwrite( this.settings, { [key]: _class.default_settings } ); } if (typeof _class.init !== "function") continue; await _class.init(this, { ...config.collections[key] }); this.collections[key] = "init"; } } /** * Hook/Override this method to wait for any conditions before loading collections. * @param {Object} main */ async ready_to_load_collections() { } /** * Loads any available collections, processing their load queues. * @param {Object} [collections=this.collections] - Key-value map of collection instances. */ async load_collections(collections = this.collections) { const collection_keys = Object.keys(collections || {}).sort((a, b) => { const order_a = this.config.collections?.[a]?.load_order || 0; const order_b = this.config.collections?.[b]?.load_order || 0; return order_a - order_b; }); for (const key of collection_keys) { const time_start = Date.now(); if (typeof this[key]?.process_load_queue === "function") { await this[key].process_load_queue(); this[key].load_time_ms = Date.now() - time_start; this.collections[key] = "loaded"; console.log(`Loaded ${this[key].collection_key} in ${this[key].load_time_ms}ms`); } } } /** * Removes a main from the global.smart_env_configs to exclude it on reload * @param {Class} main * @param {Object|null} [unload_config=null] */ static unload_main(main) { const main_key = camel_case_to_snake_case(main.constructor.name); this.smart_env_configs[main_key] = null; delete this.smart_env_configs[main_key]; } unload_main(main) { this.constructor.unload_main(main); } /** * Triggers a save event in all known collections. */ save() { for (const key of Object.keys(this.collections)) { this[key].process_save_queue?.(); } } /** * Initialize a module from the configured `this.opts.modules`. * @param {string} module_key * @param {object} opts * @returns {object|null} instance of the requested module or null if not found */ init_module(module_key, opts = {}) { const module_config = this.opts.modules[module_key]; if (!module_config) { return console.warn(`SmartEnv: module ${module_key} not found`); } opts = { ...{ ...module_config, class: null }, ...opts }; return new module_config.class(opts); } get notices() { if (!this._notices) { const SmartNoticesClass = this.config.modules.smart_notices.class; this._notices = new SmartNoticesClass(this, { adapter: this.config.modules.smart_notices.adapter }); } return this._notices; } /** * Exposes a settings template function from environment opts or defaults. * @returns {Function} */ get settings_template() { return this.opts.components?.smart_env?.settings || render; } /** * Renders settings UI into a container, using the environment's `settings_template`. * @param {HTMLElement} [container=this.settings_container] */ async render_settings(container = this.settings_container) { if (!this.settings_container || container !== this.settings_container) { this.settings_container = container; } if (!container) { throw new Error("Container is required"); } const frag = await this.render_component("settings", this, {}); this.smart_view.empty(container); container.appendChild(frag); return frag; } /** * Renders a named component using an optional scope and options. * @deprecated use env.smart_components.render instead (2025-10-11) * @param {string} component_key * @param {Object} scope * @param {Object} [opts] * @returns {Promise} */ async render_component(component_key, scope, opts = {}) { const component_renderer = this.get_component(component_key, scope); if (!component_renderer) { console.warn(`SmartEnv: component ${component_key} not found for scope ${scope.constructor.name}`); return this.smart_view.create_doc_fragment(`

Component Not Found

The component ${component_key} was not found for scope ${scope.constructor.name}.

`); } const frag = await component_renderer(scope, opts); return frag; } /** * Retrieves or creates a memoized component renderer function. * @deprecated use env.smart_components instead (2025-10-11) * @param {string} component_key * @param {Object} scope * @returns {Function|undefined} */ get_component(component_key, scope) { const scope_name = scope.collection_key ?? scope.scope_name; const _cache_key = scope_name ? `${scope_name}-${component_key}` : component_key; if (!this._components[_cache_key]) { try { if (this.opts.components[scope_name]?.[component_key]) { const component_config = this.opts.components[scope_name][component_key]; const component = component_config.render || component_config; this._components[_cache_key] = component.bind( this.init_module("smart_view") ); } else if (this.opts.components[component_key]) { const component_config = this.opts.components[component_key]; const component = component_config.render || component_config; this._components[_cache_key] = component.bind( this.init_module("smart_view") ); } else { console.warn( `SmartEnv: component ${component_key} not found for scope ${scope_name}` ); } } catch (e) { console.error("Error getting component", e); console.log( `scope_name: ${scope_name}; component_key: ${component_key}; this.opts.components: ${Object.keys( this.opts.components || {} ).join(", ")}; this.opts.components[scope_name]: ${Object.keys( this.opts.components[scope_name] || {} ).join(", ")}` ); } } return this._components[_cache_key]; } /** * A built-in settings schema for this environment. * @abstract * @returns {Object} */ get settings_config() { return {}; } get global_prop() { return this.opts.global_prop ?? "smart_env"; } get item_types() { return this.opts.item_types; } get fs_module_config() { return this.opts.modules.smart_fs; } get fs() { if (!this.smart_fs) { this.smart_fs = new this.fs_module_config.class(this, { adapter: this.fs_module_config.adapter, fs_path: this.opts.env_path || "" }); } return this.smart_fs; } get env_data_dir() { const env_settings_files = this.fs.file_paths?.filter((path) => path.endsWith("smart_env.json")) || []; let env_data_dir = ".smart-env"; if (env_settings_files.length > 0) { if (env_settings_files.length > 1) { const env_data_dir_counts = env_settings_files.map((path) => { const dir = path.split("/").slice(-2, -1)[0]; return { dir, count: this.fs.file_paths.filter((p) => p.includes(dir)).length }; }); env_data_dir = env_data_dir_counts.reduce( (max, dirObj) => dirObj.count > max.count ? dirObj : max, env_data_dir_counts[0] ).dir; } else { env_data_dir = env_settings_files[0].split("/").slice(-2, -1)[0]; } } return env_data_dir; } get data_fs() { if (!this._fs) { this._fs = new this.fs_module_config.class(this, { adapter: this.fs_module_config.adapter, fs_path: this.data_fs_path }); } return this._fs; } get data_fs_path() { if (!this._data_fs_path) { this._data_fs_path = (this.opts.env_path + (this.opts.env_path ? this.opts.env_path.includes("\\") ? "\\" : "/" : "") + this.env_data_dir).replace(/\\\\/g, "\\").replace(/\/\//g, "/"); } return this._data_fs_path; } /** * Saves the current settings to the file system. * @param {Object|null} [settings=null] - Optional settings to override the current settings before saving. * @returns {Promise} */ async save_settings(settings) { this._saved = false; if (!await this.data_fs.exists("")) { await this.data_fs.mkdir(""); } await this.data_fs.write("smart_env.json", JSON.stringify(settings, null, 2)); this._saved = true; } /** * Loads settings from the file system, merging with any `default_settings` * @returns {Promise} the loaded settings */ async load_settings() { if (!await this.data_fs.exists("smart_env.json")) await this.save_settings({}); let settings = JSON.parse(JSON.stringify(this.config.default_settings || {})); deep_merge(settings, JSON.parse(await this.data_fs.read("smart_env.json"))); this._saved = true; if (this.fs.auto_excluded_files) { const existing_file_exclusions = settings.smart_sources.file_exclusions.split(",").map((s) => s.trim()).filter(Boolean); settings.smart_sources.file_exclusions = [...existing_file_exclusions, ...this.fs.auto_excluded_files].filter((value, index, self) => self.indexOf(value) === index).join(","); } return settings; } /** * Refreshes file-system state if exclusions changed, * then re-renders relevant settings UI */ async update_exclusions() { this.smart_sources._fs = null; await this.smart_sources.init_fs(); } // DEPRECATED /** * Lazily instantiate the module 'smart_view'. * @deprecated use env.smart_components instead (2025-09-30) * @returns {object} */ get smart_view() { if (!this._smart_view) { this._smart_view = this.init_module("smart_view"); } return this._smart_view; } /** @deprecated access `this.state` and `collection.state` directly instead */ get collections_loaded() { return this.state === "loaded"; } /** @deprecated Use this['main_class_name'] instead of this.main/this.plugin */ get main() { return this.smart_env_configs[this.mains[0]]?.main; } /** * @deprecated use component pattern instead */ get ejs() { return this.opts.ejs; } /** * @deprecated use component pattern instead */ get templates() { return this.opts.templates; } /** * @deprecated use component pattern instead */ get views() { return this.opts.views; } /** * @deprecated use this.config instead */ get opts() { return this.config; } /** * @deprecated Use this.main_class_name instead of this.plugin */ get plugin() { return this.main; } }; function collection_to_plain(collection) { return { items: Object.fromEntries( Object.entries(collection.items || {}).map(([key, item]) => [key, item.data]) ) }; } function build_events_opts(module_config) { if (!module_config) return {}; if (typeof module_config === "function") { return { adapter_class: module_config }; } const adapter_class = module_config.adapter_class || module_config.adapter; return adapter_class ? { adapter_class } : {}; } // node_modules/obsidian-smart-env/node_modules/smart-file-system/utils/glob_to_regex.js function create_regex(pattern, { case_sensitive, extended_glob, windows_paths }) { const regex_pattern = glob_to_regex_pattern(pattern, extended_glob); const adjusted_pattern = adjust_for_windows_paths(regex_pattern, windows_paths); const flags = case_sensitive ? "" : "i"; return new RegExp(`^${adjusted_pattern}$`, flags); } function adjust_for_windows_paths(pattern, windows_paths) { return windows_paths ? pattern.replace(/\\\//g, "[\\\\/]").replace(/\\\\\\/g, "[\\\\/]") : pattern; } function glob_to_regex_pattern(pattern, extended_glob) { let in_class = false; let in_brace = 0; let result = ""; for (let i = 0; i < pattern.length; i++) { const char = pattern[i]; switch (char) { case "\\": if (i + 1 < pattern.length) { result += `\\${pattern[i + 1]}`; i++; } else { result += "\\\\"; } break; case "/": result += "\\/"; break; case "[": if (!in_class) { const closingIndex = pattern.indexOf("]", i + 1); if (closingIndex === -1) { result += "\\["; } else { in_class = true; if (pattern[i + 1] === "!") { result += "[^"; i++; } else { result += "["; } } } else { result += "\\["; } break; case "]": if (in_class) { in_class = false; result += "]"; } else { result += "\\]"; } break; case "{": if (!in_class) { const closingIndex = pattern.indexOf("}", i + 1); if (closingIndex === -1) { result += "\\{"; } else { in_brace++; result += "("; } } else { result += "\\{"; } break; case "}": if (!in_class && in_brace > 0) { in_brace--; result += ")"; } else { result += "\\}"; } break; case ",": if (!in_class && in_brace > 0) { result += "|"; } else { result += ","; } break; case "*": if (!in_class) { if (i + 1 < pattern.length && pattern[i + 1] === "*") { result += ".*"; i++; } else { result += "[^/]*"; } } else { result += "\\*"; } break; case "?": if (!in_class) { result += "[^/]"; } else { result += "\\?"; } break; // We escape these to ensure they remain literal case "(": case ")": case "+": case "|": case "^": case "$": case ".": result += `\\${char}`; break; default: result += char; break; } } if (in_class) { result += "]"; in_class = false; } if (extended_glob) { result = result.replace(/\\\+\\\((.*?)\\\)/g, "($1)+").replace(/\\\@\\\((.*?)\\\)/g, "($1)").replace(/\\\!\\\((.*?)\\\)/g, "(?!$1).*").replace(/\\\?\\\((.*?)\\\)/g, "($1)?").replace(/\\\*\\\((.*?)\\\)/g, "($1)*"); } return result; } function glob_to_regex(pattern, options = {}) { const default_options = { case_sensitive: true, extended_glob: false, windows_paths: false }; const merged_options = { ...default_options, ...options }; if (pattern === "") { return /^$/; } if (pattern === "*" && !merged_options.windows_paths) { return /^[^/]+$/; } if (pattern === "**" && !merged_options.windows_paths) { return /^.+$/; } return create_regex(pattern, merged_options); } // node_modules/obsidian-smart-env/node_modules/smart-file-system/utils/fuzzy_search.js function fuzzy_search(arr, search_term) { let matches = []; for (let i = 0; i < arr.length; i++) { const search_chars = search_term.toLowerCase().split(""); let match = true; let distance = 0; const name = arr[i]; const label_name = name.toLowerCase(); for (let j = 0; j < search_chars.length; j++) { const search_index = label_name.substring(distance).indexOf(search_chars[j]); if (search_index >= 0) { distance += search_index + 1; } else { match = false; break; } } if (match) matches.push({ name, distance }); } matches.sort((a, b) => a.distance - b.distance); return matches.map((match) => match.name); } // node_modules/obsidian-smart-env/node_modules/smart-file-system/smart_fs.js var SmartFs = class { /** * Create a new SmartFs instance * * @param {Object} env - The Smart Environment instance * @param {Object} [opts={}] - Optional configuration * @param {string} [opts.fs_path] - Custom environment path */ constructor(env, opts = {}) { this.env = env; this.opts = opts; this.fs_path = opts.fs_path || opts.env_path || ""; if (!opts.adapter) throw new Error("SmartFs requires an adapter"); this.adapter = new opts.adapter(this); this.excluded_patterns = []; if (Array.isArray(opts.exclude_patterns)) { opts.exclude_patterns.forEach((pattern) => this.add_ignore_pattern(pattern)); } this.folders = {}; this.files = {}; this.file_paths = []; this.folder_paths = []; this.auto_excluded_files = []; } async refresh() { this.files = {}; this.file_paths = []; this.folders = {}; this.folder_paths = []; await this.init(); } async init() { await this.load_exclusions(); await this.load_files(); } async load_files() { const all = await this.list_recursive(); this.file_paths = []; this.folder_paths = []; all.forEach((file) => { if (file.type === "file") { this.files[file.path] = file; this.file_paths.push(file.path); } else if (file.type === "folder") { this.folders[file.path] = file; this.folder_paths.push(file.path); } }); } include_file(file_path) { const file = this.adapter.get_file(file_path); this.files[file.path] = file; this.file_paths.push(file.path); return file; } /** * Load .gitignore patterns * * @returns {Promise} Array of RegExp patterns */ async load_exclusions() { const gitignore_path = ".gitignore"; const gitignore_exists = await this.adapter.exists(gitignore_path); if (gitignore_exists && !this.env.settings.skip_excluding_gitignore) { const gitignore_content = await this.adapter.read(gitignore_path, "utf-8"); gitignore_content.split("\n").filter((line) => !line.startsWith("#")).filter(Boolean).forEach((pattern) => this.add_ignore_pattern(pattern)); } this.add_ignore_pattern(".**"); this.add_ignore_pattern("**/.**"); this.add_ignore_pattern("**/.*/**"); this.add_ignore_pattern("**/*.ajson"); } /** * Add a new ignore pattern * * @param {string} pattern - The pattern to add */ add_ignore_pattern(pattern, opts = {}) { this.excluded_patterns.push(glob_to_regex(pattern.trim(), opts)); } /** * Check if a path is ignored based on gitignore patterns * * @param {string} _path - The path to check * @returns {boolean} True if the path is ignored, false otherwise */ is_excluded(_path) { try { if (_path.includes("#")) return true; if (!this.excluded_patterns.length) return false; return this.excluded_patterns.some((pattern) => pattern.test(_path)); } catch (e) { console.error(`Error checking if path is excluded: ${e.message}`); console.error(`Path: `, _path); throw e; } } /** * Check if any path in an array of paths is excluded * * @param {string[]} paths - Array of paths to check * @returns {boolean} True if any path is excluded, false otherwise */ has_excluded_patterns(paths) { return paths.some((p) => this.is_excluded(p)); } /** * Pre-process an array of paths, throwing an error if any path is excluded * * @param {string[]} paths - Array of paths to pre-process * @throws {Error} If any path in the array is excluded * @returns {string[]} The array of paths */ pre_process(paths) { if (this.has_excluded_patterns(paths)) { throw new Error(`Path is excluded: ${paths.find((p) => this.is_excluded(p))}`); } return paths; } /** * Post-process the result of an operation * * @param {any} returned_value - The value returned by the operation * @returns {any} The post-processed value */ post_process(returned_value) { if (this.adapter.post_process) return this.adapter.post_process(returned_value); if (Array.isArray(returned_value)) { returned_value = returned_value.filter((r) => { if (typeof r === "string") return !this.is_excluded(r); if (typeof r === "object" && r.path) return !this.is_excluded(r.path); return true; }); } return returned_value; } // v2 /** * Use the adapter for a method * runs pre_process and post_process (checks exclusions) * @param {string} method - The method to use * @param {string[]} paths - The paths to use * @param {...any} args - Additional arguments for the method * @returns {Promise} The result of the method */ async use_adapter(method, paths, ...args) { if (!this.adapter[method]) throw new Error(`Method ${method} not found in adapter`); paths = this.pre_process(paths ?? []); let resp = await this.adapter[method](...paths, ...args); return this.post_process(resp); } use_adapter_sync(method, paths, ...args) { if (!this.adapter[method]) throw new Error(`Method ${method} not found in adapter`); paths = this.pre_process(paths ?? []); let resp = this.adapter[method](...paths, ...args); return this.post_process(resp); } /** * Append content to a file * * @param {string} rel_path - The relative path of the file to append to * @param {string|Buffer} content - The content to append * @returns {Promise} A promise that resolves when the operation is complete */ async append(rel_path, content) { return await this.use_adapter("append", [rel_path], content); } /** * Create a new directory * * @param {string} rel_path - The relative path of the directory to create * @returns {Promise} A promise that resolves when the operation is complete */ async mkdir(rel_path, opts = { recursive: true }) { return await this.use_adapter("mkdir", [rel_path], opts); } /** * Check if a file or directory exists * * @param {string} rel_path - The relative path to check * @returns {Promise} True if the path exists, false otherwise */ async exists(rel_path) { return await this.use_adapter("exists", [rel_path]); } exists_sync(rel_path) { return this.use_adapter_sync("exists_sync", [rel_path]); } /** * List files in a directory * * @param {string} rel_path - The relative path to list * @returns {Promise} Array of file paths */ async list(rel_path = "/") { return await this.use_adapter("list", [rel_path]); } async list_recursive(rel_path = "/") { return await this.use_adapter("list_recursive", [rel_path]); } async list_files(rel_path = "/") { return await this.use_adapter("list_files", [rel_path]); } async list_files_recursive(rel_path = "/") { return await this.use_adapter("list_files_recursive", [rel_path]); } async list_folders(rel_path = "/") { return await this.use_adapter("list_folders", [rel_path]); } async list_folders_recursive(rel_path = "/") { return await this.use_adapter("list_folders_recursive", [rel_path]); } /** * Read the contents of a file * * @param {string} rel_path - The relative path of the file to read * @returns {Promise} The contents of the file */ async read(rel_path, encoding = "utf-8") { try { const content = await this.adapter.read(rel_path, encoding); return content; } catch (error) { console.warn("Error during read: " + error.message, rel_path); if (error.code === "ENOENT") return null; return { error: error.message }; } } /** * Remove a file * * @param {string} rel_path - The relative path of the file to remove * @returns {Promise} A promise that resolves when the operation is complete */ async remove(rel_path) { return await this.use_adapter("remove", [rel_path]); } /** * Remove a directory * * @param {string} rel_path - The relative path of the directory to remove * @returns {Promise} A promise that resolves when the operation is complete */ async remove_dir(rel_path, recursive = false) { return await this.use_adapter("remove_dir", [rel_path], recursive); } /** * Rename a file or directory * * @param {string} rel_path - The current relative path * @param {string} new_rel_path - The new relative path * @returns {Promise} A promise that resolves when the operation is complete */ async rename(rel_path, new_rel_path) { await this.use_adapter("rename", [rel_path, new_rel_path]); await this.refresh(); } /** * Get file or directory statistics * * @param {string} rel_path - The relative path to get statistics for * @returns {Promise} An object containing file or directory statistics */ async stat(rel_path) { return await this.use_adapter("stat", [rel_path]); } /** * Write content to a file * Should handle when target path is within a folder that doesn't exist * * @param {string} rel_path - The relative path of the file to write to * @param {string|Buffer} content - The content to write * @returns {Promise} A promise that resolves when the operation is complete */ async write(rel_path, content) { try { await this.adapter.write(rel_path, content); } catch (error) { console.error("Error during write:", error); throw error; } } // // aliases // async create(rel_path, content) { return await this.use_adapter('write', [rel_path], content); } // async update(rel_path, content) { return await this.use_adapter('write', [rel_path], content); } get_link_target_path(link_target, source_path) { if (this.adapter.get_link_target_path) return this.adapter.get_link_target_path(link_target, source_path); if (!this.file_paths) return console.warn("get_link_target_path: file_paths not found"); const matching_file_paths = this.file_paths.filter((path) => path.includes(link_target)); return fuzzy_search(matching_file_paths, link_target)[0]; } get sep() { return this.adapter.sep || "/"; } get_full_path(rel_path = "") { return this.adapter.get_full_path(rel_path); } get base_path() { return this.adapter.get_base_path(); } }; // node_modules/obsidian-smart-env/src/adapters/smart-fs/obsidian.js var obsidian = __toESM(require("obsidian"), 1); var ObsidianFsAdapter = class { /** * Create an ObsidianFsAdapter instance * * @param {Object} smart_fs - The SmartFs instance */ constructor(smart_fs) { this.smart_fs = smart_fs; this.obsidian = obsidian; this.obsidian_app = smart_fs.env.main.app; this.obsidian_adapter = smart_fs.env.main.app.vault.adapter; } get fs_path() { return this.smart_fs.fs_path; } get_file(file_path) { const file = {}; file.path = file_path.replace(/\\/g, "/").replace(this.smart_fs.fs_path, "").replace(/^\//, ""); file.type = "file"; file.extension = file.path.split(".").pop().toLowerCase(); file.name = file.path.split("/").pop(); file.basename = file.name.split(".").shift(); Object.defineProperty(file, "stat", { get: () => { const tfile = this.obsidian_app.vault.getAbstractFileByPath(file_path); if (tfile) { return { ctime: tfile.stat.ctime, mtime: tfile.stat.mtime, size: tfile.stat.size, isDirectory: () => tfile instanceof this.obsidian.TFolder, isFile: () => tfile instanceof this.obsidian.TFile }; } return null; } }); return file; } /** * Append content to a file * * @param {string} rel_path - The relative path of the file to append to * @param {string} data - The content to append * @returns {Promise} A promise that resolves when the operation is complete */ async append(rel_path, data) { if (!rel_path.startsWith(this.fs_path)) rel_path = this.fs_path + "/" + rel_path; return await this.obsidian_adapter.append(rel_path, data); } /** * Create a new directory * * @param {string} rel_path - The relative path of the directory to create * @returns {Promise} A promise that resolves when the operation is complete */ async mkdir(rel_path) { if (!rel_path.startsWith(this.fs_path)) rel_path = this.fs_path + "/" + rel_path; return await this.obsidian_adapter.mkdir(rel_path); } /** * Check if a file or directory exists * * @param {string} rel_path - The relative path to check * @returns {Promise} True if the path exists, false otherwise */ async exists(rel_path) { if (!rel_path.startsWith(this.fs_path)) rel_path = this.fs_path + "/" + rel_path; return await this.obsidian_adapter.exists(rel_path); } exists_sync(rel_path) { return !!this.obsidian_app.vault.getAbstractFileByPath(rel_path); } /** * List files in a directory (NOT up-to-date with list_recursive) * * @param {string} rel_path - The relative path to list * @returns {Promise} Array of file paths */ async list(rel_path, opts = {}) { if (!rel_path.startsWith(this.fs_path)) rel_path = this.fs_path + "/" + rel_path; if (rel_path.startsWith("/")) rel_path = rel_path.slice(1); if (rel_path.endsWith("/")) rel_path = rel_path.slice(0, -1); if (rel_path.includes(".")) { const { files: file_paths } = await this.obsidian_adapter.list(rel_path); const files2 = file_paths.map((file_path) => { if (this.smart_fs.fs_path) file_path = file_path.replace(this.smart_fs.fs_path, "").slice(1); const file_name = file_path.split("/").pop(); const file = { basename: file_name.split(".")[0], extension: file_name.split(".").pop().toLowerCase(), name: file_name, path: file_path }; return file; }); return files2; } const files = this.obsidian_app.vault.getAllLoadedFiles().filter((file) => { const last_slash = file.path.lastIndexOf("/"); if (last_slash === -1 && rel_path !== "") return false; const folder_path = file.path.slice(0, last_slash); if (folder_path !== rel_path) return false; return true; }); return files; } // NOTE: currently does not handle hidden files and folders async list_recursive(rel_path, opts = {}) { if (!rel_path.startsWith(this.fs_path)) rel_path = this.fs_path + "/" + rel_path; if (rel_path.startsWith("/")) rel_path = rel_path.slice(1); if (rel_path.endsWith("/")) rel_path = rel_path.slice(0, -1); const files = this.obsidian_app.vault.getAllLoadedFiles().filter((file) => { if (file.path.length > 200) { this.smart_fs.auto_excluded_files.push(file.path); return false; } if (rel_path !== "" && !file.path.startsWith(rel_path)) return false; if (file instanceof this.obsidian.TFile) { if (opts.type === "folder") return false; file.type = "file"; } else if (file instanceof this.obsidian.TFolder) { if (opts.type === "file") return false; delete file.basename; delete file.extension; file.type = "folder"; } if (this.smart_fs.fs_path) file.path = file.path.replace(this.smart_fs.fs_path, "").slice(1); return true; }); return files; } async list_files(rel_path) { return await this.list(rel_path, { type: "file" }); } async list_files_recursive(rel_path) { return await this.list_recursive(rel_path, { type: "file" }); } async list_folders(rel_path) { return await this.list(rel_path, { type: "folder" }); } async list_folders_recursive(rel_path) { return await this.list_recursive(rel_path, { type: "folder" }); } /** * Read the contents of a file * * @param {string} rel_path - The relative path of the file to read * @returns {Promise} The contents of the file */ async read(rel_path, encoding, opts = {}) { if (!rel_path.startsWith(this.fs_path)) rel_path = this.fs_path + "/" + rel_path; if (encoding === "utf-8") { if (!opts.no_cache) { const tfile = this.obsidian_app.vault.getFileByPath(rel_path); if (tfile) return await this.obsidian_app.vault.cachedRead(tfile); } return await this.obsidian_adapter.read(rel_path); } if (encoding === "base64") { const array_buffer2 = await this.obsidian_adapter.readBinary(rel_path, "base64"); const base642 = this.obsidian.arrayBufferToBase64(array_buffer2); return base642; } const array_buffer = await this.obsidian_adapter.readBinary(rel_path); return array_buffer; } /** * Rename a file or directory * * @param {string} old_path - The current path of the file or directory * @param {string} new_path - The new path for the file or directory * @returns {Promise} A promise that resolves when the operation is complete */ async rename(old_path, new_path) { if (!old_path.startsWith(this.fs_path)) old_path = this.fs_path + "/" + old_path; if (!new_path.startsWith(this.fs_path)) new_path = this.fs_path + "/" + new_path; return await this.obsidian_adapter.rename(old_path, new_path); } /** * Remove a file * * @param {string} rel_path - The relative path of the file to remove * @returns {Promise} A promise that resolves when the operation is complete */ async remove(rel_path) { if (!rel_path.startsWith(this.fs_path)) rel_path = this.fs_path + "/" + rel_path; try { return await this.obsidian_adapter.remove(rel_path); } catch (error) { console.warn(`Error removing file: ${rel_path}`, error); } } /** * Remove a directory * * @param {string} rel_path - The relative path of the directory to remove * @returns {Promise} A promise that resolves when the operation is complete */ async remove_dir(rel_path, recursive = false) { if (!rel_path.startsWith(this.fs_path)) rel_path = this.fs_path + "/" + rel_path; return await this.obsidian_adapter.rmdir(rel_path, recursive); } /** * Get file or directory information * * @param {string} rel_path - The relative path of the file or directory * @returns {Promise} An object containing file or directory information */ async stat(rel_path) { if (!rel_path.startsWith(this.fs_path)) rel_path = this.fs_path + "/" + rel_path; return await this.obsidian_adapter.stat(rel_path); } /** * Write content to a file * * @param {string} rel_path - The relative path of the file to write to * @param {string} data - The content to write * @returns {Promise} A promise that resolves when the operation is complete */ async write(rel_path, data) { if (!data) data = ""; if (!rel_path.startsWith(this.fs_path)) rel_path = this.fs_path + "/" + rel_path; const folder_path = rel_path.split("/").slice(0, -1).join("/"); if (!await this.exists(folder_path)) { await this.mkdir(folder_path); console.log(`Created folder: ${folder_path}`); } return await this.obsidian_adapter.write(rel_path, data); } get_link_target_path(link_path, file_path) { return this.obsidian_app.metadataCache.getFirstLinkpathDest(link_path, file_path)?.path; } get_base_path() { return this.obsidian_adapter.basePath; } get_full_path(rel_path = "") { const sep = rel_path.includes("/") ? "/" : "\\"; return this.get_base_path() + sep + rel_path; } /** * Registers Obsidian vault/workspace listeners that emit Smart Environment events for Smart Sources. * @param {import('smart-sources').SmartSources} sources_collection * @returns {boolean} */ register_source_watchers(sources_collection) { if (this._source_watchers_registered) return this._source_watchers_registered; const plugin = this.smart_fs.env?.main; if (!plugin?.registerEvent) { console.warn("ObsidianFsAdapter: Unable to register source watchers without plugin context"); return false; } const { app } = plugin; const emit_event = (event_key, payload) => { if (!payload?.path && !payload?.item_key) return; this.smart_fs.env.events?.emit(event_key, { collection_key: sources_collection.collection_key, item_key: payload.item_key || payload.path, ...payload }); }; plugin.registerEvent( app.vault.on("create", (file) => { emit_event("sources:created", { path: file.path, event_source: "obsidian:vault.create" }); }) ); plugin.registerEvent( app.vault.on("modify", (file) => { emit_event("sources:modified", { path: file.path, event_source: "obsidian:vault.modify" }); }) ); plugin.registerEvent( app.vault.on("rename", (file, old_path) => { emit_event("sources:renamed", { path: file.path, old_path, event_source: "obsidian:vault.rename" }); }) ); plugin.registerEvent( app.vault.on("delete", (file) => { emit_event("sources:deleted", { path: file.path, event_source: "obsidian:vault.delete" }); }) ); plugin.registerEvent( app.workspace.on("editor-change", (_editor, info) => { const file = info?.file; if (!file) return; emit_event("sources:modified", { path: file.path, event_source: "obsidian:workspace.editor-change" }); }) ); this._source_watchers_registered = true; return true; } }; // node_modules/obsidian-smart-env/node_modules/smart-view/utils/empty.js function empty(elm) { const range = document.createRange(); range.selectNodeContents(elm); range.deleteContents(); } // node_modules/obsidian-smart-env/node_modules/smart-view/utils/replace_html.js var replace_html = /* @__PURE__ */ (() => { const cache = /* @__PURE__ */ new Map(); return (container, html_snippet) => { const key = html_snippet.trim(); let tpl = cache.get(key); if (!tpl) { tpl = document.createElement("template"); tpl.innerHTML = key; cache.set(key, tpl); } container.replaceChildren(tpl.content.cloneNode(true)); }; })(); // node_modules/obsidian-smart-env/node_modules/smart-view/utils/replace_with_fragment.js var replace_with_fragment = (container, html_snippet) => { const range = document.createRange(); const frag = range.createContextualFragment(html_snippet.trim()); container.replaceChildren(frag); }; // node_modules/obsidian-smart-env/node_modules/smart-view/utils/safe_inner_html.js var restricted_re = /<(td|th|tr|thead|tbody|tfoot|caption|col|colgroup|option|optgroup|li|dt|dd|source|track)\b/i; var safe_inner_html = (container, html_snippet) => { const trimmed = html_snippet.trim(); (restricted_re.test(trimmed) ? replace_with_fragment : replace_html)(container, trimmed); }; // node_modules/obsidian-smart-env/node_modules/smart-utils/escape_html.js function escape_html(str = "") { return String(str).replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); } // node_modules/obsidian-smart-env/node_modules/smart-utils/create_hash.js function murmur_hash_32(input_string, seed = 0) { let remainder = input_string.length & 3; let bytes = input_string.length - remainder; let h1 = seed; let c1 = 3432918353; let c2 = 461845907; let i = 0; let k1 = 0; let chunk = 0; while (i < bytes) { chunk = input_string.charCodeAt(i) & 255 | (input_string.charCodeAt(i + 1) & 255) << 8 | (input_string.charCodeAt(i + 2) & 255) << 16 | (input_string.charCodeAt(i + 3) & 255) << 24; i += 4; k1 = chunk; k1 = multiply_32(k1, c1); k1 = rotate_left_32(k1, 15); k1 = multiply_32(k1, c2); h1 ^= k1; h1 = rotate_left_32(h1, 13); h1 = h1 * 5 + 3864292196 | 0; } k1 = 0; switch (remainder) { case 3: k1 ^= (input_string.charCodeAt(i + 2) & 255) << 16; // falls through case 2: k1 ^= (input_string.charCodeAt(i + 1) & 255) << 8; // falls through case 1: k1 ^= input_string.charCodeAt(i) & 255; k1 = multiply_32(k1, c1); k1 = rotate_left_32(k1, 15); k1 = multiply_32(k1, c2); h1 ^= k1; break; } h1 ^= input_string.length; h1 = fmix_32(h1); return h1 | 0; } function murmur_hash_32_alphanumeric(input_string, seed = 0) { const signed_hash = murmur_hash_32(input_string, seed); const unsigned_hash = signed_hash >>> 0; return unsigned_hash.toString(36); } function multiply_32(a, b) { return (a & 65535) * b + ((a >>> 16) * b << 16) | 0; } function rotate_left_32(value, shift) { return value << shift | value >>> 32 - shift; } function fmix_32(h) { h ^= h >>> 16; h = multiply_32(h, 2246822507); h ^= h >>> 13; h = multiply_32(h, 3266489909); h ^= h >>> 16; return h | 0; } // node_modules/obsidian-smart-env/node_modules/smart-utils/convert_to_time_ago.js function convert_to_time_ago(timestamp) { const now = Date.now(); const ms = timestamp < 1e12 ? timestamp * 1e3 : timestamp; const diff_ms = now - ms; const is_future = diff_ms < 0; const seconds = Math.floor(Math.abs(diff_ms) / 1e3); const intervals = [ { label: "year", seconds: 31536e3 }, { label: "month", seconds: 2592e3 }, { label: "day", seconds: 86400 }, { label: "hour", seconds: 3600 }, { label: "minute", seconds: 60 }, { label: "second", seconds: 1 } ]; for (const interval of intervals) { const count = Math.floor(seconds / interval.seconds); if (count >= 1) { const suffix = `${count} ${interval.label}${count > 1 ? "s" : ""}`; return is_future ? `in ${suffix}` : `${suffix} ago`; } } return "just now"; } // node_modules/obsidian-smart-env/node_modules/smart-utils/cos_sim.js function cos_sim(vector1 = [], vector2 = []) { if (vector1.length !== vector2.length) { throw new Error("Vectors must have the same length"); } let dot_product = 0; let magnitude1 = 0; let magnitude2 = 0; const epsilon = 1e-8; for (let i = 0; i < vector1.length; i++) { dot_product += vector1[i] * vector2[i]; magnitude1 += vector1[i] * vector1[i]; magnitude2 += vector2[i] * vector2[i]; } magnitude1 = Math.sqrt(magnitude1); magnitude2 = Math.sqrt(magnitude2); if (magnitude1 < epsilon || magnitude2 < epsilon) return 0; return dot_product / (magnitude1 * magnitude2); } // node_modules/obsidian-smart-env/node_modules/smart-utils/get_by_path.js function get_by_path(obj, path, scope = null) { if (!path) return ""; const keys = path.split("."); if (scope) { keys.unshift(scope); } const final_key = keys.pop(); const instance = keys.reduce((acc, key) => acc && acc[key], obj); if (instance && typeof instance[final_key] === "function") { return instance[final_key].bind(instance); } return instance ? instance[final_key] : void 0; } // node_modules/obsidian-smart-env/node_modules/smart-utils/set_by_path.js function set_by_path(obj, path, value, scope = null) { const keys = path.split("."); if (scope) { keys.unshift(scope); } const final_key = keys.pop(); const target = keys.reduce((acc, key) => { if (!acc[key] || typeof acc[key] !== "object") { acc[key] = {}; } return acc[key]; }, obj); target[final_key] = value; } // node_modules/obsidian-smart-env/node_modules/smart-utils/delete_by_path.js function delete_by_path(obj, path, scope = null) { const keys = path.split("."); if (scope) { keys.unshift(scope); } const final_key = keys.pop(); const instance = keys.reduce((acc, key) => acc && acc[key], obj); if (instance) { delete instance[final_key]; } } // node_modules/obsidian-smart-env/node_modules/smart-utils/geom.js function compute_centroid(points) { if (!points || points.length === 0) { return null; } const n = points.length; const dim = points[0].length; const sums = new Float64Array(dim); for (let i = 0; i < n; i++) { const p = points[i]; for (let d = 0; d < dim; d++) { sums[d] += p[d]; } } for (let d = 0; d < dim; d++) { sums[d] /= n; } return Array.from(sums); } function compute_medoid(points) { if (!points || points.length === 0) { return null; } if (points.length === 1) { return points[0]; } const n = points.length; const dim = points[0].length; const sum_of_distances = new Float64Array(n); for (let i = 0; i < n - 1; i++) { const p_i = points[i]; for (let j = i + 1; j < n; j++) { const p_j = points[j]; let dist_sq = 0; for (let d = 0; d < dim; d++) { const diff = p_i[d] - p_j[d]; dist_sq += diff * diff; } const dist = Math.sqrt(dist_sq); sum_of_distances[i] += dist; sum_of_distances[j] += dist; } } let min_index = 0; let min_sum = sum_of_distances[0]; for (let i = 1; i < n; i++) { if (sum_of_distances[i] < min_sum) { min_sum = sum_of_distances[i]; min_index = i; } } return points[min_index]; } // node_modules/obsidian-smart-env/node_modules/smart-view/smart_view.js var element_disposers = /* @__PURE__ */ new WeakMap(); var smart_setting_listeners = /* @__PURE__ */ new WeakMap(); var SmartView = class { static version = 0.1; /** * @constructor * @param {object} opts - Additional options or overrides for rendering. */ constructor(opts = {}) { this.opts = opts; this._adapter = null; } /** * Renders all setting components within a container. * @async * @param {HTMLElement|DocumentFragment} container - The container element. * @param {Object} opts - Additional options for rendering. * @returns {Promise} */ async render_setting_components(container, opts = {}) { const components = container.querySelectorAll(".setting-component"); const promises = []; for (const component of components) { promises.push(this.render_setting_component(component, opts)); } await Promise.all(promises); return container; } /** * Creates a document fragment from HTML string. * @param {string} html - The HTML string. * @returns {DocumentFragment} */ create_doc_fragment(html) { return document.createRange().createContextualFragment(html); } /** * Gets the adapter instance used for rendering (e.g., Obsidian or Node, etc.). * @returns {Object} The adapter instance. */ get adapter() { if (!this._adapter) { if (!this.opts.adapter) { throw new Error("No adapter provided to SmartView. Provide a 'smart_view.adapter' in env config."); } const AdapterClass = this.opts.adapter; this._adapter = new AdapterClass(this); } return this._adapter; } /** * Gets an icon (implemented in the adapter). * @param {string} icon_name - Name of the icon to get. * @returns {string} The icon HTML string. */ get_icon_html(icon_name) { return this.adapter.get_icon_html(icon_name); } /** * Renders a single setting component (implemented in adapter). * @async * @param {HTMLElement} setting_elm - The DOM element for the setting. * @param {Object} opts - Additional options for rendering. * @returns {Promise<*>} */ async render_setting_component(setting_elm, opts = {}) { return await this.adapter.render_setting_component(setting_elm, opts); } /** * Renders markdown content (implemented in adapter). * @param {string} markdown - The markdown content. * @param {object|null} scope - The scope to pass for rendering. * @returns {Promise} */ async render_markdown(markdown, scope = null) { return await this.adapter.render_markdown(markdown, scope); } /** * Gets a value from an object by path. * @param {Object} obj - The object to search in. * @param {string} path - The path to the value. * @returns {*} */ get_by_path(obj, path, settings_scope = null) { return get_by_path(obj, path, settings_scope); } /** * Sets a value in an object by path. * @param {Object} obj - The object to modify. * @param {string} path - The path to set the value. * @param {*} value - The value to set. */ set_by_path(obj, path, value, settings_scope = null) { set_by_path(obj, path, value, settings_scope); } /** * Deletes a value from an object by path. * @param {Object} obj - The object to modify. * @param {string} path - The path to delete the value. */ delete_by_path(obj, path, settings_scope = null) { delete_by_path(obj, path, settings_scope); } /** * Escapes HTML special characters in a string. * @param {string} str - The string to escape. * @returns {string} The escaped string. */ escape_html(str) { return escape_html(str); } /** * A convenience method to build a setting HTML snippet from a config object. * @param {Object} setting_config * @returns {string} */ render_setting_html(setting_config) { if (setting_config.type === "html") { return setting_config.value; } const attributes = Object.entries(setting_config).map(([attr, value]) => { if (attr.includes("class")) return ""; if (typeof value === "number") return `data-${attr.replace(/_/g, "-")}=${value}`; return `data-${attr.replace(/_/g, "-")}="${value}"`; }).join("\n"); return `
`; } /** * Renders settings from a config, returning a fragment. * @async * @deprecated Use render_settings_config utility in Obsidian (obsidian-smart-env) * @param {Object} settings_config * @param {Object} opts * @param {Object} [opts.scope={}] - The scope to use when rendering settings (should have settings property). * @returns {Promise} */ async render_settings(settings_config12, opts = {}) { const is_fx = typeof settings_config12 === "function"; const html = Object.entries(is_fx ? await settings_config12(opts.scope) : settings_config12).map(([setting_key, setting_config]) => { if (!setting_config.setting) { setting_config.setting = setting_key; } return this.render_setting_html(setting_config); }).join("\n"); const frag = this.create_doc_fragment(`
${html}
`); return await this.render_setting_components(frag, opts); } /** * Scans the given container for elements that have `data-smart-setting` and attaches * a 'change' event listener that updates the corresponding path in `scope.settings`. * * Listener bookkeeping is done via WeakMap, not DOM attributes, to avoid * clone/attribute-related bugs on re-render. * * @param {Object} scope - An object containing a `settings` property, where new values will be stored. * @param {HTMLElement|Document} [container=document] - The DOM element to scan. Defaults to the entire document. */ add_settings_listeners(scope, container = document) { if (!container || typeof container.querySelectorAll !== "function") return; const elements = container.querySelectorAll("[data-smart-setting]"); elements.forEach((elm) => { const path = elm.dataset.smartSetting; if (!path) return; if (smart_setting_listeners.has(elm)) { return; } const handler = () => { let new_value; if (elm instanceof HTMLInputElement) { if (elm.type === "checkbox") { new_value = elm.checked; } else if (elm.type === "radio") { if (elm.checked) { new_value = elm.value; } else { return; } } else { new_value = elm.value; } } else if (elm instanceof HTMLSelectElement || elm instanceof HTMLTextAreaElement) { new_value = elm.value; } else { new_value = elm.value ?? elm.textContent; } this.set_by_path(scope.settings, path, new_value); }; smart_setting_listeners.set(elm, handler); elm.addEventListener("change", handler); if (elm instanceof HTMLElement) { this.attach_disposer(elm, () => { const existing = smart_setting_listeners.get(elm); if (existing) { elm.removeEventListener("change", existing); smart_setting_listeners.delete(elm); } }); } }); } apply_style_sheet(sheet) { if (typeof sheet === "string") { const css_hash = murmur_hash_32_alphanumeric(sheet); if (document.getElementById(`style-sheet-${css_hash}`)) { return; } const styleEl = document.createElement("style"); styleEl.id = `style-sheet-${css_hash}`; styleEl.textContent = sheet; document.head.appendChild(styleEl); return; } if ("adoptedStyleSheets" in Document.prototype) { document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet]; } else { const styleEl = document.createElement("style"); if (sheet.cssRules) { styleEl.textContent = Array.from(sheet.cssRules).map((rule) => rule.cssText).join("\n"); } document.head.appendChild(styleEl); } } empty(elm) { empty(elm); } safe_inner_html(elm, html) { safe_inner_html(elm, html); } /** * Attaches one or more disposer functions to an element that will be called * when that element has been observed in the DOM and is later removed. * * - Multiple calls for the same element accumulate disposer functions. * - Disposers are only invoked once, on the first removal after the * element has been in the DOM. * - No DOM attributes or properties are used for bookkeeping; everything * is tracked via WeakMap. * * @param {HTMLElement} el - The element to monitor. * @param {Function|Function[]} dispose - The disposer function or array of functions to call on removal. */ attach_disposer(el, dispose) { if (!el) return; const doc = el.ownerDocument; const win = doc && doc.defaultView; const MutationObserverCtor = win && win.MutationObserver; if (!doc || !win || !MutationObserverCtor || !doc.body) return; let dispose_fns; if (typeof dispose === "function") { dispose_fns = [dispose]; } else if (Array.isArray(dispose)) { dispose_fns = dispose.filter((fn) => typeof fn === "function"); } else { console.warn("[smart-view] attach_disposer called with invalid disposer"); return; } if (!dispose_fns.length) { console.warn("[smart-view] attach_disposer called with no valid disposer functions"); return; } let entry = element_disposers.get(el); if (!entry) { entry = { dispose_fns: /* @__PURE__ */ new Set(), observer: null, has_been_in_dom: false, disposed: false }; element_disposers.set(el, entry); } if (entry.disposed) { entry.disposed = false; entry.has_been_in_dom = false; } for (const fn of dispose_fns) { entry.dispose_fns.add(fn); } if (!entry.observer) { const observer = new MutationObserverCtor(() => { const in_dom = doc.body.contains(el); if (in_dom) { entry.has_been_in_dom = true; return; } if (!entry.has_been_in_dom || entry.disposed) { return; } entry.disposed = true; try { for (const fn of entry.dispose_fns) { try { fn(); } catch (err) { console.error("[smart-view] disposer error", err); } } } finally { try { observer.disconnect(); } catch (e) { } if (element_disposers.get(el) === entry) { element_disposers.delete(el); } } }); entry.observer = observer; observer.observe(doc.body, { childList: true, subtree: true }); } } }; // node_modules/obsidian-smart-env/node_modules/smart-view/adapters/_adapter.js var SmartViewAdapter = class { constructor(main) { this.main = main; } // NECESSARY OVERRIDES /** * Retrieves the class used for settings. * Must be overridden by subclasses to return the appropriate setting class. * @abstract * @returns {Function} The setting class constructor. * @throws Will throw an error if not implemented in the subclass. */ get setting_class() { throw new Error("setting_class() not implemented"); } /** * Generates the HTML for a specified icon. * Must be overridden by subclasses to provide the correct icon HTML. * @abstract * @param {string} icon_name - The name of the icon to generate HTML for. * @returns {string} The HTML string representing the icon. * @throws Will throw an error if not implemented in the subclass. */ get_icon_html(icon_name) { throw new Error("get_icon_html() not implemented"); } /** * Renders Markdown content within a specific scope. * Must be overridden by subclasses to handle Markdown rendering appropriately. * @abstract * @param {string} markdown - The Markdown content to render. * @param {object|null} [scope=null] - The scope within which to render the Markdown. * @returns {Promise} A promise that resolves when rendering is complete. * @throws Will throw an error if not implemented in the subclass. */ async render_markdown(markdown, scope = null) { throw new Error("render_markdown() not implemented"); } /** * Opens a specified URL. * Should be overridden by subclasses to define how URLs are opened. * @abstract * @param {string} url - The URL to open. */ open_url(url) { throw new Error("open_url() not implemented"); } /** * Handles the selection of a folder by invoking the folder selection dialog and updating the setting. * @abstract * @param {string} setting - The path of the setting being modified. * @param {string} value - The current value of the setting. * @param {HTMLElement} elm - The HTML element associated with the setting. * @param {object} scope - The current scope containing settings and actions. */ handle_folder_select(path, value, elm, scope) { throw new Error("handle_folder_select not implemented"); } /** * Handles the selection of a file by invoking the file selection dialog and updating the setting. * @abstract * @param {string} setting - The path of the setting being modified. * @param {string} value - The current value of the setting. * @param {HTMLElement} elm - The HTML element associated with the setting. * @param {object} scope - The current scope containing settings and actions. */ handle_file_select(path, value, elm, scope) { throw new Error("handle_file_select not implemented"); } /** * Performs actions before a setting is changed, such as clearing notices and updating the UI. * @abstract * @param {string} setting - The path of the setting being changed. * @param {*} value - The new value for the setting. * @param {HTMLElement} elm - The HTML element associated with the setting. * @param {object} scope - The current scope containing settings and actions. */ pre_change(path, value, elm) { } /** * Performs actions after a setting is changed, such as updating UI elements. * @abstract * @param {string} setting - The path of the setting that was changed. * @param {*} value - The new value for the setting. * @param {HTMLElement} elm - The HTML element associated with the setting. * @param {object} changed - Additional information about the change. */ post_change(path, value, elm) { } /** * Reverts a setting to its previous value in case of validation failure or error. * @abstract * @param {string} setting - The path of the setting to revert. * @param {HTMLElement} elm - The HTML element associated with the setting. * @param {object} scope - The current scope containing settings. */ revert_setting(path, elm, scope) { console.warn("revert_setting() not implemented"); } // DEFAULT IMPLEMENTATIONS (may be overridden) get setting_renderers() { return { text: this.render_text_component, string: this.render_text_component, password: this.render_password_component, number: this.render_number_component, dropdown: this.render_dropdown_component, toggle: this.render_toggle_component, textarea: this.render_textarea_component, textarea_array: this.render_textarea_array_component, button: this.render_button_component, remove: this.render_remove_component, folder: this.render_folder_select_component, "text-file": this.render_file_select_component, file: this.render_file_select_component, slider: this.render_slider_component, html: this.render_html_component, button_with_confirm: this.render_button_with_confirm_component, json: this.render_json_component, array: this.render_array_component }; } async render_setting_component(elm, opts = {}) { this.empty(elm); const path = elm.dataset.setting; const scope = opts.scope || this.main.main; const settings_scope = opts.settings_scope || null; try { let value = elm.dataset.value ?? this.main.get_by_path(scope.settings, path, settings_scope); if (typeof value === "undefined" && typeof elm.dataset.default !== "undefined") { value = elm.dataset.default; if (typeof value === "string") value = value.toLowerCase() === "true" ? true : value === "false" ? false : value; this.main.set_by_path(scope.settings, path, value, settings_scope); } const renderer = this.setting_renderers[elm.dataset.type]; if (!renderer) { console.warn(`Unsupported setting type: ${elm.dataset.type}`); return elm; } const setting = renderer.call(this, elm, path, value, scope, settings_scope); if (elm.dataset.name) setting.setName(elm.dataset.name); if (elm.dataset.description) { const frag = this.main.create_doc_fragment(`${elm.dataset.description}`); setting.setDesc(frag); } if (elm.dataset.tooltip) setting.setTooltip(elm.dataset.tooltip); this.add_button_if_needed(setting, elm, path, scope); this.handle_disabled_and_hidden(elm); return elm; } catch (e) { console.error(JSON.stringify({ path, elm }, null, 2)); console.error(JSON.stringify(e, null, 2)); } } render_dropdown_component(elm, path, value, scope, settings_scope) { const smart_setting = new this.setting_class(elm); let options; smart_setting.addDropdown((dropdown) => { if (elm.dataset.required) dropdown.selectEl.setAttribute("required", true); const opts_callback = elm.dataset.optionsCallback ? this.main.get_by_path(scope, elm.dataset.optionsCallback) : null; if (typeof opts_callback === "function") { console.log(`getting options callback: ${elm.dataset.optionsCallback}`); Promise.resolve(opts_callback()).then((opts) => { opts.forEach((option) => { const opt = dropdown.addOption(option.value, option.label ?? option.name ?? option.value); opt.selected = option.value === value; if (opts.length === 1 && opt.selected) dropdown.selectEl.classList.add("dropdown-no-options"); }); dropdown.setValue(value); }); } else { if (!options || !options.length) { options = this.get_dropdown_options(elm); } options.forEach((option) => { const opt = dropdown.addOption(option.value, option.label ?? option.name ?? option.value); opt.selected = option.value === value; if (options.length === 1 && opt.selected) dropdown.selectEl.classList.add("dropdown-no-options"); }); dropdown.setValue(value); } dropdown.onChange((value2) => { this.handle_on_change(path, value2, elm, scope, settings_scope); }); }); return smart_setting; } render_text_component(elm, path, value, scope, settings_scope) { const smart_setting = new this.setting_class(elm); smart_setting.addText((text) => { text.setPlaceholder(elm.dataset.placeholder || ""); if (value) text.setValue(value); let debounceTimer; if (elm.dataset.button) { smart_setting.addButton((button) => { button.setButtonText(elm.dataset.button); button.onClick(async () => this.handle_on_change(path, text.getValue(), elm, scope)); }); } else { text.onChange(async (value2) => { clearTimeout(debounceTimer); debounceTimer = setTimeout(() => this.handle_on_change(path, value2.trim(), elm, scope, settings_scope), 2e3); }); } }); return smart_setting; } render_password_component(elm, path, value, scope, settings_scope) { const smart_setting = new this.setting_class(elm); smart_setting.addText((text) => { text.inputEl.type = "password"; text.setPlaceholder(elm.dataset.placeholder || ""); if (value) text.setValue(value); let debounceTimer; text.onChange(async (value2) => { clearTimeout(debounceTimer); debounceTimer = setTimeout(() => this.handle_on_change(path, value2, elm, scope, settings_scope), 2e3); }); }); return smart_setting; } render_number_component(elm, path, value, scope, settings_scope) { const smart_setting = new this.setting_class(elm); smart_setting.addText((number) => { number.inputEl.type = "number"; number.setPlaceholder(elm.dataset.placeholder || ""); if (typeof value !== "undefined") number.inputEl.value = parseInt(value); number.inputEl.min = elm.dataset.min || 0; if (elm.dataset.max) number.inputEl.max = elm.dataset.max; let debounceTimer; number.onChange(async (value2) => { clearTimeout(debounceTimer); debounceTimer = setTimeout(() => this.handle_on_change(path, parseInt(value2), elm, scope, settings_scope), 2e3); }); }); return smart_setting; } render_toggle_component(elm, path, value, scope, settings_scope) { const smart_setting = new this.setting_class(elm); smart_setting.addToggle((toggle) => { let checkbox_val = value ?? false; if (typeof checkbox_val === "string") { checkbox_val = checkbox_val.toLowerCase() === "true"; } toggle.setValue(checkbox_val); toggle.onChange(async (value2) => this.handle_on_change(path, value2, elm, scope, settings_scope)); }); return smart_setting; } render_textarea_component(elm, path, value, scope, settings_scope) { const smart_setting = new this.setting_class(elm); smart_setting.addTextArea((textarea) => { textarea.setPlaceholder(elm.dataset.placeholder || ""); textarea.setValue(value || ""); let debounceTimer; textarea.onChange(async (value2) => { clearTimeout(debounceTimer); debounceTimer = setTimeout(() => this.handle_on_change(path, value2, elm, scope, settings_scope), 2e3); }); }); return smart_setting; } render_textarea_array_component(elm, path, value, scope, settings_scope) { const smart_setting = new this.setting_class(elm); smart_setting.addTextArea((textarea) => { textarea.setPlaceholder(elm.dataset.placeholder || ""); textarea.setValue(Array.isArray(value) ? value.join("\n") : value || ""); let debounceTimer; textarea.onChange(async (value2) => { value2 = value2.split("\n").map((v) => v.trim()).filter((v) => v); clearTimeout(debounceTimer); debounceTimer = setTimeout(() => this.handle_on_change(path, value2, elm, scope, settings_scope), 2e3); }); }); return smart_setting; } render_button_component(elm, path, value, scope, settings_scope) { const smart_setting = new this.setting_class(elm); smart_setting.addButton((button) => { button.setButtonText(elm.dataset.btnText || elm.dataset.name); button.onClick(async () => { if (elm.dataset.confirm && !confirm(elm.dataset.confirm)) return; if (elm.dataset.href) this.open_url(elm.dataset.href); if (elm.dataset.callback) { const callback = this.main.get_by_path(scope, elm.dataset.callback); if (callback) callback(path, value, elm, scope, settings_scope); } }); }); return smart_setting; } render_remove_component(elm, path, value, scope, settings_scope) { const smart_setting = new this.setting_class(elm); smart_setting.addButton((button) => { button.setButtonText(elm.dataset.btnText || elm.dataset.name || "Remove"); button.onClick(async () => { this.main.delete_by_path(scope.settings, path, settings_scope); if (elm.dataset.callback) { const callback = this.main.get_by_path(scope, elm.dataset.callback); if (callback) callback(path, value, elm, scope, settings_scope); } }); }); return smart_setting; } render_folder_select_component(elm, path, value, scope, settings_scope) { const smart_setting = new this.setting_class(elm); smart_setting.addFolderSelect((folder_select) => { folder_select.setPlaceholder(elm.dataset.placeholder || ""); if (value) folder_select.setValue(value); folder_select.inputEl.closest("div").addEventListener("click", () => { this.handle_folder_select(path, value, elm, scope); }); folder_select.inputEl.querySelector("input").addEventListener("change", (e) => { const folder = e.target.value; this.handle_on_change(path, folder, elm, scope, settings_scope); }); }); return smart_setting; } render_file_select_component(elm, path, value, scope, settings_scope) { const smart_setting = new this.setting_class(elm); smart_setting.addFileSelect((file_select) => { file_select.setPlaceholder(elm.dataset.placeholder || ""); if (value) file_select.setValue(value); file_select.inputEl.closest("div").addEventListener("click", () => { this.handle_file_select(path, value, elm, scope, settings_scope); }); }); return smart_setting; } render_slider_component(elm, path, value, scope, settings_scope) { const smart_setting = new this.setting_class(elm); smart_setting.addSlider((slider) => { const min = parseFloat(elm.dataset.min) || 0; const max = parseFloat(elm.dataset.max) || 100; const step = parseFloat(elm.dataset.step) || 1; const currentValue = typeof value !== "undefined" ? parseFloat(value) : min; slider.setLimits(min, max, step); slider.setValue(currentValue); slider.onChange((newVal) => { const numericVal = parseFloat(newVal); this.handle_on_change(path, numericVal, elm, scope, settings_scope); }); }); return smart_setting; } render_html_component(elm, path, value, scope) { this.safe_inner_html(elm, value); return elm; } /** * Renders an array setting component for managing a list of strings. * @param {HTMLElement} elm - Container element for the setting. * @param {string} path - Dot-notation path to store the array. * @param {Array} value - Initial array value. * @param {object} scope - Scope containing settings and actions. * @param {object|null} settings_scope - Optional nested settings scope. * @returns {object} smart_setting instance. */ render_array_component(elm, path, value, scope, settings_scope) { const smart_setting = new this.setting_class(elm); let arr = Array.isArray(value) ? [...value] : []; const items_container = document.createElement("div"); items_container.className = "array-items-container"; const render_items = () => { items_container.innerHTML = ""; arr.forEach((val, idx) => { const row = document.createElement("div"); row.className = "array-item-row"; const input = document.createElement("input"); input.type = "text"; input.value = val; input.placeholder = "Value"; const remove_btn = document.createElement("button"); remove_btn.textContent = "\u2715"; remove_btn.title = "Remove"; input.addEventListener("change", () => { arr[idx] = input.value; trigger_change(); }); remove_btn.addEventListener("click", () => { arr.splice(idx, 1); render_items(); trigger_change(); }); row.appendChild(input); row.appendChild(remove_btn); items_container.appendChild(row); }); }; const add_row = document.createElement("div"); add_row.className = "array-add-row"; const new_input = document.createElement("input"); new_input.type = "text"; new_input.placeholder = "Value"; const add_btn = document.createElement("button"); add_btn.textContent = "+"; add_btn.title = "Add value"; add_btn.addEventListener("click", () => { const v = new_input.value.trim(); if (!v) return; arr.push(v); new_input.value = ""; render_items(); trigger_change(); }); add_row.appendChild(new_input); add_row.appendChild(add_btn); smart_setting.controlEl.appendChild(items_container); smart_setting.controlEl.appendChild(add_row); const trigger_change = () => { this.handle_on_change(path, [...arr], elm, scope, settings_scope); }; render_items(); elm.appendChild(smart_setting.settingEl); return smart_setting; } render_json_component(elm, path, value, scope, settings_scope) { try { const smart_setting = new this.setting_class(elm); let obj = typeof value === "object" && value !== null ? { ...value } : {}; const pairs_container = document.createElement("div"); pairs_container.className = "json-pairs-container"; const renderPairs = () => { pairs_container.innerHTML = ""; Object.entries(obj).forEach(([key, val], idx) => { const pair_div = document.createElement("div"); pair_div.className = "json-pair-row"; const key_i = document.createElement("input"); key_i.type = "text"; key_i.value = key; key_i.placeholder = "Property"; const value_i = document.createElement("input"); value_i.type = "text"; value_i.value = val; value_i.placeholder = "Value"; const remove_btn = document.createElement("button"); remove_btn.textContent = "\u2715"; remove_btn.title = "Remove"; key_i.addEventListener("change", () => { const newKey = key_i.value.trim(); if (!newKey) return; if (newKey !== key) { obj[newKey] = obj[key]; delete obj[key]; renderPairs(); triggerChange(); } }); value_i.addEventListener("change", () => { obj[key_i.value] = value_i.value; triggerChange(); }); remove_btn.addEventListener("click", () => { delete obj[key_i.value]; renderPairs(); triggerChange(); }); pair_div.appendChild(key_i); pair_div.appendChild(value_i); pair_div.appendChild(remove_btn); pairs_container.appendChild(pair_div); }); }; const add_div = document.createElement("div"); add_div.className = "json-add-row"; const new_key_i = document.createElement("input"); new_key_i.type = "text"; new_key_i.placeholder = "Property"; const new_val_i = document.createElement("input"); new_val_i.type = "text"; new_val_i.placeholder = "Value"; const add_btn = document.createElement("button"); add_btn.textContent = "+"; add_btn.title = "Add property"; add_btn.addEventListener("click", () => { const k = new_key_i.value.trim(); if (!k || k in obj) return; obj[k] = new_val_i.value; new_key_i.value = ""; new_val_i.value = ""; renderPairs(); triggerChange(); }); add_div.appendChild(new_key_i); add_div.appendChild(new_val_i); add_div.appendChild(add_btn); smart_setting.controlEl.appendChild(pairs_container); smart_setting.controlEl.appendChild(add_div); const triggerChange = () => { this.handle_on_change(path, { ...obj }, elm, scope, settings_scope); }; renderPairs(); elm.appendChild(smart_setting.settingEl); return smart_setting; } catch (e) { console.error(e); } } add_button_if_needed(smart_setting, elm, path, scope) { if (elm.dataset.btn) { smart_setting.addButton((button) => { button.setButtonText(elm.dataset.btn); if (elm.dataset.btnCallback || elm.dataset.btnHref || elm.dataset.callback || elm.dataset.href) { button.inputEl.addEventListener("click", (e) => { if (elm.dataset.btnCallback && typeof scope[elm.dataset.btnCallback] === "function") { if (elm.dataset.btnCallbackArg) scope[elm.dataset.btnCallback](elm.dataset.btnCallbackArg); else scope[elm.dataset.btnCallback](path, null, smart_setting, scope); } else if (elm.dataset.btnHref) { this.open_url(elm.dataset.btnHref); } else if (elm.dataset.callback && typeof this.main.get_by_path(scope, elm.dataset.callback) === "function") { this.main.get_by_path(scope, elm.dataset.callback)(path, null, smart_setting, scope); } else if (elm.dataset.href) { this.open_url(elm.dataset.href); } else { console.error("No callback or href found for button."); } }); } if (elm.dataset.btnDisabled || elm.dataset.disabled && elm.dataset.btnDisabled !== "false") { button.inputEl.disabled = true; } }); } } handle_disabled_and_hidden(elm) { if (elm.dataset.disabled && elm.dataset.disabled !== "false") { elm.classList.add("disabled"); elm.querySelector("input, select, textarea, button").disabled = true; } if (elm.dataset.hidden && elm.dataset.hidden !== "false") { elm.style.display = "none"; } } get_dropdown_options(elm) { return Object.entries(elm.dataset).reduce((acc, [k, v]) => { if (!k.startsWith("option")) return acc; const [value, name] = v.split("|"); acc.push({ value, name: name || value }); return acc; }, []); } handle_on_change(path, value, elm, scope, settings_scope) { this.pre_change(path, value, elm, scope); if (elm.dataset.validate) { const valid = this[elm.dataset.validate](path, value, elm, scope); if (!valid) { elm.querySelector(".setting-item").style.border = "2px solid red"; this.revert_setting(path, elm, scope); return; } } this.main.set_by_path(scope.settings, path, value, settings_scope); if (elm.dataset.callback) { const callback = this.main.get_by_path(scope, elm.dataset.callback); if (callback) callback(path, value, elm, scope); } this.post_change(path, value, elm, scope); } render_button_with_confirm_component(elm, path, value, scope) { const smart_setting = new this.setting_class(elm); smart_setting.addButton((button) => { button.setButtonText(elm.dataset.btnText || elm.dataset.name); elm.appendChild(this.main.create_doc_fragment(`
${elm.dataset.confirm || "Are you sure?"}
`)); const confirm_row = elm.querySelector(".sc-inline-confirm-row"); const confirm_yes = confirm_row.querySelector(".sc-inline-confirm-yes"); const confirm_cancel = confirm_row.querySelector(".sc-inline-confirm-cancel"); button.onClick(async () => { confirm_row.style.display = "block"; elm.querySelector(".setting-item").style.display = "none"; }); confirm_yes.addEventListener("click", async () => { if (elm.dataset.href) this.open_url(elm.dataset.href); if (elm.dataset.callback) { const callback = this.main.get_by_path(scope, elm.dataset.callback); if (callback) callback(path, value, elm, scope); } elm.querySelector(".setting-item").style.display = "block"; confirm_row.style.display = "none"; }); confirm_cancel.addEventListener("click", () => { confirm_row.style.display = "none"; elm.querySelector(".setting-item").style.display = "block"; }); }); return smart_setting; } empty(elm) { empty(elm); } safe_inner_html(elm, html) { safe_inner_html(elm, html); } }; // node_modules/obsidian-smart-env/node_modules/smart-view/adapters/obsidian.js var import_obsidian = require("obsidian"); var SmartViewObsidianAdapter = class extends SmartViewAdapter { get setting_class() { return import_obsidian.Setting; } open_url(url) { window.open(url); } async render_file_select_component(elm, path, value) { return super.render_text_component(elm, path, value); } async render_markdown(markdown, scope) { const component = scope.env.smart_connections_plugin?.connections_view || new import_obsidian.Component(); if (!scope) return console.warn("Scope required for rendering markdown in Obsidian adapter"); const frag = this.main.create_doc_fragment("
"); const container = frag.querySelector(".inner"); try { await import_obsidian.MarkdownRenderer.render( scope.env.plugin.app, markdown, container, scope?.file_path || "", component ); } catch (e) { console.warn("Error rendering markdown in Obsidian adapter", e); } return frag; } get_icon_html(name) { return (0, import_obsidian.getIcon)(name).outerHTML; } // Obsidian Specific is_mod_event(event) { return import_obsidian.Keymap.isModEvent(event); } render_folder_select_component(elm, path, value, scope, settings_scope) { const smart_setting = new this.setting_class(elm); const folders = scope.env.plugin.app.vault.getAllFolders().sort((a, b) => a.path.localeCompare(b.path)); smart_setting.addDropdown((dropdown) => { if (elm.dataset.required) dropdown.inputEl.setAttribute("required", true); dropdown.addOption("", "No folder selected"); folders.forEach((folder) => { dropdown.addOption(folder.path, folder.path); }); dropdown.onChange((value2) => { this.handle_on_change(path, value2, elm, scope, settings_scope); }); dropdown.setValue(value); }); return smart_setting; } }; // node_modules/obsidian-smart-env/node_modules/smart-collections/utils/collection_instance_name_from.js function collection_instance_name_from(class_name) { if (class_name.endsWith("Item")) { return class_name.replace(/Item$/, "").replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase(); } return class_name.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase().replace(/y$/, "ie") + "s"; } // node_modules/obsidian-smart-env/node_modules/smart-collections/utils/helpers.js function create_uid(data) { const str = JSON.stringify(data); let hash = 0; if (str.length === 0) return hash; for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i); hash = (hash << 5) - hash + char; hash = hash & hash; if (hash < 0) hash = hash * -1; } return hash.toString() + str.length; } // node_modules/obsidian-smart-env/node_modules/smart-collections/utils/deep_equal.js function deep_equal(obj1, obj2, visited = /* @__PURE__ */ new WeakMap()) { if (obj1 === obj2) return true; if (obj1 === null || obj2 === null || obj1 === void 0 || obj2 === void 0) return false; if (typeof obj1 !== typeof obj2 || Array.isArray(obj1) !== Array.isArray(obj2)) return false; if (Array.isArray(obj1)) { if (obj1.length !== obj2.length) return false; return obj1.every((item, index) => deep_equal(item, obj2[index], visited)); } if (typeof obj1 === "object") { if (visited.has(obj1)) return visited.get(obj1) === obj2; visited.set(obj1, obj2); const keys1 = Object.keys(obj1); const keys2 = Object.keys(obj2); if (keys1.length !== keys2.length) return false; return keys1.every((key) => deep_equal(obj1[key], obj2[key], visited)); } return obj1 === obj2; } // node_modules/obsidian-smart-env/node_modules/smart-collections/utils/get_item_display_name.js function get_item_display_name(key, show_full_path) { if (show_full_path) { return key.split("/").join(" > ").replace(".md", ""); } return key.split("/").pop().replace(".md", ""); } // node_modules/obsidian-smart-env/node_modules/smart-collections/utils/create_actions_proxy.js function create_actions_proxy(ctx, actions_source) { const input = actions_source || {}; const is_plain_object6 = (val) => typeof val === "object" && val !== null && !Array.isArray(val); const is_function = (val) => typeof val === "function"; const is_class_export = (val) => is_function(val) && /^class\s/.test(Function.prototype.toString.call(val)); const is_action_object = (val) => is_plain_object6(val) && is_function(val.action); const is_action_candidate = (val) => is_function(val) || is_action_object(val) || is_class_export(val); const ignored_meta_keys = /* @__PURE__ */ new Set(["length", "name", "prototype"]); const clone_with_descriptors = (obj) => { if (!is_plain_object6(obj)) return obj; const out = Object.create(Object.getPrototypeOf(obj) || null); for (const key of Reflect.ownKeys(obj)) { const descriptor = Object.getOwnPropertyDescriptor(obj, key); if (!descriptor) continue; const next = { ...descriptor }; if ("value" in next && is_plain_object6(next.value)) { next.value = clone_with_descriptors(next.value); } try { Object.defineProperty(out, key, next); } catch { out[key] = next.value; } } return out; }; const should_bucket_actions = (val) => { if (!is_plain_object6(val)) return false; if (is_action_object(val)) return false; const keys = Reflect.ownKeys(val); if (keys.length === 0) return false; let found_candidate = false; for (const key of keys) { const descriptor = Object.getOwnPropertyDescriptor(val, key); if (!descriptor) continue; if ("value" in descriptor) { const entry = descriptor.value; if (is_action_candidate(entry)) { found_candidate = true; continue; } if (is_plain_object6(entry)) { if (should_bucket_actions(entry)) { found_candidate = true; continue; } return false; } if (typeof entry === "undefined") continue; return false; } return false; } return found_candidate; }; const clone_descriptor = (descriptor) => { if (!descriptor) return descriptor; if (!("value" in descriptor)) return { ...descriptor }; const cloned = is_plain_object6(descriptor.value) ? clone_with_descriptors(descriptor.value) : descriptor.value; return { ...descriptor, value: cloned }; }; const build_sources = (src) => { const global_source2 = /* @__PURE__ */ Object.create(null); const scoped_sources2 = /* @__PURE__ */ new Map(); for (const key of Reflect.ownKeys(src)) { const descriptor = Object.getOwnPropertyDescriptor(src, key); if (!descriptor) continue; if ("value" in descriptor && should_bucket_actions(descriptor.value)) { scoped_sources2.set(key, clone_with_descriptors(descriptor.value)); continue; } try { Object.defineProperty(global_source2, key, clone_descriptor(descriptor)); } catch { global_source2[key] = descriptor.value; } } return { global_source: global_source2, scoped_sources: scoped_sources2 }; }; const { global_source, scoped_sources } = build_sources(input); const has_own = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop); const cache = /* @__PURE__ */ Object.create(null); const copy_metadata = (source, target, omit = []) => { if (!source || !target) return; const skips = /* @__PURE__ */ new Set([...ignored_meta_keys, ...omit]); for (const key of Reflect.ownKeys(source)) { if (skips.has(key)) continue; const descriptor = Object.getOwnPropertyDescriptor(source, key); if (!descriptor) continue; try { Object.defineProperty(target, key, descriptor); } catch { target[key] = descriptor.value; } } }; const instantiate_class = (Ctor) => { const instance = new Ctor(ctx); const candidate = instance.action || instance.run || instance.execute || instance.call; if (is_function(candidate)) { const bound = candidate.bind(instance); copy_metadata(Ctor, bound); copy_metadata(instance, bound); bound.instance = instance; return bound; } copy_metadata(Ctor, instance); return instance; }; const bind_or_clone = (val) => { if (is_class_export(val)) { return instantiate_class(val); } if (is_action_object(val)) { const bound = val.action.bind(ctx); copy_metadata(val, bound, ["action"]); return bound; } if (is_function(val)) { const bound = val.bind(ctx); copy_metadata(val, bound); return bound; } if (is_plain_object6(val)) { return clone_with_descriptors(val); } return val; }; const scope_actions_for = () => { const scope_key = ctx?.constructor?.key; if (typeof scope_key === "undefined" || scope_key === null) return null; const bucket = scoped_sources.get(scope_key); return bucket && is_plain_object6(bucket) ? bucket : null; }; const cache_result = (target, prop, value) => { target[prop] = value; return value; }; const compute_and_cache = (target, prop) => { const scoped = scope_actions_for(); if (scoped && has_own(scoped, prop)) { return cache_result(target, prop, bind_or_clone(scoped[prop])); } if (has_own(global_source, prop)) { return cache_result(target, prop, bind_or_clone(global_source[prop])); } return cache_result(target, prop, void 0); }; const union_keys = () => { const scoped = scope_actions_for(); const keys = new Set(Reflect.ownKeys(cache)); for (const key of Reflect.ownKeys(global_source)) { keys.add(key); } if (scoped) { for (const key of Reflect.ownKeys(scoped)) { keys.add(key); } } return Array.from(keys); }; const descriptor_for = (target, prop) => ({ configurable: true, enumerable: true, value: target[prop] }); return new Proxy(cache, { get: (target, prop) => { if (prop === Symbol.toStringTag) return "ActionsProxy"; if (prop in target) return target[prop]; return compute_and_cache(target, prop); }, has: (target, prop) => { if (prop in target) return true; const scoped = scope_actions_for(); if (scoped && has_own(scoped, prop)) return true; return has_own(global_source, prop); }, ownKeys: () => union_keys(), getOwnPropertyDescriptor: (target, prop) => { if (has_own(target, prop)) { return descriptor_for(target, prop); } const scoped = scope_actions_for(); if (scoped && has_own(scoped, prop)) { if (!has_own(target, prop)) { compute_and_cache(target, prop); } return descriptor_for(target, prop); } if (has_own(global_source, prop)) { if (!has_own(target, prop)) { compute_and_cache(target, prop); } return descriptor_for(target, prop); } return void 0; }, defineProperty: (target, prop, descriptor) => { if ("value" in descriptor) { target[prop] = descriptor.value; return true; } return false; }, set: (target, prop, value) => { target[prop] = value; return true; }, deleteProperty: (target, prop) => { if (has_own(target, prop)) { delete target[prop]; } return true; } }); } // node_modules/obsidian-smart-env/node_modules/smart-collections/item.js var CollectionItem = class _CollectionItem { static version = 2e-3; /** * Default properties for an instance of CollectionItem. * Override in subclasses to define different defaults. * @returns {Object} */ static get defaults() { return { data: {} }; } /** * @param {Object} env - The environment/context. * @param {Object|null} [data=null] - Initial data for the item. */ constructor(env, data = null) { env.create_env_getter(this); this.config = this.env?.config; this.merge_defaults(); if (data) deep_merge(this.data, data); if (!this.data.class_name) this.data.class_name = this.collection.item_class_name; } /** * Loads an item from data and initializes it. * @param {Object} env * @param {Object} data * @returns {CollectionItem} */ static load(env, data) { const item = new this(env, data); item.init(); return item; } /** * Merge default properties from the entire inheritance chain. * @private */ merge_defaults() { let current_class = this.constructor; while (current_class) { for (let key in current_class.defaults) { const default_val = current_class.defaults[key]; if (typeof default_val === "object") { this[key] = { ...default_val, ...this[key] }; } else { this[key] = this[key] === void 0 ? default_val : this[key]; } } current_class = Object.getPrototypeOf(current_class); } } /** * Generates or retrieves a unique key for the item. * Key syntax supports: * - `[i]` for sequences * - `/` for super-sources (groups, directories, clusters) * - `#` for sub-sources (blocks) * @returns {string} The unique key */ get_key() { return create_uid(this.data); } /** * Updates the item data and returns true if changed. * @param {Object} data * @returns {boolean} True if data changed. */ update_data(data) { const sanitized_data = this.sanitize_data(data); const current_data = { ...this.data }; deep_merge(current_data, sanitized_data); const changed = !deep_equal(this.data, current_data); if (!changed) return false; this.data = current_data; return true; } /** * Sanitizes data for saving. Ensures no circular references. * @param {*} data * @returns {*} Sanitized data. */ sanitize_data(data) { if (data instanceof _CollectionItem) return data.ref; if (Array.isArray(data)) return data.map((val) => this.sanitize_data(val)); if (typeof data === "object" && data !== null) { return Object.keys(data).reduce((acc, key) => { acc[key] = this.sanitize_data(data[key]); return acc; }, {}); } return data; } /** * Initializes the item. Override as needed. * @param {Object} [input_data] - Additional data that might be provided on creation. */ init(input_data) { } /** * Queues this item for saving. */ queue_save() { this._queue_save = true; } /** * Saves this item using its data adapter. * @returns {Promise} */ async save() { try { await this.data_adapter.save_item(this); this.init(); } catch (err) { this._queue_save = true; console.error(err, err.stack); } } /** * Queues this item for loading. */ queue_load() { this._queue_load = true; } /** * Loads this item using its data adapter. * @returns {Promise} */ async load() { try { await this.data_adapter.load_item(this); this.init(); } catch (err) { this._load_error = err; this.on_load_error(err); } } /** * Handles load errors by re-queuing for load. * Override if needed. * @param {Error} err */ on_load_error(err) { this.queue_load(); } /** * Validates the item before saving. Checks for presence and validity of key. * @deprecated should be better handled 2025-12-17 (wrong scope?) * @returns {boolean} */ validate_save() { if (!this.key) return false; if (this.key.trim() === "") return false; if (this.key === "undefined") return false; return true; } /** * Marks this item as deleted. This does not immediately remove it from memory, * but queues a save that will result in the item being removed from persistent storage. */ delete() { this.deleted = true; this.queue_save(); } /** * Filters items in the collection based on provided options. * functional filter (returns true or false) for filtering items in collection; called by collection class * @param {Object} filter_opts - Filtering options. * @param {string} [filter_opts.exclude_key] - A single key to exclude. * @param {string[]} [filter_opts.exclude_keys] - An array of keys to exclude. If exclude_key is provided, it's added to this array. * @param {string} [filter_opts.exclude_key_starts_with] - Exclude keys starting with this string. * @param {string[]} [filter_opts.exclude_key_starts_with_any] - Exclude keys starting with any of these strings. * @param {string} [filter_opts.exclude_key_includes] - Exclude keys that include this string. * @param {string[]} [filter_opts.exclude_key_includes_any] - Exclude keys that include any of these strings. * @param {string} [filter_opts.exclude_key_ends_with] - Exclude keys ending with this string. * @param {string[]} [filter_opts.exclude_key_ends_with_any] - Exclude keys ending with any of these strings. * @param {string} [filter_opts.key_ends_with] - Include only keys ending with this string. * @param {string} [filter_opts.key_starts_with] - Include only keys starting with this string. * @param {string[]} [filter_opts.key_starts_with_any] - Include only keys starting with any of these strings. * @param {string} [filter_opts.key_includes] - Include only keys that include this string. * @returns {boolean} True if the item passes the filter, false otherwise. */ filter(filter_opts = {}) { const { exclude_key, exclude_keys = exclude_key ? [exclude_key] : [], exclude_key_starts_with, exclude_key_starts_with_any, exclude_key_includes, exclude_key_includes_any, exclude_key_ends_with, exclude_key_ends_with_any, key_ends_with, key_starts_with, key_starts_with_any, key_includes, key_includes_any } = filter_opts; if (exclude_keys?.includes(this.key)) return false; if (exclude_key_starts_with && this.key.startsWith(exclude_key_starts_with)) return false; if (exclude_key_starts_with_any && exclude_key_starts_with_any.some((prefix) => this.key.startsWith(prefix))) return false; if (exclude_key_includes && this.key.includes(exclude_key_includes)) return false; if (exclude_key_includes_any && exclude_key_includes_any.some((include) => this.key.includes(include))) return false; if (exclude_key_ends_with && this.key.endsWith(exclude_key_ends_with)) return false; if (exclude_key_ends_with_any && exclude_key_ends_with_any.some((suffix) => this.key.endsWith(suffix))) return false; if (key_ends_with && !this.key.endsWith(key_ends_with)) return false; if (key_starts_with && !this.key.startsWith(key_starts_with)) return false; if (key_starts_with_any && !key_starts_with_any.some((prefix) => this.key.startsWith(prefix))) return false; if (key_includes && !this.key.includes(key_includes)) return false; if (key_includes_any && !key_includes_any.some((include) => this.key.includes(include))) return false; return true; } filter_and_score(params = {}) { if (this.filter(params.filter) === false) return null; return this.score(params); } score(params = {}) { const score_action = this.actions[params.score_algo_key]; if (typeof score_action !== "function") throw new Error(`Missing score action: ${params.score_algo_key}`); return { ...score_action(params) || {}, item: this }; } /** * Parses item data for additional processing. Override as needed. * @deprecated is this used anywhere? */ parse() { } get actions() { if (!this._actions) { this._actions = create_actions_proxy(this, { ...this.env.config.actions || {}, // main actions scope for actions/ exports ...this.env.opts.items?.[this.item_type_key]?.actions || {} // DEPRECATED OR KEEP? }); } return this._actions; } /** * Derives the collection key from the class name. * @returns {string} */ static get collection_key() { let name = this.name; if (name.match(/\d$/)) name = name.slice(0, -1); return collection_instance_name_from(name); } /** * @returns {string} The collection key for this item. */ get collection_key() { let name = this.constructor.name; if (name.match(/\d$/)) name = name.slice(0, -1); return collection_instance_name_from(name); } /** * Retrieves the parent collection from the environment. * @returns {Collection} */ get collection() { return this.env[this.collection_key]; } /** * @returns {string} The item's key. */ get key() { return this.data?.key || this.get_key(); } get item_type_key() { let name = this.constructor.name; if (name.match(/\d$/)) name = name.slice(0, -1); return camel_case_to_snake_case(name); } /** * Emits an event with item metadata. * * @param {string} event_key * @param {Object} [payload={}] * @returns {void} */ emit_event(event_key, payload = {}) { this.env.events?.emit(event_key, { collection_key: this.collection_key, item_key: this.key, ...payload }); } on_event(event_key, callback) { return this.env.events?.on(event_key, (payload) => { if (payload?.item_key && payload.item_key !== this.key) return; callback(payload); }); } once_event(event_key, callback) { return this.env.events?.once(event_key, (payload) => { if (payload?.item_key && payload.item_key !== this.key) return; callback(payload); }); } /** * @returns {Object} The data adapter for this item's collection. */ get data_adapter() { return this.collection.data_adapter; } /** * @returns {Object} The filesystem adapter. */ get data_fs() { return this.collection.data_fs; } /** * Access to collection-level settings. * @returns {Object} */ get settings() { if (!this.env.settings[this.collection_key]) this.env.settings[this.collection_key] = {}; return this.env.settings[this.collection_key]; } set settings(settings) { this.env.settings[this.collection_key] = settings; this.env.smart_settings.save(); } /** * A simple reference object for this item. * @deprecated 2025-11-11 lacks adoption * @returns {{collection_key: string, key: string}} */ get ref() { return { collection_key: this.collection_key, key: this.key }; } /** * @deprecated use env.smart_components~~env.smart_view~~ instead */ get smart_view() { if (!this._smart_view) this._smart_view = this.env.init_module("smart_view"); return this._smart_view; } /** * Retrieves the display name of the collection item. * @readonly * @deprecated Use `get_item_display_name(key, show_full_path)` instead (keep UI logic out of collections). * @returns {string} The display name. */ get name() { return get_item_display_name( this.key, this.env.settings.smart_view_filter?.show_full_path ); } }; // node_modules/obsidian-smart-env/node_modules/smart-collections/collection.js var AsyncFunction = Object.getPrototypeOf(async function() { }).constructor; var Collection = class { static version = 1e-3; /** * Constructs a new Collection instance. * * @param {Object} env - The environment context containing configurations and adapters. * @param {Object} [opts={}] - Optional configuration. * @param {string} [opts.collection_key] - Custom key to override default collection name. * @param {string} [opts.data_dir] - Custom data directory path. */ constructor(env, opts = {}) { env.create_env_getter(this); this.opts = opts; if (opts.collection_key) this.collection_key = opts.collection_key; this.env[this.collection_key] = this; this.config = this.env.config; this.items = {}; this.loaded = null; this._loading = false; this.load_time_ms = null; this.settings_container = null; } /** * Initializes a new collection in the environment. Override in subclass if needed. * * @param {Object} env * @param {Object} [opts={}] * @returns {Promise} */ static async init(env, opts = {}) { env[this.collection_key] = new this(env, opts); await env[this.collection_key].init(); env.collections[this.collection_key] = "init"; } /** * The unique collection key derived from the class name. * @returns {string} */ static get collection_key() { let name = this.name; if (name.match(/\d$/)) name = name.slice(0, -1); return name.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase(); } /** * Instance-level init. Override in subclasses if necessary. * @returns {Promise} */ async init() { } /** * Creates or updates an item in the collection. * - If `data` includes a key that matches an existing item, that item is updated. * - Otherwise, a new item is created. * After updating or creating, the item is validated. If validation fails, the item is logged and returned without being saved. * If validation succeeds for a new item, it is added to the collection and marked for saving. * * If the item’s `init()` method is async, a promise is returned that resolves once init completes. * * NOTE: wrapping in try/catch seems to fail to catch errors thrown in async init functions when awaiting create_or_update * * @param {Object} [data={}] - Data for creating/updating an item. * @returns {Promise|Item} The created or updated item. May return a promise if `init()` is async. */ create_or_update(data = {}) { const existing_item = this.find_by(data); const item = existing_item ? existing_item : new this.item_type(this.env); item._queue_save = !existing_item; const data_changed = item.update_data(data); if (!existing_item && !item.validate_save()) { return item; } if (!existing_item) { this.set(item); } if (existing_item && !data_changed) return existing_item; if (item.init instanceof AsyncFunction) { return new Promise((resolve) => { item.init(data).then(() => resolve(item)); }); } item.init(data); return item; } /** * Finds an item by partial data match (first checks key). If `data.key` provided, * returns the item with that key; otherwise attempts a match by merging data. * * @param {Object} data - Data to match against. * @returns {Item|null} */ find_by(data) { if (data.key) return this.get(data.key); const temp = new this.item_type(this.env); const temp_data = JSON.parse(JSON.stringify(data, temp.sanitize_data(data))); deep_merge(temp.data, temp_data); return temp.key ? this.get(temp.key) : null; } /** * Filters items based on provided filter options or a custom function. * * @param {Object|Function} [filter_opts={}] - Filter options or a predicate function. * @returns {Item[]} Array of filtered items. */ filter(filter_opts = {}) { if (typeof filter_opts === "function") { return Object.values(this.items).filter(filter_opts); } const results = []; const { first_n } = filter_opts; for (const item of Object.values(this.items)) { if (first_n && results.length >= first_n) break; if (item.filter(filter_opts)) results.push(item); } return results; } /** * Alias for `filter()` * @param {Object|Function} filter_opts * @returns {Item[]} */ list(filter_opts) { return this.filter(filter_opts); } /** * Retrieves an item by key. * @param {string} key * @returns {Item|undefined} */ get(key) { return this.items[key]; } /** * Retrieves multiple items by an array of keys. * @param {string[]} keys * @returns {Item[]} */ get_many(keys = []) { if (!Array.isArray(keys)) { console.error("get_many called with non-array keys:", keys); return []; } return keys.map((key) => this.get(key)).filter(Boolean); } /** * Retrieves a random item from the collection, optionally filtered by options. * @param {Object} [opts] * @returns {Item|undefined} */ get_rand(opts = null) { if (opts) { const filtered = this.filter(opts); return filtered[Math.floor(Math.random() * filtered.length)]; } const keys = this.keys; return this.items[keys[Math.floor(Math.random() * keys.length)]]; } /** * Adds or updates an item in the collection. * @param {Item} item */ set(item) { if (!item.key) throw new Error("Item must have a key property"); this.items[item.key] = item; } /** * Updates multiple items by their keys. * @param {string[]} keys * @param {Object} data */ update_many(keys = [], data = {}) { this.get_many(keys).forEach((item) => item.update_data(data)); } /** * Clears all items from the collection. */ clear() { this.items = {}; } /** * @returns {string} The collection key, can be overridden by opts.collection_key */ get collection_key() { return this._collection_key ? this._collection_key : this.constructor.collection_key; } set collection_key(key) { this._collection_key = key; } /** * Lazily initializes and returns the data adapter instance for this collection. * @returns {Object} The data adapter instance. */ get data_adapter() { if (!this._data_adapter) { const AdapterClass = this.get_adapter_class("data"); this._data_adapter = new AdapterClass(this); } return this._data_adapter; } get_adapter_class(type) { const config = this.env.opts.collections?.[this.collection_key]; const adapter_key = type + "_adapter"; const adapter_module = config?.[adapter_key] ?? this.env.opts.collections?.smart_collections?.[adapter_key]; if (typeof adapter_module === "function") return adapter_module; if (typeof adapter_module?.collection === "function") return adapter_module.collection; throw new Error(`No '${type}' adapter class found for ${this.collection_key} or smart_collections`); } /** * Data directory strategy for this collection. Defaults to 'multi'. * @deprecated should be handled in adapters (2025-12-09) * @returns {string} */ get data_dir() { return this.collection_key; } /** * File system adapter from the environment. * @returns {Object} */ get data_fs() { return this.env.data_fs; } /** * Derives the corresponding item class name based on this collection's class name. * @returns {string} */ get item_class_name() { let name = this.constructor.name; if (name.match(/\d$/)) name = name.slice(0, -1); if (name.endsWith("ies")) return name.slice(0, -3) + "y"; else if (name.endsWith("s")) return name.slice(0, -1); return name + "Item"; } /** * Derives a readable item name from the item class name. * @returns {string} */ get item_name() { return this.item_class_name.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase(); } /** * Retrieves the item type (constructor) from the environment. * @deprecated replace with item_class with strict adherence to conventions (2025-10-28) * @returns {Function} Item constructor. */ get item_type() { if (!this._item_type) this._item_type = this.resolve_item_type(); return this._item_type; } // TEMP resolver (2025-11-03): until better handled on merging configs at obsidian-smart-env startup resolve_item_type() { const available = [ this.env.config?.items?.[this.item_name], this.opts.item_type, this.env.item_types?.[this.item_class_name] ].filter(Boolean).sort((a, b) => { const a_version = a?.class?.version || a.version || 0; const b_version = b?.class?.version || b.version || 0; return b_version - a_version; }); if (available.length === 0) { throw new Error(`No item_type found for collection '${this.collection_key}' with item_name '${this.item_name}' or class_name '${this.item_class_name}'`); } return available[0].class || available[0]; } /** * Returns an array of all keys in the collection. * @returns {string[]} */ get keys() { return Object.keys(this.items); } /** * @deprecated use data_adapter instead (2024-09-14) */ get adapter() { return this.data_adapter; } /** * @method process_save_queue * @description * Saves items flagged for saving (_queue_save) back to AJSON or SQLite. This ensures persistent storage * of any updates made since last load/import. This method also writes changes to disk (AJSON files or DB). */ async process_save_queue(opts = {}) { if (opts.force) { Object.values(this.items).forEach((item) => item._queue_save = true); } await this.data_adapter.process_save_queue(opts); } /** * @alias process_save_queue * @returns {Promise} */ async save(opts = {}) { await this.process_save_queue(opts); } /** * @method process_load_queue * @description * Loads items that have been flagged for loading (_queue_load). This may involve * reading from AJSON/SQLite or re-importing from markdown if needed. * Called once initial environment is ready and collections are known. */ async process_load_queue() { await this.data_adapter.process_load_queue(); } /** * Retrieves processed settings configuration. * @returns {Object} */ get settings_config() { return this.process_settings_config({}); } /** * Processes given settings config, adding prefixes and handling conditionals. * @deprecated removing settings_config from collections (2025-11-24) * * @private * @param {Object} _settings_config * @param {string} [prefix=''] * @returns {Object} */ process_settings_config(_settings_config, prefix = "") { const add_prefix = (key) => prefix && !key.includes(`${prefix}.`) ? `${prefix}.${key}` : key; return Object.entries(_settings_config).reduce((acc, [key, val]) => { let new_val = { ...val }; if (new_val.conditional) { if (!new_val.conditional(this)) return acc; delete new_val.conditional; } if (new_val.callback) new_val.callback = add_prefix(new_val.callback); if (new_val.btn_callback) new_val.btn_callback = add_prefix(new_val.btn_callback); if (new_val.options_callback) new_val.options_callback = add_prefix(new_val.options_callback); const new_key = add_prefix(this.process_setting_key(key)); acc[new_key] = new_val; return acc; }, {}); } /** * Processes an individual setting key. Override if needed. * @param {string} key * @returns {string} */ process_setting_key(key) { return key; } /** * Default settings for this collection. Override in subclasses as needed. * @returns {Object} */ get default_settings() { return {}; } /** * Current settings for the collection. * Initializes with default settings if none exist. * @returns {Object} */ get settings() { if (!this.env.settings[this.collection_key]) { this.env.settings[this.collection_key] = this.default_settings; } return this.env.settings[this.collection_key]; } /** * Unloads collection data from memory. */ unload() { this.clear(); this.unloaded = true; this.env.collections[this.collection_key] = null; } /** * Displays a process notice if the operation exceeds one second. * * @param {string} process - Identifier for the ongoing process. * @param {Object} [opts={}] - Additional options passed to the notice. */ show_process_notice(process, opts = {}) { if (!this.debounce_process_notice) this.debounce_process_notice = {}; this.debounce_process_notice[process] = setTimeout(() => { this.debounce_process_notice[process] = null; this.env.notices?.show(process, { collection_key: this.collection_key, ...opts }); }, 1e3); } /** * Clears any pending process notice timers and removes active notices. * * @param {string} process - Identifier for the process notice to clear. */ clear_process_notice(process) { if (this.debounce_process_notice?.[process]) { clearTimeout(this.debounce_process_notice[process]); this.debounce_process_notice[process] = null; } else { this.env.notices?.remove(process); } } /** * Emits an event with collection metadata. * * @param {string} event_key * @param {Object} [payload={}] * @returns {void} */ emit_event(event_key, payload = {}) { this.env.events?.emit(event_key, { collection_key: this.collection_key, ...payload }); } on_event(event_key, callback) { return this.env.events?.on(event_key, (payload) => { if (payload?.collection_key && payload.collection_key !== this.collection_key) return; callback(payload); }); } /** * Lazily binds action functions to the collection instance. * * @returns {Object} Bound action functions keyed by name. */ get actions() { if (!this.constructor.key) this.constructor.key = this.collection_key; if (!this._actions) { const actions_modules = { ...this.env?.config?.actions || {}, ...this.env?.config?.collections?.[this.collection_key]?.actions || {}, ...this.env?.opts?.collections?.[this.collection_key]?.actions || {}, ...this.opts?.actions || {} }; this._actions = create_actions_proxy(this, actions_modules); } return this._actions; } /** * Clears cached actions proxy and rebuilds on next access. * @returns {Object} Rebuilt proxy with latest source snapshot. */ refresh_actions() { this._actions = null; return this.actions; } // debounce running process save queue queue_save() { if (this._debounce_queue_save) clearTimeout(this._debounce_queue_save); this._debounce_queue_save = setTimeout(() => { this.process_save_queue(); }, 750); } // BEGIN DEPRECATED /** * @deprecated use env.smart_components~~env.smart_view~~ instead * @returns {Object} smart_view instance */ get smart_view() { if (!this._smart_view) this._smart_view = this.env.init_module("smart_view"); return this._smart_view; } /** * Renders the settings for the collection into a given container. * @deprecated use env.render_component('collection_settings', this) instead (2025-05-25: decouple UI from collections) * @param {HTMLElement} [container=this.settings_container] * @param {Object} opts * @returns {Promise} */ async render_settings(container = this.settings_container, opts = {}) { return await this.render_collection_settings(container, opts); } /** * Helper function to render collection settings. * @deprecated use env.render_component('collection_settings', this) instead (2025-05-25: decouple UI from collections) * @param {HTMLElement} [container=this.settings_container] * @param {Object} opts * @returns {Promise} */ async render_collection_settings(container = this.settings_container, opts = {}) { if (container && (!this.settings_container || this.settings_container !== container)) { this.settings_container = container; } else if (!container) { container = this.env.smart_view.create_doc_fragment("
"); } this.env.smart_view.safe_inner_html(container, `
Loading ${this.collection_key} settings...
`); const frag = await this.env.render_component("settings", this, opts); this.env.smart_view.empty(container); container.appendChild(frag); return container; } }; // node_modules/obsidian-smart-env/node_modules/smart-entities/adapters/_adapter.js var EntitiesVectorAdapter = class { /** * @constructor * @param {Object} collection - The collection (SmartEntities or derived class) instance. */ constructor(collection) { this.collection = collection; } /** * Find the nearest entities to the given vector. * @async * @param {number[]} vec - The reference vector. * @param {Object} [filter={}] - Optional filters (limit, exclude, etc.) * @returns {Promise>} Array of results sorted by score descending. * @abstract * @throws {Error} Not implemented by default. */ async nearest(vec, filter = {}) { throw new Error("EntitiesVectorAdapter.nearest() not implemented"); } /** * Find the furthest entities from the given vector. * @async * @param {number[]} vec - The reference vector. * @param {Object} [filter={}] - Optional filters (limit, exclude, etc.) * @returns {Promise>} Array of results sorted by score ascending (furthest). * @abstract * @throws {Error} Not implemented by default. */ async furthest(vec, filter = {}) { throw new Error("EntitiesVectorAdapter.furthest() not implemented"); } /** * Embed a batch of entities. * @async * @param {Object[]} entities - Array of entity instances to embed. * @returns {Promise} * @abstract * @throws {Error} Not implemented by default. */ async embed_batch(entities) { throw new Error("EntitiesVectorAdapter.embed_batch() not implemented"); } /** * Process a queue of entities waiting to be embedded. * Typically, this will call embed_batch in batches and update entities. * @async * @param {Object[]} embed_queue - Array of entities to embed. * @returns {Promise} * @abstract * @throws {Error} Not implemented by default. */ async process_embed_queue(embed_queue) { throw new Error("EntitiesVectorAdapter.process_embed_queue() not implemented"); } }; var EntityVectorAdapter = class { /** * @constructor * @param {Object} item - The SmartEntity instance that this adapter is associated with. */ constructor(item) { this.item = item; } /** * Retrieve the current vector embedding for this entity. * @async * @returns {Promise} The entity's vector or undefined if not set. * @abstract * @throws {Error} Not implemented by default. */ async get_vec() { throw new Error("EntityVectorAdapter.get_vec() not implemented"); } /** * Store/update the vector embedding for this entity. * @async * @param {number[]} vec - The vector to set. * @returns {Promise} * @abstract * @throws {Error} Not implemented by default. */ async set_vec(vec) { throw new Error("EntityVectorAdapter.set_vec() not implemented"); } /** * Delete/remove the vector embedding for this entity. * @async * @returns {Promise} * @abstract * @throws {Error} Not implemented by default. */ async delete_vec() { throw new Error("EntityVectorAdapter.delete_vec() not implemented"); } }; // node_modules/obsidian-smart-env/node_modules/smart-utils/results_acc.js function results_acc(_acc, result, ct = 10) { if (_acc.results.size < ct) { _acc.results.add(result); if (_acc.results.size === ct && _acc.min === Number.POSITIVE_INFINITY) { let { minScore, minObj } = find_min(_acc.results); _acc.min = minScore; _acc.minResult = minObj; } } else if (result.score > _acc.min) { _acc.results.add(result); _acc.results.delete(_acc.minResult); let { minScore, minObj } = find_min(_acc.results); _acc.min = minScore; _acc.minResult = minObj; } } function furthest_acc(_acc, result, ct = 10) { if (_acc.results.size < ct) { _acc.results.add(result); if (_acc.results.size === ct && _acc.max === Number.NEGATIVE_INFINITY) { let { maxScore, maxObj } = find_max(_acc.results); _acc.max = maxScore; _acc.maxResult = maxObj; } } else if (result.score < _acc.max) { _acc.results.add(result); _acc.results.delete(_acc.maxResult); let { maxScore, maxObj } = find_max(_acc.results); _acc.max = maxScore; _acc.maxResult = maxObj; } } function find_min(results) { let minScore = Number.POSITIVE_INFINITY; let minObj = null; for (const obj of results) { if (obj.score < minScore) { minScore = obj.score; minObj = obj; } } return { minScore, minObj }; } function find_max(results) { let maxScore = Number.NEGATIVE_INFINITY; let maxObj = null; for (const obj of results) { if (obj.score > maxScore) { maxScore = obj.score; maxObj = obj; } } return { maxScore, maxObj }; } // node_modules/obsidian-smart-env/node_modules/smart-utils/sort_by_score.js function sort_by_score(a, b) { const epsilon = 1e-9; const score_diff = a.score - b.score; if (Math.abs(score_diff) < epsilon) return 0; return score_diff > 0 ? -1 : 1; } function sort_by_score_descending(a, b) { return sort_by_score(a, b); } function sort_by_score_ascending(a, b) { return sort_by_score(a, b) * -1; } // node_modules/obsidian-smart-env/node_modules/smart-entities/adapters/default.js var DefaultEntitiesVectorAdapter = class extends EntitiesVectorAdapter { constructor(collection) { super(collection); this._is_processing_embed_queue = false; this._reset_embed_queue_stats(); } /** * Find the nearest entities to the given vector. * @async * @param {number[]} vec - The reference vector. * @param {Object} [filter={}] - Optional filters (limit, exclude, etc.) * @returns {Promise>} Array of results sorted by score descending. */ async nearest(vec, filter = {}) { if (!vec || !Array.isArray(vec)) { throw new Error("Invalid vector input to nearest()"); } const { limit = 50 // TODO: default configured in settings } = filter; const nearest = this.collection.filter(filter).reduce((acc, item) => { if (!item.vec) return acc; const result = { item, score: cos_sim(vec, item.vec) }; results_acc(acc, result, limit); return acc; }, { min: 0, results: /* @__PURE__ */ new Set() }); return Array.from(nearest.results).sort(sort_by_score_descending); } /** * Find the furthest entities from the given vector. * @async * @param {number[]} vec - The reference vector. * @param {Object} [filter={}] - Optional filters (limit, exclude, etc.) * @returns {Promise>} Array of results sorted by score ascending (furthest). */ async furthest(vec, filter = {}) { if (!vec || !Array.isArray(vec)) { throw new Error("Invalid vector input to furthest()"); } const { limit = 50 // TODO: default configured in settings } = filter; const furthest = this.collection.filter(filter).reduce((acc, item) => { if (!item.vec) return acc; const result = { item, score: cos_sim(vec, item.vec) }; furthest_acc(acc, result, limit); return acc; }, { max: 0, results: /* @__PURE__ */ new Set() }); return Array.from(furthest.results).sort(sort_by_score_ascending); } /** * Embed a batch of entities. * @async * @param {Object[]} entities - Array of entity instances to embed. * @returns {Promise} */ async embed_batch(entities) { if (!this.collection.embed_model) { throw new Error("No embed_model found in collection for embedding"); } await Promise.all(entities.map((e) => e.get_embed_input())); const embeddings = await this.collection.embed_model.embed_batch(entities); embeddings.forEach((emb, i) => { const entity = entities[i]; entity.vec = emb.vec; entity.data.last_embed = entity.data.last_read; if (emb.tokens !== void 0) entity.tokens = emb.tokens; entity.emit_event("item:embedded"); }); } /** * Process a queue of entities waiting to be embedded. * Prevents multiple concurrent runs by using `_is_processing_embed_queue`. * @async * @returns {Promise} */ async process_embed_queue() { if (this._is_processing_embed_queue) { console.log("process_embed_queue is already running, skipping concurrent call."); return; } this._is_processing_embed_queue = true; try { if (!this.collection.embed_model.is_loaded) { await this.collection.embed_model.load(); } } catch (e) { this.collection.emit_event("embed_model:load_failed"); this.notices?.show("Failed to load embed_model"); return; } try { const datetime_start = Date.now(); console.log(`Getting embed queue for ${this.collection.collection_key}...`); await new Promise((resolve) => setTimeout(resolve, 1)); const embed_queue = this.collection.embed_queue; this._reset_embed_queue_stats(); if (this.collection.embed_model_key === "None") { console.log(`Smart Connections: No active embedding model for ${this.collection.collection_key}, skipping embedding`); return; } if (!this.collection.embed_model) { console.log(`Smart Connections: No active embedding model for ${this.collection.collection_key}, skipping embedding`); return; } if (!embed_queue.length) { console.log(`Smart Connections: No items in ${this.collection.collection_key} embed queue`); return; } console.log(`Time spent getting embed queue: ${Date.now() - datetime_start}ms`); console.log(`Processing ${this.collection.collection_key} embed queue: ${embed_queue.length} items`); for (let i = 0; i < embed_queue.length; i += this.collection.embed_model.batch_size) { if (this.is_queue_halted) { this.is_queue_halted = false; break; } this._show_embed_progress_notice(embed_queue.length); const batch = embed_queue.slice(i, i + this.collection.embed_model.batch_size); await Promise.all(batch.map((item) => item.get_embed_input())); try { const start_time = Date.now(); await this.embed_batch(batch); this.total_time += Date.now() - start_time; } catch (e) { if (e && e.message && e.message.includes("API key not set")) { this.halt_embed_queue_processing(`API key not set for ${this.collection.embed_model_key} Please set the API key in the settings.`); } console.error(e); console.error(`Error processing ${this.collection.collection_key} embed queue: ` + JSON.stringify(e || {}, null, 2)); } batch.forEach((item) => { item.embed_hash = item.read_hash; item._queue_save = true; }); this.embedded_total += batch.length; this.total_tokens += batch.reduce((acc, item) => acc + (item.tokens || 0), 0); if (this.embedded_total - this.last_save_total > 1e3) { this.last_save_total = this.embedded_total; await this.collection.process_save_queue(); if (this.collection.block_collection) { console.log(`Saving ${this.collection.block_collection.collection_key} block collection`); await this.collection.block_collection.process_save_queue(); } } } this._show_embed_completion_notice(embed_queue.length); await this.collection.process_save_queue(); if (this.collection.block_collection) { await this.collection.block_collection.process_save_queue(); } } finally { this._is_processing_embed_queue = false; } } get should_show_embed_progress_notice() { if (Date.now() - (this.last_notice_time ?? 0) > 3e4) { return true; } return this.embedded_total - this.last_notice_embedded_total >= 100; } /** * Displays the embedding progress notice. * @private * @returns {void} */ _show_embed_progress_notice(embed_queue_length) { if (embed_queue_length < 100) return; if (!this.should_show_embed_progress_notice) return; this.last_notice_time = Date.now(); this.last_notice_embedded_total = this.embedded_total; this.collection.emit_event("embedding:progress_reported", { progress: this.embedded_total, total: embed_queue_length, tokens_per_second: this._calculate_embed_tokens_per_second(), model_name: this.collection.embed_model_key }); this.notices?.show("embedding_progress", { progress: this.embedded_total, total: embed_queue_length, tokens_per_second: this._calculate_embed_tokens_per_second(), model_name: this.collection.embed_model_key }); } /** * Displays the embedding completion notice. * @private * @returns {void} */ _show_embed_completion_notice() { this.notices?.remove("embedding_progress"); if (this.embedded_total > 100) { this.collection.emit_event("embedding:completed", { total_embeddings: this.embedded_total, tokens_per_second: this._calculate_embed_tokens_per_second(), model_name: this.collection.embed_model_key }); this.notices?.show("embedding_complete", { total_embeddings: this.embedded_total, tokens_per_second: this._calculate_embed_tokens_per_second(), model_name: this.collection.embed_model_key }); } } /** * Halts the embed queue processing. * @param {string|null} msg - Optional message. */ halt_embed_queue_processing(msg = null) { this.is_queue_halted = true; console.log("Embed queue processing halted"); this.notices?.remove("embedding_progress"); this.collection.emit_event("embedding:paused", { progress: this.embedded_total, total: this.collection._embed_queue.length, tokens_per_second: this._calculate_embed_tokens_per_second(), model_name: this.collection.embed_model_key }); this.notices?.show("embedding_paused", { progress: this.embedded_total, total: this.collection._embed_queue.length, tokens_per_second: this._calculate_embed_tokens_per_second(), model_name: this.collection.embed_model_key }); } /** * Resumes the embed queue processing after a delay. * @param {number} [delay=0] - The delay in milliseconds before resuming. * @returns {void} */ resume_embed_queue_processing(delay = 0) { console.log("resume_embed_queue_processing"); this.notices?.remove("embedding_paused"); setTimeout(() => { this.embedded_total = 0; this.process_embed_queue(); }, delay); } /** * Calculates the number of tokens processed per second. * @private * @returns {number} Tokens per second. */ _calculate_embed_tokens_per_second() { const elapsed_time = this.total_time / 1e3; return Math.round(this.total_tokens / (elapsed_time || 1)); } /** * Resets the statistics related to embed queue processing. * @private * @returns {void} */ _reset_embed_queue_stats() { this.collection._embed_queue = []; this.embedded_total = 0; this.is_queue_halted = false; this.last_save_total = 0; this.last_notice_embedded_total = 0; this.total_tokens = 0; this.total_time = 0; } get notices() { return this.collection.env.notices; } }; var DefaultEntityVectorAdapter = class extends EntityVectorAdapter { get data() { return this.item.data; } /** * Retrieve the current vector embedding for this entity. * @async * @returns {Promise} The entity's vector or undefined if not set. */ async get_vec() { return this.vec; } /** * Store/update the vector embedding for this entity. * @async * @param {number[]} vec - The vector to set. * @returns {Promise} */ async set_vec(vec) { this.vec = vec; } /** * Delete/remove the vector embedding for this entity. * @async * @returns {Promise} */ async delete_vec() { if (this.item.data?.embeddings?.[this.item.embed_model_key]) { delete this.item.data.embeddings[this.item.embed_model_key].vec; } } // adds synchronous get/set for vec get vec() { return this.item.data?.embeddings?.[this.item.embed_model_key]?.vec; } set vec(vec) { if (!this.item.data.embeddings) { this.item.data.embeddings = {}; } if (!this.item.data.embeddings[this.item.embed_model_key]) { this.item.data.embeddings[this.item.embed_model_key] = {}; } this.item.data.embeddings[this.item.embed_model_key].vec = vec; } }; // node_modules/obsidian-smart-env/node_modules/smart-entities/actions/find_connections.js var FRONTMATTER_SUFFIX = "---frontmatter---"; var to_array = (value) => { if (Array.isArray(value)) { return value.map((entry) => typeof entry === "string" ? entry.trim() : "").filter((entry) => entry.length > 0); } if (typeof value === "string") { const parts = value.includes(",") ? value.split(",") : [value]; return parts.map((part) => part.trim()).filter((part) => part.length > 0); } return []; }; var merge_settings_with_params = (entity, params = {}) => ({ ...entity.env.settings.smart_view_filter || {}, ...params, entity }); var remove_limit_fields = (filter_opts) => { const next = { ...filter_opts }; if (typeof next.limit !== "undefined") delete next.limit; if (next.filter) { next.filter = { ...next.filter }; if (typeof next.filter.limit !== "undefined") delete next.filter.limit; } return next; }; var apply_frontmatter_exclusion = (filter_opts) => { if (!filter_opts.exclude_frontmatter_blocks) return filter_opts; const next = { ...filter_opts }; const suffixes = Array.isArray(next.exclude_key_ends_with_any) ? [...next.exclude_key_ends_with_any] : []; suffixes.push(FRONTMATTER_SUFFIX); next.exclude_key_ends_with_any = suffixes; return next; }; var append_entity_filters = (filter_opts, entity) => { if (!entity) return filter_opts; const next = { ...filter_opts }; let exclude_starts = Array.isArray(next.exclude_key_starts_with_any) ? [...next.exclude_key_starts_with_any] : []; if (typeof next.exclude_key_starts_with === "string") { exclude_starts.push(next.exclude_key_starts_with); delete next.exclude_key_starts_with; } const entity_key = entity.source_key || entity.key; if (entity_key) exclude_starts.push(entity_key); if (next.exclude_inlinks && Array.isArray(entity.inlinks) && entity.inlinks.length) { exclude_starts = [...exclude_starts, ...entity.inlinks]; } if (next.exclude_outlinks && Array.isArray(entity.outlinks) && entity.outlinks.length) { exclude_starts = [...exclude_starts, ...entity.outlinks.map((o) => o.key)]; } if (exclude_starts.length) next.exclude_key_starts_with_any = exclude_starts; if (next.exclude_filter) { const exclude_values = to_array(next.exclude_filter); const current = Array.isArray(next.exclude_key_includes_any) ? [...next.exclude_key_includes_any] : []; next.exclude_key_includes_any = [...current, ...exclude_values]; } if (next.include_filter) { const include_values = to_array(next.include_filter); const current = Array.isArray(next.key_includes_any) ? [...next.key_includes_any] : []; next.key_includes_any = [...current, ...include_values]; } return next; }; var create_find_connections_filter_opts = (entity, params = {}) => { const merged = merge_settings_with_params(entity, params); const without_limits = remove_limit_fields(merged); const with_frontmatter = apply_frontmatter_exclusion(without_limits); return append_entity_filters(with_frontmatter, entity); }; async function find_connections(params = {}) { const limit = params.filter?.limit || params.limit || this.env.settings.smart_view_filter?.results_limit || 10; const filter_opts = create_find_connections_filter_opts(this, params); if (params.filter?.limit) delete params.filter.limit; if (params.limit) delete params.limit; const cache_key = this.key + murmur_hash_32_alphanumeric(JSON.stringify({ ...filter_opts, entity: null })); if (!this.env.connections_cache) this.env.connections_cache = {}; if (!this.env.connections_cache[cache_key]) { const connections = (await this.nearest(filter_opts)).sort(sort_by_score).slice(0, limit); this.connections_to_cache(cache_key, connections); } return this.connections_from_cache(cache_key); } find_connections.action_type = "connections"; // node_modules/obsidian-smart-env/node_modules/smart-entities/smart_entity.js var SmartEntity = class extends CollectionItem { /** * Creates an instance of SmartEntity. * @constructor * @param {Object} env - The environment instance. * @param {Object} [opts={}] - Configuration options. */ constructor(env, opts = {}) { super(env, opts); this.entity_adapter = new DefaultEntityVectorAdapter(this); } /** * Provides default values for a SmartEntity instance. * @static * @readonly * @returns {Object} The default values. */ static get defaults() { return { data: { path: null, last_embed: { hash: null }, embeddings: {} } }; } get vector_adapter() { if (!this._vector_adapter) { this._vector_adapter = new this.collection.opts.vector_adapter.item(this); } return this._vector_adapter; } /** * Initializes the SmartEntity instance. * Checks if the entity has a vector and if it matches the model dimensions. * If not, it queues an embed. * Removes embeddings for inactive models. * @returns {void} */ init() { super.init(); if (!this.vec || !this.vec.length) { this.vec = null; this.queue_embed(); } Object.entries(this.data.embeddings || {}).forEach(([model, embedding]) => { if (model !== this.embed_model_key) { this.data.embeddings[model] = null; delete this.data.embeddings[model]; } }); } /** * Queues the entity for embedding. * @returns {void} */ queue_embed() { this._queue_embed = true; } /** * Finds the nearest entities to this entity. * @param {Object} [filter={}] - Optional filters to apply. * @deprecated use actions (getter) instead * @returns {Array<{item:Object, score:number}>} An array of result objects with score and item. */ async nearest(filter = {}) { return await this.collection.nearest_to(this, filter); } /** * Prepares the input for embedding. * @async * @param {string} [content=null] - Optional content to use instead of calling subsequent read() * @returns {Promise} Should be overridden in child classes. */ async get_embed_input(content = null) { } // override in child class /** * Retrieves the embed input, either from cache or by generating it. * @readonly * @returns {string|Promise} The embed input string or a promise resolving to it. */ get embed_input() { return this._embed_input ? this._embed_input : this.get_embed_input(); } /** * Finds connections relevant to this entity based on provided parameters. * @async * @param {Object} [params={}] - Parameters for finding connections. * @deprecated should be in actions (getter) but also see ConnectionsLists (smart-lists) * @returns {Array<{item:Object, score:number}>} An array of result objects with score and item. */ async find_connections(params = {}) { return await this.actions.find_connections(params); } /** * Retrieves connections from the cache based on the cache key. * @param {string} cache_key - The cache key. * @deprecated migrating to ConnectionsLists (smart-lists) * @returns {Array<{item:Object, score:number}>} The cached connections. */ connections_from_cache(cache_key) { return this.env.connections_cache[cache_key]; } /** * Stores connections in the cache with the provided cache key. * @param {string} cache_key - The cache key. * @deprecated migrating to ConnectionsLists (smart-lists) * @param {Array<{item:Object, score:number}>} connections - The connections to cache. * @returns {void} */ connections_to_cache(cache_key, connections) { this.env.connections_cache[cache_key] = connections; } get read_hash() { return this.data.last_read?.hash; } set read_hash(hash) { if (!this.data.last_read) this.data.last_read = {}; this.data.last_read.hash = hash; } get embedding_data() { if (!this.data.embeddings[this.embed_model_key]) { this.data.embeddings[this.embed_model_key] = {}; } return this.data.embeddings[this.embed_model_key]; } get last_embed() { if (!this.embedding_data.last_embed) { this.embedding_data.last_embed = {}; if (this.data.last_embed) { this.embedding_data.last_embed = this.data.last_embed; delete this.data.last_embed; this.queue_save(); } } return this.embedding_data.last_embed; } get embed_hash() { return this.last_embed?.hash; } set embed_hash(hash) { if (!this.embedding_data.last_embed) this.embedding_data.last_embed = {}; this.embedding_data.last_embed.hash = hash; } /** * Gets the embed link for the entity. * @readonly * @returns {string} The embed link. */ get embed_link() { return `![[${this.path}]]`; } /** * Gets the key of the embedding model. * @readonly * @returns {string} The embedding model key. */ get embed_model_key() { return this.collection.embed_model_key; } /** * Gets the embedding model instance from the collection. * @readonly * @returns {Object} The embedding model instance. */ get embed_model() { return this.collection.embed_model; } /** * Determines if the entity should be embedded if unembedded. NOT the same as is_unembedded. * @readonly * @returns {boolean} True if no vector is set, false otherwise. */ get should_embed() { return this.size > (this.settings?.min_chars || 300); } /** * Sets the error for the embedding model. * @param {string} error - The error message. */ set error(error) { this.data.embeddings[this.embed_model_key].error = error; } /** * Gets the number of tokens associated with the entity's embedding. * @readonly * @returns {number|undefined} The number of tokens, or undefined if not set. */ get tokens() { return this.last_embed?.tokens; } /** * Sets the number of tokens for the embedding. * @param {number} tokens - The number of tokens. */ set tokens(tokens) { this.last_embed.tokens = tokens; } /** * Gets the vector representation from the entity adapter. * @readonly * @returns {Array|undefined} The vector or undefined if not set. */ get vec() { return this.entity_adapter.vec; } /** * Sets the vector representation in the entity adapter. * @param {Array} vec - The vector to set. */ set vec(vec) { this.entity_adapter.vec = vec; this._queue_embed = false; this._embed_input = null; this.queue_save(); } /** * Removes all embeddings from the entity. * @returns {void} */ remove_embeddings() { this.data.embeddings = null; this.queue_save(); } /** * Retrieves the key of the entity. * @returns {string} The entity key. */ get_key() { return this.data.key || this.data.path; } /** * Retrieves the path of the entity. * @readonly * @returns {string|null} The entity path. */ get path() { return this.data.path; } get is_unembedded() { if (!this.vec) return true; if (!this.embed_hash || this.embed_hash !== this.read_hash) return true; return false; } }; // node_modules/obsidian-smart-env/node_modules/smart-entities/smart_entities.js var SmartEntities = class extends Collection { /** * Creates an instance of SmartEntities. * @constructor * @param {Object} env - The environment instance. * @param {Object} opts - Configuration options. */ constructor(env, opts) { super(env, opts); this.entities_vector_adapter = new DefaultEntitiesVectorAdapter(this); this.model_instance_id = null; this._embed_queue = []; } /** * Unloads the smart embedding model. * @async * @returns {Promise} */ async unload() { if (typeof this.embed_model?.unload === "function") { this.embed_model.unload(); } super.unload(); } /** * Gets the key of the embedding model. * @readonly * @returns {string} The embedding model key. */ get embed_model_key() { return this.embed_model?.model_key; } /** * Gets the embedding model instance. * @readonly * @returns {Object|null} The embedding model instance or null if none. */ get embed_model() { if (this.env.embedding_models.default) { return this.env.embedding_models.default.instance; } throw new Error("DEPRECATED SMART ENVIRONMENT LOADED: UPDATE SMART PLUGINS."); } set embed_model(embed_model) { this.env._embed_model = embed_model; } reload_embed_model() { console.log("reload_embed_model"); this.embed_model.unload(); this.env._embed_model = null; } /** * Finds the nearest entities to a given entity. * @async * @param {Object} entity - The reference entity. * @deprecated moved to action (type=score) and retrieve using filter_and_score()/get_results() patterns * @param {Object} [filter={}] - Optional filters to apply. * @returns {Promise>} An array of result objects with score and item. */ async nearest_to(entity, filter = {}) { return await this.nearest(entity.vec, filter); } /** * Finds the nearest entities to a vector using the default adapter. * @async * @deprecated moved to action (type=score) and retrieve using filter_and_score()/get_results() patterns * @param {Array} vec - The vector to compare against. * @param {Object} [filter={}] - Optional filters to apply. * @returns {Promise>} An array of result objects with score and item. */ async nearest(vec, filter = {}) { if (!vec) { console.warn("nearest: no vec"); return []; } return await this.entities_vector_adapter.nearest(vec, filter); } /** * Finds the furthest entities from a vector using the default adapter. * @async * @deprecated moved to action (type=score) and retrieve using filter_and_score()/get_results() patterns * @param {Array} vec - The vector to compare against. * @param {Object} [filter={}] - Optional filters to apply. * @returns {Promise>} An array of result objects with score and item. */ async furthest(vec, filter = {}) { if (!vec) return console.warn("furthest: no vec"); return await this.entities_vector_adapter.furthest(vec, filter); } /** * Gets the file name based on collection key and embedding model key. * @readonly * @deprecated likely unused (2025-09-29) * @returns {string} The constructed file name. */ get file_name() { return this.collection_key + "-" + this.embed_model_key.split("/").pop(); } /** * Looks up entities based on hypothetical content. * @deprecated moved to action (type=score) and retrieve using get_results() (pre-process generates hypothetical vecs) * @async * @param {Object} [params={}] - The parameters for the lookup. * @param {Array} [params.hypotheticals=[]] - The hypothetical content to lookup. * @param {Object} [params.filter] - The filter to use for the lookup. * @param {number} [params.k] - Deprecated: Use `filter.limit` instead. * @returns {Promise|Object>} The lookup results or an error object. */ async lookup(params = {}) { const { hypotheticals = [] } = params; if (!hypotheticals?.length) return { error: "hypotheticals is required" }; if (!this.embed_model) return { error: "Embedding search is not enabled." }; const hyp_vecs = await this.embed_model.embed_batch(hypotheticals.map((h) => ({ embed_input: h }))); const limit = params.filter?.limit || params.k || this.env.settings.lookup_k || 10; if (params.filter?.limit) delete params.filter.limit; const filter = { ...this.env.chats?.current?.scope || {}, // DEPRECATED: since Smart Chat v1 (remove after removing legacy Smart Chat v0 from obsidian-smart-connections) ...params.filter || {} }; const results = await hyp_vecs.reduce(async (acc_promise, embedding, i) => { const acc = await acc_promise; const results2 = await this.nearest(embedding.vec, filter); results2.forEach((result) => { if (!acc[result.item.path] || result.score > acc[result.item.path].score) { acc[result.item.path] = { key: result.item.key, score: result.score, item: result.item, hypothetical_i: i }; } else { result.score = acc[result.item.path].score; } }); return acc; }, Promise.resolve({})); const top_k = Object.values(results).sort(sort_by_score).slice(0, limit); console.log(`Found and returned ${top_k.length} ${this.collection_key}.`); return top_k; } /** * Gets the configuration for settings. * @readonly * @returns {Object} The settings configuration. */ get settings_config() { return settings_config; } /** * @deprecated use env.render_component('collection_settings', this) instead (2025-05-25: decouple UI from collections) */ async render_settings(container = this.settings_container, opts = {}) { container = await this.render_collection_settings(container, opts); const embed_model_settings_frag = await this.env.render_component("settings", this.embed_model, opts); container.appendChild(embed_model_settings_frag); return container; } /** * Gets the notices from the environment. * @readonly * @returns {Object} The notices object. */ get notices() { return this.env.smart_connections_plugin?.notices || this.env.main?.notices; } /** * Gets the embed queue containing items to be embedded. * @readonly * @returns {Array} The embed queue. */ get embed_queue() { if (!this._embed_queue?.length) { console.time(`Building embed queue`); this._embed_queue = Object.values(this.items).filter((item) => item._queue_embed || item.is_unembedded && item.should_embed); console.timeEnd(`Building embed queue`); } return this._embed_queue; } /** * Processes the embed queue by delegating to the default vector adapter. * @async * @returns {Promise} */ async process_embed_queue() { await this.entities_vector_adapter.process_embed_queue(); } /** * Handles changes to the embedding model by reinitializing and processing the load queue. * @async * @returns {Promise} */ async embed_model_changed() { await this.unload(); await this.init(); this.render_settings(); await this.process_load_queue(); } /** * @deprecated since v4 2025-11-28 */ get connections_filter_config() { return connections_filter_config; } }; var settings_config = { "min_chars": { name: "Minimum length", type: "number", description: "Minimum length of entity to embed (in characters).", placeholder: "Enter number ex. 300", default: 300 } }; var connections_filter_config = { "smart_view_filter.show_full_path": { "name": "Show full path", "type": "toggle", "description": "Turning on will include the folder path in the connections results." }, // "smart_view_filter.render_markdown": { // "name": "Render markdown", // "type": "toggle", // "description": "Turn off to prevent rendering markdown and display connection results as plain text.", // }, "smart_view_filter.results_limit": { "name": "Results limit", "type": "number", "description": "Adjust the number of connections displayed in the connections view (default 20).", "default": 20 }, "smart_view_filter.exclude_inlinks": { "name": "Exclude inlinks (backlinks)", "type": "toggle", "description": "Exclude notes that already link to the current note from the connections results." }, "smart_view_filter.exclude_outlinks": { "name": "Exclude outlinks", "type": "toggle", "description": "Exclude notes that are already linked from within the current note from appearing in the connections results." }, "smart_view_filter.include_filter": { "name": "Include filter", "type": "text", "description": "Notes must match this value in their file/folder path. Matching notes will be included in the connections results. Separate multiple values with commas." }, "smart_view_filter.exclude_filter": { "name": "Exclude filter", "type": "text", "description": "Notes must *not* match this value in their file/folder path. Matching notes will be *excluded* from the connections results. Separate multiple values with commas." }, // should be better scoped at source-level (leaving here for now since connections_filter_config needs larger refactor) "smart_view_filter.exclude_blocks_from_source_connections": { "name": "Hide blocks in results", "type": "toggle", "description": "Show only sources in the connections results (no blocks)." } // // hide frontmatter blocks from connections results // "smart_view_filter.exclude_frontmatter_blocks": { // "name": "Hide frontmatter blocks in results", // "type": "toggle", // "description": "Show only sources in the connections results (no frontmatter blocks).", // }, }; // node_modules/obsidian-smart-env/node_modules/smart-sources/actions/find_connections.js async function find_connections2(params = {}) { const filter_settings = this.env.settings.smart_view_filter; const exclude_blocks_from_source_connections = params.exclude_blocks_from_source_connections ?? filter_settings?.exclude_blocks_from_source_connections ?? false; const limit = params.filter?.limit || params.limit || this.env.settings.smart_view_filter?.results_limit || 20; let connections; if (this.block_collection.settings.embed_blocks && !exclude_blocks_from_source_connections) connections = []; else connections = await find_connections.call(this, params); const filter_opts = create_find_connections_filter_opts(this, params); if (params.filter?.limit) delete params.filter.limit; if (params.limit) delete params.limit; if (!exclude_blocks_from_source_connections) { const cache_key = this.key + murmur_hash_32_alphanumeric(JSON.stringify({ ...filter_opts, entity: null })) + "_blocks"; if (!this.env.connections_cache) this.env.connections_cache = {}; if (!this.env.connections_cache[cache_key]) { const nearest = (await this.env.smart_blocks.nearest(this.vec, filter_opts)).sort(sort_by_score).slice(0, limit); this.connections_to_cache(cache_key, nearest); } connections = [ ...connections, ...this.connections_from_cache(cache_key) ].sort(sort_by_score).slice(0, limit); } return connections; } find_connections2.action_type = "connections"; // node_modules/obsidian-smart-env/node_modules/smart-sources/smart_source.js var SmartSource = class extends SmartEntity { /** * Provides default values for a SmartSource instance. * @static * @readonly * @returns {Object} The default values. */ static get defaults() { return { data: { last_read: { hash: null, mtime: 0 }, embeddings: {} }, _embed_input: null, // Stored temporarily _queue_load: true }; } /** * Initializes the SmartSource instance by queuing an import if blocks are missing. * @returns {void} */ init() { super.init(); if (!this.data.blocks) this.queue_import(); } /** * Queues the SmartSource for import. * @returns {void} */ queue_import() { this._queue_import = true; } /** * Imports the SmartSource by checking for updates and parsing content. * @async * @returns {Promise} */ async import() { this._queue_import = false; try { await this.source_adapter?.import(); this.emit_event("sources:imported"); } catch (err) { if (err.code === "ENOENT") { console.log(`Smart Connections: Deleting ${this.path} data because it no longer exists on disk`); this.delete(); } else { console.warn("Smart Connections: Error during import: re-queueing import", err); this.queue_import(); } } } /** * @deprecated likely extraneous */ async parse_content(content = null) { const parse_fns = this.env?.opts?.collections?.smart_sources?.content_parsers || []; for (const fn of parse_fns) { await fn(this, content); } if (this.data.last_import?.hash === this.data.last_read?.hash) { if (this.data.blocks) return; } } /** * Finds connections relevant to this SmartSource based on provided parameters. * @async * @deprecated use ConnectionsLists * @param {Object} [params={}] - Parameters for finding connections. * @param {boolean} [params.exclude_blocks_from_source_connections=false] - Whether to exclude block connections from source connections. * @param {Object} [params.exclude_frontmatter_blocks=true] - Whether to exclude frontmatter blocks from source connections. * @returns {Array} An array of relevant SmartSource entities. */ async find_connections(params = {}) { return await this.actions.find_connections(params); } /** * Prepares the embed input for the SmartSource by reading content and applying exclusions. * @async * @returns {Promise} The embed input string or `false` if already embedded. */ async get_embed_input(content = null) { if (typeof this._embed_input === "string" && this._embed_input.length) return this._embed_input; if (!content) content = await this.read(); if (!content) { console.warn("SmartSource.get_embed_input: No content available for embedding: " + this.path); return ""; } if (this.excluded_lines.length) { const content_lines = content.split("\n"); this.excluded_lines.forEach((lines) => { const { start, end } = lines; for (let i = start; i <= end; i++) { content_lines[i] = ""; } }); content = content_lines.filter((line) => line.length).join("\n"); } const breadcrumbs = this.path.split("/").join(" > ").replace(".md", ""); const max_tokens = this.collection.embed_model.model.data.max_tokens || 500; const max_chars = Math.floor(max_tokens * 3.7); this._embed_input = `${breadcrumbs}: ${content}`.substring(0, max_chars); return this._embed_input; } /** * Opens the SmartSource note in the SmartConnections plugin. * @returns {void} */ open() { this.env.smart_connections_plugin.open_note(this.path); } /** * Retrieves the block associated with a specific line number. * @param {number} line - The line number to search for. * @returns {SmartBlock|null} The corresponding SmartBlock or `null` if not found. */ get_block_by_line(line) { return Object.entries(this.data.blocks || {}).reduce((acc, [sub_key, range]) => { if (acc) return acc; if (range[0] <= line && range[1] >= line) { const block = this.block_collection.get(this.key + sub_key); if (block?.vec) return block; } return acc; }, null); } /** * Checks if the source file exists in the file system. * @async * @returns {Promise} A promise that resolves to `true` if the file exists, `false` otherwise. */ async has_source_file() { return await this.fs.exists(this.path); } // CRUD /** * FILTER/SEARCH METHODS */ /** * Searches for keywords within the entity's data and content. * @async * @param {Object} search_filter - The search filter object. * @param {string[]} search_filter.keywords - An array of keywords to search for. * @param {string} [search_filter.type='any'] - The type of search to perform. 'any' counts all matching keywords, 'all' counts only if all keywords match. * @returns {Promise} A promise that resolves to the number of matching keywords. */ async search(search_filter = {}) { const { keywords, type = "any", limit } = search_filter; if (!keywords || !Array.isArray(keywords)) { console.warn("Entity.search: keywords not set or is not an array"); return 0; } if (limit && this.collection.search_results_ct >= limit) return 0; const lowercased_keywords = keywords.map((keyword) => keyword.toLowerCase()); const content = await this.read(); if (!content || typeof content !== "string" || !content.length) { if (content.mime_type) { console.warn(`Entity.search: No content available for searching: ${this.path}, mime_type: ${content.mime_type}`); } else { console.warn(`Entity.search: No content available for searching: ${this.path}, content: ${content ? JSON.stringify(content) : "empty"}`); } return 0; } const lowercased_content = content.toLowerCase(); const lowercased_path = this.path.toLowerCase(); const matching_keywords = lowercased_keywords.filter( (keyword) => lowercased_path.includes(keyword) || lowercased_content.includes(keyword) ); if (type === "all") { return matching_keywords.length === lowercased_keywords.length ? matching_keywords.length : 0; } else { return matching_keywords.length; } } /** * ADAPTER METHODS */ use_source_adapter(method, ...args) { if (!this.source_adapter) { console.warn(`No source adapter available for ${this.key}. Cannot use method ${method}.`); return; } if (typeof this.source_adapter[method] !== "function") { console.warn(`Source adapter for ${this.key} does not implement method ${method}.`); return; } return this.source_adapter[method](...args); } /** * Appends content to the end of the source file. * @async * @param {string} content - The content to append to the file. * @returns {Promise} A promise that resolves when the operation is complete. */ async append(content) { await this.use_source_adapter("append", content); await this.import(); } /** * Updates the entire content of the source file. * @async * @param {string} full_content - The new content to write to the file. * @param {Object} [opts={}] - Additional options for the update. * @returns {Promise} A promise that resolves when the operation is complete. */ async update(full_content, opts = {}) { try { await this.use_source_adapter("update", full_content, opts); await this.import(); } catch (error) { console.error(`Error during update for ${this.key}:`, error); } } /** * Reads the entire content of the source file. * @async * @param {Object} [opts={}] - Additional options for reading. * @returns {Promise} A promise that resolves with the content of the file. */ async read(opts = {}) { try { return await this.use_source_adapter("read", opts) || ""; } catch (error) { console.error(`Error during reading ${this.key} (returning empty string)`, error); return ""; } } /** * Removes the source file from the file system and deletes the entity. * This is different from `delete()` because it also removes the source file. * @async * @returns {Promise} A promise that resolves when the operation is complete. */ async remove() { try { await this.use_source_adapter("remove"); } catch (error) { console.error(`Error during remove for ${this.key}:`, error); } } /** * Moves the current source to a new location. * Handles the destination as a string (new path) or entity (block or source). * * @async * @param {string|Object|SmartEntity} entity_ref - The destination path or entity to move to. * @throws {Error} If the entity reference is invalid. * @returns {Promise} A promise that resolves when the move operation is complete. */ async move_to(entity_ref) { try { await this.use_source_adapter("move_to", entity_ref); } catch (error) { console.error(`Error during move for ${this.key}:`, error); } } /** * Merges the given content into the current source. * Parses the content into blocks and either appends to existing blocks, replaces blocks, or replaces all content. * * @async * @param {string} content - The content to merge into the current source. * @param {Object} [opts={}] - Options object. * @param {string} [opts.mode='append'] - The merge mode: 'append', 'replace_blocks', or 'replace_all'. * @returns {Promise} */ async merge(content, opts = {}) { try { await this.use_source_adapter("merge", content, opts); await this.import(); } catch (error) { console.error(`Error during merge for ${this.key}:`, error); } } /** * Handles errors during the load process. * @param {Error} err - The error encountered during load. * @returns {void} */ on_load_error(err) { super.on_load_error(err); if (err.code === "ENOENT") { this._queue_load = false; this.queue_import(); } } // GETTERS /** * Retrieves the block collection associated with SmartSources. * @readonly * @returns {SmartBlocks} The block collection instance. */ get block_collection() { return this.env.smart_blocks; } /** * Retrieves the vector representations of all blocks within the SmartSource. * @readonly * @returns {Array>} An array of vectors. */ get block_vecs() { return this.blocks.map((block) => block.vec).filter((vec) => vec); } /** * Retrieves all blocks associated with the SmartSource. * @readonly * @returns {Array} An array of SmartBlock instances. * @description * Uses block refs (Fastest) to get blocks without iterating over all blocks */ get blocks() { if (this.data.blocks) return this.block_collection.get_many(Object.keys(this.data.blocks).map((key) => this.key + key)); return []; } /** * Determines if the SmartSource is excluded from processing. * @readonly * @returns {boolean} `true` if excluded, `false` otherwise. */ get excluded() { return this.fs.is_excluded(this.path); } /** * Retrieves the lines excluded from embedding. * @readonly * @returns {Array} An array of objects with `start` and `end` line numbers. */ get excluded_lines() { return this.blocks.filter((block) => block.excluded).map((block) => block.lines); } /** * Retrieves the file system instance from the SmartSource's collection. * @readonly * @returns {SmartFS} The file system instance. */ get fs() { return this.collection.fs; } /** * Retrieves the file object associated with the SmartSource. * @deprecated should be replaced with adapter methods * @readonly * @returns {Object} The file object. */ get file() { return this.fs.files[this.path]; } /** * Retrieves the file name of the SmartSource. * @readonly * @returns {string} The file name. */ get file_name() { return this.path.split("/").pop(); } /** * Retrieves the file path of the SmartSource. * @readonly * @returns {string} The file path. */ get file_path() { return this.path; } /** * Retrieves the file type based on the file extension. * @readonly * @returns {string} The file type in lowercase. */ get file_type() { if (!this._ext) { this._ext = this.collection.get_extension_for_path(this.path) || "md"; } return this._ext; } /** * Retrieves the modification time of the SmartSource. * @deprecated should be replaced with adapter methods (see get size) * @readonly * @returns {number} The modification time. */ get mtime() { return this.file?.stat?.mtime || 0; } /** * Retrieves the size of the SmartSource. * @readonly * @returns {number} The size. */ get size() { return this.source_adapter?.size || 0; } /** * Retrieves the last import stat of the SmartSource. * @readonly * @returns {Object} The last import stat. */ get last_import() { return this.data?.last_import; } /** * Retrieves the last import modification time of the SmartSource. * @readonly * @returns {number} The last import modification time. */ get last_import_mtime() { return this.last_import?.mtime || 0; } /** * Retrieves the last import size of the SmartSource. * @readonly * @returns {number} The last import size. */ get last_import_size() { return this.last_import?.size || 0; } /** * Retrieves the paths of inlinks to this SmartSource. * @readonly * @returns {Array} An array of inlink paths. */ get inlinks() { return Object.keys(this.collection.links?.[this.path] || {}); } get is_media() { return this.source_adapter.is_media || false; } /** * Determines if the SmartSource is gone (i.e., the file no longer exists). * @readonly * @returns {boolean} `true` if gone, `false` otherwise. */ get is_gone() { return !this.file; } /** * Retrieves the last read hash of the SmartSource. * @readonly * @returns {string|undefined} The last read hash or `undefined` if not set. */ get last_read() { return this.data.last_read; } get metadata() { return this.data.metadata; } get outdated() { return this.source_adapter.outdated; } /** * Retrieves the outlink paths from the SmartSource. * @readonly * @returns {Array} An array of outlink paths. */ get outlinks() { return (this.data.outlinks || []).map((link) => { const link_ref = link?.target || link; if (typeof link_ref !== "string") return null; if (link_ref.startsWith("http")) return null; const link_path = this.fs.get_link_target_path(link_ref, this.file_path); return { key: link_path, embedded: link.embedded || false }; }).filter((link_path) => link_path); } /** * @deprecated path should be derived from key (stable key principle) */ get path() { return this.data.path || this.data.key; } get source_adapters() { return this.collection.source_adapters; } get source_adapter() { if (this._source_adapter) return this._source_adapter; if (this.source_adapters[this.file_type]) this._source_adapter = new this.source_adapters[this.file_type](this); else { for (const Adapter of Object.values(this.source_adapters)) { if (typeof Adapter.detect_type !== "function") continue; if (Adapter.detect_type(this)) { this._source_adapter = new Adapter(this); break; } } } return this._source_adapter; } // COMPONENTS /** * Calculates the mean vector of all blocks within the SmartSource. * @readonly * @returns {Array|null} The mean vector or `null` if no vectors are present. */ get mean_block_vec() { if (this._mean_block_vec) { this._mean_block_vec = compute_centroid(this.block_vecs); } return this._mean_block_vec; } /** * Calculates the median vector of all blocks within the SmartSource. * @readonly * @returns {Array|null} The median vector or `null` if no vectors are present. */ get median_block_vec() { if (this._median_block_vec) { this._median_block_vec = compute_medoid(this.block_vecs); } return this._median_block_vec; } // DEPRECATED methods /** * @async * @deprecated Use `read` instead. * @returns {Promise} A promise that resolves with the content of the file. */ async _read() { return await this.source_adapter._read(); } /** * @async * @deprecated Use `remove` instead. * @returns {Promise} A promise that resolves when the entity is destroyed. */ async destroy() { await this.remove(); } /** * @async * @deprecated Use `update` instead. * @param {string} content - The content to update. * @returns {Promise} */ async _update(content) { await this.source_adapter.update(content); } /** * @deprecated Use `source` instead. * @readonly * @returns {SmartSource} The associated SmartSource instance. */ get t_file() { return this.fs.files[this.path]; } }; var smart_source_default = { class: SmartSource, actions: { find_connections: find_connections2 } }; // node_modules/obsidian-smart-env/node_modules/smart-sources/smart_sources.js var SmartSources = class extends SmartEntities { /** * Creates an instance of SmartSources. * @constructor * @param {Object} env - The environment instance. * @param {Object} [opts={}] - Configuration options. */ constructor(env, opts = {}) { super(env, opts); this.search_results_ct = 0; this._excluded_headings = null; this.env_event_unsubscribers = []; this.sources_re_import_queue = {}; this.sources_re_import_timeout = null; this.sources_re_import_halted = false; } /** * Initializes the SmartSources instance by performing an initial scan of sources. * @async * @returns {Promise} */ async init() { await super.init(); await this.init_items(); this.register_env_event_listeners(); this.register_source_watchers(); } /** * Registers env.events listeners for source lifecycle events emitted by filesystem adapters. * @returns {void} */ register_env_event_listeners() { this.unregister_env_event_listeners(); if (!this.env?.events) return; const listeners = [ ["sources:created", (event) => this.handle_source_created(event)], ["sources:modified", (event) => this.handle_source_modified(event)], ["sources:renamed", (event) => this.handle_source_renamed(event)], ["sources:deleted", (event) => this.handle_source_deleted(event)] ]; this.env_event_unsubscribers = listeners.map(([event_key, handler]) => this.env.events.on(event_key, handler)).filter(Boolean); } /** * Unregisters env.events listeners that were previously attached by this collection. * @returns {void} */ unregister_env_event_listeners() { if (!Array.isArray(this.env_event_unsubscribers)) return; while (this.env_event_unsubscribers.length) { const unsub = this.env_event_unsubscribers.pop(); try { unsub?.(); } catch (error) { console.warn("SmartSources: Failed to unregister env event listener", error); } } } /** * Determines whether the incoming event should be handled by this collection instance. * @param {Object} event * @returns {boolean} */ should_handle_event(event = {}) { const { collection_key } = event; if (collection_key && collection_key !== this.collection_key) return false; return true; } /** * Normalizes event payload keys into a canonical source path. * @param {Object} event * @returns {string|undefined} */ get_event_path(event = {}) { return event.item_key || event.path || event.new_path; } /** * Handles create events emitted by filesystem adapters. * @param {Object} event * @returns {void} */ handle_source_created(event = {}) { if (!this.should_handle_event(event)) return; const key = this.get_event_path(event); if (!key) return; const source = this.init_file_path(key) || this.get(key); if (!source) { console.warn("SmartSources: Unable to initialize source on create event", event); return; } this.queue_source_re_import(source, { event_source: event.event_source }); } /** * Handles modify events emitted by filesystem adapters. * @param {Object} event * @returns {void} */ handle_source_modified(event = {}) { if (!this.should_handle_event(event)) return; const key = this.get_event_path(event); if (!key) return; if (this.fs.is_excluded(key)) return; let source = this.get(key); if (!source) source = this.init_file_path(key); if (!source) { console.warn("SmartSources: Unable to resolve source on modify event", { key, event }); return; } this.queue_source_re_import(source, { event_source: event.event_source }); } /** * Handles rename events emitted by filesystem adapters. * @param {Object} event * @returns {void} */ handle_source_renamed(event = {}) { if (!this.should_handle_event(event)) return; const new_key = this.get_event_path(event); const old_key = event.old_path || event.from; if (!new_key && !old_key) return; if (old_key && this.items[old_key]) { const old_source = this.items[old_key]; old_source?.delete?.(); delete this.items[old_key]; if (this.rename_debounce_timeout) clearTimeout(this.rename_debounce_timeout); this.rename_debounce_timeout = setTimeout(() => { this.process_save_queue(); this.rename_debounce_timeout = null; }, 1e3); } if (!new_key) return; let source = this.get(new_key); if (!source) source = this.init_file_path(new_key); if (!source) { console.warn("SmartSources: Unable to initialize source on rename event", event); return; } this.queue_source_re_import(source, { event_source: event.event_source }); } /** * Handles delete events emitted by filesystem adapters. * @param {Object} event * @returns {void} */ handle_source_deleted(event = {}) { if (!this.should_handle_event(event)) return; const key = this.get_event_path(event); if (!key) return; delete this.items[key]; if (this.sources_re_import_queue[key]) { delete this.sources_re_import_queue[key]; } } /** * Requests filesystem adapters to register source watchers for this collection. * @returns {void} */ register_source_watchers() { const adapter = this.fs?.adapter; if (!adapter || typeof adapter.register_source_watchers !== "function") return; if (this._source_watchers_registered) return; this._source_watchers_registered = adapter.register_source_watchers(this); } /** * Queues a SmartSource for re-import and schedules processing. * @param {import('./smart_source.js').SmartSource} source * @param {Object} [event_meta] * @returns {void} */ queue_source_re_import(source, event_meta = {}) { if (!source?.key) return; if (this.sources_re_import_queue[source.key]) return; source.data.last_import = { at: 0, hash: null, mtime: 0, size: 0 }; this.sources_re_import_queue[source.key] = { source, event_meta }; this.debounce_re_import_queue(); } /** * Debounces re-import processing to respect the configured wait time. * @returns {void} */ debounce_re_import_queue() { this.sources_re_import_halted = true; if (this.sources_re_import_timeout) clearTimeout(this.sources_re_import_timeout); const queue_keys = Object.keys(this.sources_re_import_queue || {}); if (!queue_keys.length) { this.sources_re_import_timeout = null; return; } const wait_seconds = typeof this.env?.settings?.re_import_wait_time === "number" ? this.env.settings.re_import_wait_time : 13; this.sources_re_import_timeout = setTimeout( () => this.run_re_import(), wait_seconds * 1e3 ); } /** * Processes the queued re-import tasks. * @returns {Promise} */ async run_re_import() { this.sources_re_import_halted = false; const queue_entries = Object.entries(this.sources_re_import_queue || {}); if (!queue_entries.length) { if (this.sources_re_import_timeout) clearTimeout(this.sources_re_import_timeout); this.sources_re_import_timeout = null; return; } for (const [key, { source }] of queue_entries) { await source.import(); if (!this._embed_queue) this._embed_queue = []; if (source.should_embed) this._embed_queue.push(source); if (this.block_collection?.settings?.embed_blocks) { for (const block of source.blocks || []) { if (block._queue_embed || block.should_embed && block.is_unembedded) { this._embed_queue.push(block); block._queue_embed = true; } } } delete this.sources_re_import_queue[key]; if (this.sources_re_import_halted) { this.debounce_re_import_queue(); break; } } if (this._embed_queue?.length) { const embed_start_at = Date.now(); await this.process_embed_queue(); console.log(`Processed embed queue in ${Date.now() - embed_start_at}ms`); } if (this.sources_re_import_timeout) clearTimeout(this.sources_re_import_timeout); this.sources_re_import_timeout = null; } /** * Initializes items by letting each adapter do any necessary file-based scanning. * Adapters that do not rely on file scanning can skip or do nothing. * @async * @returns {Promise} */ async init_items() { this.emit_event("source:initial_scan_started"); this.show_process_notice("initial_scan"); for (const AdapterClass of Object.values(this.source_adapters)) { if (typeof AdapterClass.init_items === "function") { await AdapterClass.init_items(this); } } this.clear_process_notice("initial_scan"); this.emit_event("source:initial_scan_completed"); this.notices?.show("done_initial_scan", { collection_key: this.collection_key }); } /** * Creates (or returns existing) a SmartSource for a given file path, if the extension is recognized. * @param {string} file_path - The path to the file or pseudo-file * @returns {SmartSource|undefined} The newly created or existing SmartSource, or undefined if no recognized extension */ init_file_path(file_path) { const ext = this.get_extension_for_path(file_path); if (!ext) { return; } if (this.fs.is_excluded(file_path)) { console.warn(`File ${file_path} is excluded from processing.`); return; } if (!this.fs.files[file_path]) { this.fs.include_file(file_path); } if (this.items[file_path]) return this.items[file_path]; const item = new this.item_type(this.env, { path: file_path }); this.items[file_path] = item; item.queue_import(); item.queue_load(); return item; } /** * Looks for an extension in descending order: * e.g. split "my.file.name.github" -> ["my","file","name","github"] * Try 'file.name.github', 'name.github', 'github' * Return the first that is in 'source_adapters' * @param {string} file_path * @returns {string|undefined} recognized extension, or undefined if none */ get_extension_for_path(file_path) { if (!file_path) return void 0; const pcs = file_path.split("."); if (pcs.length < 2) return void 0; let last_ext; pcs.shift(); while (pcs.length) { const supported_ext = pcs.join(".").toLowerCase(); if (this.source_adapters[supported_ext]) { return supported_ext; } last_ext = pcs.shift(); } return last_ext; } /** * Builds a map of links between sources. * @returns {Object} An object mapping link paths to source keys. */ build_links_map() { const start_time = Date.now(); this.links = {}; for (const source of Object.values(this.items)) { for (const link of source.outlinks) { if (!this.links[link.key]) this.links[link.key] = {}; this.links[link.key][source.key] = { ...link, key: void 0 }; } } const end_time = Date.now(); console.log(`Time spent building links: ${end_time - start_time}ms`); return this.links; } /** * Creates a new source with the given key and content. * @async * @param {string} key - The key (path) of the new source. * @param {string} content - The content to write to the new source. * @returns {Promise} The created SmartSource instance. */ async create(key, content) { await this.fs.write(key, content); await this.fs.refresh(); const source = await this.create_or_update({ path: key }); await source.import(); return source; } /** * Performs a lexical search for matching SmartSource content. * @async * @deprecated uses this.actions 2025-12-02 * @param {Object} search_filter - The filter criteria for the search. * @param {string[]} search_filter.keywords - An array of keywords to search for. * @param {number} [search_filter.limit] - The maximum number of results to return. * @returns {Promise>} A promise that resolves to an array of matching SmartSource entities. */ async search(search_filter = {}) { const { keywords, limit, ...filter_opts } = search_filter; if (!keywords) { console.warn("search_filter.keywords not set"); return []; } this.search_results_ct = 0; const initial_results = this.filter(filter_opts); const search_results = []; for (let i = 0; i < initial_results.length; i += 10) { const batch = initial_results.slice(i, i + 10); const batch_results = await Promise.all( batch.map(async (item) => { try { const matches = await item.search(search_filter); if (matches) { this.search_results_ct++; return { item, score: matches }; } else return null; } catch (error) { console.error(`Error searching item ${item.id || "unknown"}:`, error); return null; } }) ); search_results.push(...batch_results.filter(Boolean)); } return search_results.sort((a, b) => b.score - a.score).map((result) => result.item); } /** * Looks up entities based on the provided parameters. * @async * @deprecated uses this.actions 2025-12-02 * @param {Object} [params={}] - Parameters for the lookup. * @param {Object} [params.filter] - Filter options. * @param {number} [params.k] - Deprecated. Use `params.filter.limit` instead. * @returns {Promise>} A promise that resolves to an array of matching SmartSource entities. */ async lookup(params = {}) { const limit = params.filter?.limit || params.k || this.env.settings.lookup_k || 10; if (params.filter?.limit) delete params.filter.limit; if (params.collection) { const collection = this.env[params.collection]; if (collection && collection.lookup) { delete params.collection; params.skip_blocks = true; const results2 = await collection.lookup(params); if (results2.error) { console.warn(results2.error); return []; } return results2.slice(0, limit); } } let results = await super.lookup(params); if (results.error) { console.warn(results.error); return []; } if (this.block_collection?.settings?.embed_blocks && !params.skip_blocks) { results = [ ...results, ...await this.block_collection.lookup(params) ].sort(sort_by_score); } return results.slice(0, limit); } /** * Processes the load queue by loading items and optionally importing them. * Called after a "re-load" from settings, or after environment init. * @async * @returns {Promise} */ async process_load_queue() { await super.process_load_queue(); if (this.collection_key === "smart_sources" && this.env.smart_blocks) { Object.values(this.env.smart_blocks.items).forEach((item) => item.init()); } if (this.block_collection) { this.block_collection.loaded = Object.keys(this.block_collection.items).length; } if (!this.opts.prevent_import_on_load) { await this.process_source_import_queue(this.opts); } this.build_links_map(); this.block_collection.cleanup_blocks(); } /** * @method process_source_import_queue * @description * Imports items (SmartSources or SmartBlocks) that have been flagged for import. */ async process_source_import_queue(opts = {}) { const { process_embed_queue = true, force = false } = opts; if (force) Object.values(this.items).forEach((item) => item._queue_import = true); const import_queue = Object.values(this.items).filter((item) => item._queue_import); console.log("import_queue " + import_queue.length); if (import_queue.length) { const time_start = Date.now(); for (let i = 0; i < import_queue.length; i += 100) { this.notices?.show("import_progress", { progress: i, total: import_queue.length }); await Promise.all(import_queue.slice(i, i + 100).map((item) => item.import())); } setTimeout(() => { this.notices?.remove("import_progress"); }, 1e3); this.notices?.show("done_import", { count: import_queue.length, time_in_seconds: (Date.now() - time_start) / 1e3 }); } else { this.notices?.show("no_import_queue"); } this.build_links_map(); if (process_embed_queue) await this.process_embed_queue(); else console.log("skipping process_embed_queue"); await this.process_save_queue(); await this.block_collection?.process_save_queue(); this.emit_event("sources:import_completed"); } /** * Retrieves the source adapters based on the collection configuration. * @readonly * @returns {Object} An object mapping file extensions to adapter constructors. */ get source_adapters() { if (!this._source_adapters) { const source_adapters = Object.entries(this.env.opts.collections?.[this.collection_key]?.source_adapters || {}); const _source_adapters = source_adapters.reduce((acc, [key, Adapter]) => { if (Adapter.extensions) Adapter.extensions?.forEach((ext) => acc[ext] = Adapter); else if (typeof Adapter.detect_type === "function") acc[key] = Adapter; return acc; }, {}); if (Object.keys(_source_adapters).length) { this._source_adapters = _source_adapters; } } return this._source_adapters; } /** * Retrieves the notices system from the environment. * @readonly * @returns {Object} The notices object. */ get notices() { return this.env.smart_connections_plugin?.notices || this.env.main?.notices; } /** * Retrieves the currently active note. * @readonly * @returns {SmartSource|null} The current SmartSource instance or null if none. */ get current_note() { return this.get(this.env.smart_connections_plugin.app.workspace.getActiveFile().path); } /** * Retrieves the file system instance, initializing it if necessary. * @readonly * @returns {SmartFS} The file system instance. */ get fs() { if (!this._fs) { this._fs = new this.env.opts.modules.smart_fs.class(this.env, { adapter: this.env.opts.modules.smart_fs.adapter, fs_path: this.env.opts.env_path || "", exclude_patterns: this.excluded_patterns || [] }); } return this._fs; } /** * Retrieves the settings configuration by combining superclass settings and adapter-specific settings. * @readonly * @returns {Object} The settings configuration object. */ get settings_config() { const _settings_config = { ...super.settings_config, ...this.process_settings_config(settings_config2), // ...this.process_settings_config(this.embed_model.settings_config, 'embed_model'), ...Object.entries(this.source_adapters).reduce((acc, [file_extension, adapter_constructor]) => { if (acc[adapter_constructor]) return acc; const item = this.items[Object.keys(this.items).find((i) => i.endsWith(file_extension))]; const adapter_instance = new adapter_constructor(item || new this.item_type(this.env, {})); if (adapter_instance.settings_config) { acc[adapter_constructor.name] = { type: "html", value: `

${adapter_constructor.name} adapter

` }; acc = { ...acc, ...adapter_instance.settings_config }; } return acc; }, {}) }; return _settings_config; } /** * Retrieves the block collection associated with SmartSources. * @readonly * @returns {SmartBlocks} The block collection instance. */ get block_collection() { return this.env.smart_blocks; } /** * Retrieves the embed queue containing items and their blocks to be embedded. * @readonly * @returns {Array} The embed queue. */ get embed_queue() { if (!this._embed_queue.length) { try { const embed_blocks = this.block_collection.settings.embed_blocks; this._embed_queue = Object.values(this.items).reduce((acc, item) => { if (item._queue_embed || item.should_embed && item.is_unembedded) acc.push(item); if (embed_blocks) item.blocks.forEach((block) => { if (block._queue_embed || block.should_embed && block.is_unembedded) acc.push(block); }); return acc; }, []); } catch (e) { console.error(`Error getting embed queue:`, e); } } return this._embed_queue; } /** * Clears all data by removing sources and blocks, reinitializing the file system, and reimporting items. * @async * @returns {Promise} */ async run_clear_all() { this.notices?.show("clearing_all"); await this.data_adapter.clear_all(); this.clear(); this.block_collection.clear(); this._fs = null; await this.init_fs(); await this.init_items(); this._excluded_headings = null; Object.values(this.items).forEach((item) => { item.queue_import(); item.queue_embed(); item.loaded_at = Date.now() + 9999999999; }); this.notices?.remove("clearing_all"); this.notices?.show("done_clearing_all"); await this.process_source_import_queue(); } async init_fs(opts = {}) { const { force_refresh = false } = opts; if (force_refresh) await this.env.fs.refresh(); await this.fs.load_exclusions(); this.fs.file_paths = this.fs.post_process(this.env.fs.file_paths); this.fs.files = this.fs.file_paths.reduce((acc, file_path) => { acc[file_path] = this.env.fs.files[file_path]; return acc; }, {}); this.fs.folder_paths = this.fs.post_process(this.env.fs.folder_paths); this.fs.folders = this.fs.folder_paths.reduce((acc, folder_path) => { acc[folder_path] = this.env.fs.folders[folder_path]; return acc; }, {}); } // /** // * Deletes all *.ajson files in the "multi/" data_dir, then re-saves all sources (opts.force=true). // */ // async run_clean_up_data() { // this.notices?.show('pruning_collection', { collection_key: this.block_collection.collection_key }); // // Identify blocks to remove // const remove_smart_blocks = this.block_collection.filter(item => { // if(!item.vec) return false; // skip blocks that have no vec? // if(item.is_gone) { // item.reason = "is_gone"; // return true; // } // if(!item.should_embed){ // item.reason = "should not embed"; // return true; // } // return false; // }); // // Remove identified blocks // for(let i = 0; i < remove_smart_blocks.length; i++){ // const item = remove_smart_blocks[i]; // if(item.is_gone) item.delete(); // else item.remove_embeddings(); // } // this.notices?.remove('pruning_collection'); // this.notices?.show('done_pruning_collection', { collection_key: this.block_collection.collection_key, count: remove_smart_blocks.length }); // console.log(`Pruned ${remove_smart_blocks.length} blocks:\n${remove_smart_blocks.map(item => `${item.reason} - ${item.key}`).join("\n")}`); // // 1) remove all .ajson files in `this.data_dir` ("multi" by default) // await this.data_fs.remove_dir(this.data_dir, true); // // 2) forcibly re-save all items // await this.process_save_queue({ force: true }); // } /** * Retrieves patterns for excluding files/folders from processing. * @readonly * @returns {Array} */ get excluded_patterns() { return [ ...this.file_exclusions?.map((file) => `${file}**`) || [], ...(this.folder_exclusions || []).map((folder) => `${folder}**`), this.env.env_data_dir + "/**" ]; } /** * Retrieves the file exclusion patterns from settings. * @readonly * @returns {Array} An array of file exclusion patterns. */ get file_exclusions() { const csv = this.env.settings?.smart_sources?.file_exclusions; return csv?.length ? csv.split(",").map((file) => file.trim()) : []; } /** * Retrieves the folder exclusion patterns from settings. * @readonly * @returns {Array} An array of folder exclusion patterns. */ get folder_exclusions() { const csv = this.env.settings?.smart_sources?.folder_exclusions; return csv?.length ? csv.split(",").map((folder) => { folder = folder.trim(); if (folder === "") return false; if (folder === "/") return false; if (!folder.endsWith("/")) return folder + "/"; return folder; }).filter(Boolean) : []; } /** * Retrieves the excluded headings from settings. * @readonly * @returns {Array} An array of excluded headings. */ get excluded_headings() { if (!this._excluded_headings) { const csv = this.env.settings?.smart_sources?.excluded_headings; this._excluded_headings = csv?.length ? csv.split(",").map((heading) => heading.trim()) : []; } return this._excluded_headings; } /** * Retrieves the count of included files that are not excluded. * @readonly * @returns {number} The number of included files. */ get included_files() { const extensions = Object.keys(this.source_adapters); return this.fs.file_paths.filter((file_path) => extensions.some((ext) => file_path.endsWith(ext)) && !this.fs.is_excluded(file_path)).length; } get excluded_file_paths() { return this.env.fs.file_paths.filter((file_path) => this.fs.is_excluded(file_path)); } /** * Retrieves the total number of files, regardless of exclusion. * @readonly * @returns {number} The total number of files. */ get total_files() { return this.fs.file_paths.filter((file) => file.endsWith(".md") || file.endsWith(".canvas")).length; } /** * Unloads the collection and clears registered listeners and timers. * @returns {void} */ unload() { this.unregister_env_event_listeners(); if (this.sources_re_import_timeout) clearTimeout(this.sources_re_import_timeout); this.sources_re_import_timeout = null; this.sources_re_import_queue = {}; super.unload(); } get data_dir() { return "multi"; } }; var settings_config2 = { // file_exclusions: { // name: 'File Exclusions', // description: 'Comma-separated list of files to exclude.', // type: 'text', // default: '', // callback: 'update_exclusions', // }, // folder_exclusions: { // name: 'Folder Exclusions', // description: 'Comma-separated list of folders to exclude.', // type: 'text', // default: '', // callback: 'update_exclusions', // }, // excluded_headings: { // name: 'Excluded Headings', // description: 'Comma-separated list of headings to exclude.', // type: 'text', // default: '', // }, }; // node_modules/obsidian-smart-env/node_modules/smart-collections/adapters/_adapter.js var CollectionDataAdapter = class { /** * @constructor * @param {Object} collection - The collection instance that this adapter manages. */ constructor(collection) { this.collection = collection; this.env = collection.env; } /** * The class to use for item adapters. * @type {typeof ItemDataAdapter} */ ItemDataAdapter = ItemDataAdapter; /** * Optional factory method to create item adapters. * If `this.item_adapter_class` is not null, it uses that; otherwise can be overridden by subclasses. * @param {Object} item - The item to create an adapter for. * @returns {ItemDataAdapter} */ create_item_adapter(item) { if (!this.ItemDataAdapter) { throw new Error("No item_adapter_class specified and create_item_adapter not overridden."); } return new this.ItemDataAdapter(item); } /** * Load a single item by its key using an `ItemDataAdapter`. * @async * @abstract * @param {string} key - The key of the item to load. * @returns {Promise} Resolves when the item is loaded. */ async load_item(key) { throw new Error("Not implemented"); } /** * Save a single item by its key using its associated `ItemDataAdapter`. * @async * @abstract * @param {string} key - The key of the item to save. * @returns {Promise} Resolves when the item is saved. */ async save_item(key) { throw new Error("Not implemented"); } /** * Delete a single item by its key. This may involve updating or removing its file, * as handled by the `ItemDataAdapter`. * @async * @abstract * @param {string} key - The key of the item to delete. * @returns {Promise} Resolves when the item is deleted. */ async delete_item(key) { throw new Error("Not implemented"); } /** * Process any queued load operations. Typically orchestrates calling `load_item()` * on items that have been flagged for loading. * @async * @abstract * @returns {Promise} */ async process_load_queue() { throw new Error("Not implemented"); } /** * Process any queued save operations. Typically orchestrates calling `save_item()` * on items that have been flagged for saving. * @async * @abstract * @returns {Promise} */ async process_save_queue() { throw new Error("Not implemented"); } /** * Load the item's data from storage if it has been updated externally. * @async * @param {string} key - The key of the item to load. * @returns {Promise} Resolves when the item is loaded. */ async load_item_if_updated(item) { const adapter = this.create_item_adapter(item); await adapter.load_if_updated(); } /** * Clear all data associated with this collection. * @async * @abstract * @returns {Promise} */ async clear_all() { throw new Error("Not implemented"); } }; var ItemDataAdapter = class { /** * @constructor * @param {Object} item - The collection item instance that this adapter manages. */ constructor(item) { this.item = item; } /** * Load the item's data from storage. May involve reading a file and parsing * its contents, then updating `item.data`. * @async * @abstract * @returns {Promise} Resolves when the item is fully loaded. */ async load() { throw new Error("Not implemented"); } /** * Save the item's data to storage. May involve writing to a file or appending * lines in an append-only format. * @async * @abstract * @param {string|null} [ajson=null] - An optional serialized representation of the item’s data. * If not provided, the adapter should derive it from the item. * @returns {Promise} Resolves when the item is saved. */ async save(ajson = null) { throw new Error("Not implemented"); } /** * Delete the item's data from storage. May involve removing a file or writing * a `null` entry in an append-only file to signify deletion. * @async * @abstract * @returns {Promise} Resolves when the item’s data is deleted. */ async delete() { throw new Error("Not implemented"); } /** * Returns the file path or unique identifier used by this adapter to locate and store * the item's data. This may be a file name derived from the item's key. * @abstract * @returns {string} The path or identifier for the item's data. */ get data_path() { throw new Error("Not implemented"); } /** * @returns {CollectionDataAdapter} The collection data adapter that this item data adapter belongs to. */ get collection_adapter() { return this.item.collection.data_adapter; } get env() { return this.item.env; } /** * Load the item's data from storage if it has been updated externally. * @async * @abstract * @returns {Promise} Resolves when the item is loaded. */ async load_if_updated() { throw new Error("Not implemented"); } }; // node_modules/obsidian-smart-env/node_modules/smart-collections/adapters/_file.js var FileCollectionDataAdapter = class extends CollectionDataAdapter { /** * The class to use for item adapters. * @type {typeof ItemDataAdapter} */ ItemDataAdapter = FileItemDataAdapter; /** * @returns {Object} Filesystem interface derived from environment or collection settings. */ get fs() { return this.collection.data_fs || this.collection.env.data_fs; } async clear_all() { await this.fs.remove_dir(this.collection.data_dir, true); } }; var FileItemDataAdapter = class extends ItemDataAdapter { /** * @returns {Object} Filesystem interface derived from environment or collection settings. */ get fs() { return this.item.collection.data_fs || this.item.collection.env.data_fs; } /** * Resolve the file path for the item's data. * @abstract * @returns {string} Path to the persisted item data. */ get data_path() { throw new Error("Not implemented"); } async load_if_updated() { const data_path = this.data_path; if (await this.fs.exists(data_path)) { const loaded_at = this.item.loaded_at || 0; const data_file_stat = await this.fs.stat(data_path); if (data_file_stat.mtime > loaded_at + 1 * 60 * 1e3) { console.log(`Smart Collections: Re-loading item ${this.item.key} because it has been updated on disk`); await this.load(); } } } }; // node_modules/obsidian-smart-env/node_modules/smart-collections/adapters/ajson_multi_file.js var class_to_collection_key = { "SmartSource": "smart_sources", "SmartNote": "smart_sources", // DEPRECATED "SmartBlock": "smart_blocks", "SmartDirectory": "smart_directories" }; var AjsonMultiFileCollectionDataAdapter = class extends FileCollectionDataAdapter { /** * The class to use for item adapters. * @type {typeof ItemDataAdapter} */ ItemDataAdapter = AjsonMultiFileItemDataAdapter; /** * Load a single item by its key. * @async * @param {string} key * @returns {Promise} */ async load_item(key) { const item = this.collection.get(key); if (!item) return; const adapter = this.create_item_adapter(item); await adapter.load(); } /** * Save a single item by its key. * @async * @param {string} key * @returns {Promise} */ async save_item(key) { const item = this.collection.get(key); if (!item) return; const adapter = this.create_item_adapter(item); await adapter.save(); } /** * Process any queued load operations. * @async * @returns {Promise} */ async process_load_queue() { this.collection.emit_event("collection:load_started"); this.collection.show_process_notice("loading_collection"); if (!await this.fs.exists(this.collection.data_dir)) { await this.fs.mkdir(this.collection.data_dir); } const load_queue = Object.values(this.collection.items).filter((item) => item._queue_load); if (!load_queue.length) { this.collection.clear_process_notice("loading_collection"); return; } const now = Date.now(); console.log(`Loading ${this.collection.collection_key}: ${load_queue.length} items from disk`); const batch_size = 100; for (let i = 0; i < load_queue.length; i += batch_size) { const batch = load_queue.slice(i, i + batch_size); await Promise.all(batch.map((item) => { const adapter = this.create_item_adapter(item); return adapter.load().catch((err) => { console.warn(`Error loading item ${item.key}`, err); item.queue_load(); }); })); } console.log(`Loaded ${this.collection.collection_key} from disk in ${Date.now() - now}ms`); this.collection.loaded = load_queue.length; this.collection.clear_process_notice("loading_collection"); this.collection.emit_event("collection:load_completed"); } /** * Process any queued save operations. * @async * @returns {Promise} */ async process_save_queue() { this.collection.emit_event("collection:save_started"); this.collection.show_process_notice("saving_collection"); const save_queue = Object.values(this.collection.items).filter((item) => item._queue_save); console.log(`Saving ${this.collection.collection_key}: ${save_queue.length} items`); const time_start = Date.now(); const batch_size = 50; for (let i = 0; i < save_queue.length; i += batch_size) { const batch = save_queue.slice(i, i + batch_size); await Promise.all(batch.map((item) => { const adapter = this.create_item_adapter(item); return adapter.save().catch((err) => { console.warn(`Error saving item ${item.key}`, err); item.queue_save(); }); })); } const deleted_items = Object.values(this.collection.items).filter((item) => item.deleted); if (deleted_items.length) { deleted_items.forEach((item) => { delete this.collection.items[item.key]; }); } console.log(`Saved ${this.collection.collection_key} in ${Date.now() - time_start}ms`); this.collection.clear_process_notice("saving_collection"); this.collection.emit_event("collection:save_completed"); } get_item_data_path(key) { return [ this.collection.data_dir || "multi", this.fs?.sep || "/", this.get_data_file_name(key) + ".ajson" ].join(""); } /** * Transforms the item key into a safe filename. * Replaces spaces, slashes, and dots with underscores. * @returns {string} safe file name */ get_data_file_name(key) { return key.split("#")[0].replace(/[\s\/\.]/g, "_").replace(".md", ""); } /** * Build a single AJSON line for the given item and data. * @param {Object} item * @returns {string} */ get_item_ajson(item) { const collection_key = item.collection_key; const key = item.key; const data_value = item.deleted ? "null" : JSON.stringify(item.data); return `${JSON.stringify(`${collection_key}:${key}`)}: ${data_value},`; } }; var AjsonMultiFileItemDataAdapter = class extends FileItemDataAdapter { /** * Derives the `.ajson` file path from the collection's data_dir and item key. * @returns {string} */ get data_path() { return this.collection_adapter.get_item_data_path(this.item.key); } /** * Load the item from its `.ajson` file. * @async * @returns {Promise} */ async load() { try { const raw_data = await this.fs.adapter.read(this.data_path, "utf-8", { no_cache: true }); if (!raw_data) { this.item.queue_import(); return; } const { rewrite, file_data } = this._parse(raw_data); if (rewrite) { if (file_data.length) await this.fs.write(this.data_path, file_data); else await this.fs.remove(this.data_path); } const last_import_mtime = this.item.data.last_import?.at || 0; if (last_import_mtime && this.item.init_file_mtime > last_import_mtime) { this.item.queue_import(); } } catch (e) { this.item.queue_import(); } } /** * Parse the entire AJSON content as a JSON object, handle legacy keys, and extract final state. * @private * @param {string} ajson * @returns {boolean} */ _parse(ajson) { try { let rewrite = false; if (!ajson.length) return false; ajson = ajson.trim(); const original_line_count = ajson.split("\n").length; const json_str = "{" + ajson.slice(0, -1) + "}"; const data = JSON.parse(json_str); const entries = Object.entries(data); for (let i = 0; i < entries.length; i++) { const [ajson_key, value] = entries[i]; if (!value) { delete data[ajson_key]; rewrite = true; continue; } const { collection_key, item_key, changed } = this._parse_ajson_key(ajson_key); if (changed) { rewrite = true; data[collection_key + ":" + item_key] = value; delete data[ajson_key]; } const collection = this.env[collection_key]; if (!collection) continue; const existing_item = collection.get(item_key); if (!value.key) value.key = item_key; if (existing_item) { existing_item.data = value; existing_item._queue_load = false; existing_item.loaded_at = Date.now(); } else { const ItemClass = collection.item_type; const new_item = new ItemClass(this.env, value); new_item._queue_load = false; new_item.loaded_at = Date.now(); collection.set(new_item); } } if (rewrite || original_line_count > entries.length) { rewrite = true; } return { rewrite, file_data: rewrite ? Object.entries(data).map(([key, value]) => `${JSON.stringify(key)}: ${JSON.stringify(value)},`).join("\n") : null }; } catch (e) { if (ajson.split("\n").some((line) => !line.endsWith(","))) { console.warn("fixing trailing comma error"); ajson = ajson.split("\n").map((line) => line.endsWith(",") ? line : line + ",").join("\n"); return this._parse(ajson); } console.warn("Error parsing JSON:", e); return { rewrite: true, file_data: null }; } } _parse_ajson_key(ajson_key) { let changed; let [collection_key, ...item_key] = ajson_key.split(":"); if (class_to_collection_key[collection_key]) { collection_key = class_to_collection_key[collection_key]; changed = true; } return { collection_key, item_key: item_key.join(":"), changed }; } /** * Save the current state of the item by appending a new line to its `.ajson` file. * @async * @returns {Promise} */ async save(retries = 0) { try { const ajson_line = this.get_item_ajson(); await this.fs.append(this.data_path, "\n" + ajson_line); this.item._queue_save = false; } catch (e) { if (e.code === "ENOENT" && retries < 1) { const dir = this.collection_adapter.collection.data_dir; if (!await this.fs.exists(dir)) { await this.fs.mkdir(dir); } return await this.save(retries + 1); } console.warn("Error saving item", this.data_path, this.item.key, e); } } /** * Build a single AJSON line for the given item and data. * @param {Object} item * @returns {string} */ get_item_ajson() { return this.collection_adapter.get_item_ajson(this.item); } }; // node_modules/obsidian-smart-env/node_modules/smart-sources/adapters/data/ajson_multi_file.js var AjsonMultiFileSourcesDataAdapter = class extends AjsonMultiFileCollectionDataAdapter { ItemDataAdapter = AjsonMultiFileSourceDataAdapter; }; var AjsonMultiFileSourceDataAdapter = class extends AjsonMultiFileItemDataAdapter { }; // node_modules/obsidian-smart-env/node_modules/smart-sources/adapters/_adapter.js var SourceContentAdapter = class { constructor(item) { this.item = item; } async import() { this.throw_not_implemented("import"); } async create() { this.throw_not_implemented("create"); } async update() { this.throw_not_implemented("update"); } async read() { this.throw_not_implemented("read"); } async remove() { this.throw_not_implemented("remove"); } // HELPER METHODS get data() { return this.item.data; } // async create_hash(content) { return await create_hash(content); } create_hash(content) { return murmur_hash_32_alphanumeric(content); } get settings() { return this.item.env.settings.smart_sources[this.adapter_key]; } get adapter_key() { return to_snake(this.constructor.name); } static get adapter_key() { return to_snake(this.name); } get fs() { return this.item.collection.fs; } get env() { return this.item.env; } }; function to_snake(str) { return str[0].toLowerCase() + str.slice(1).replace(/([A-Z])/g, "_$1").toLowerCase(); } // node_modules/obsidian-smart-env/node_modules/smart-blocks/parsers/markdown.js function parse_markdown_blocks(markdown, opts = {}) { const { start_index = 1, line_keys = false } = opts; const lines = markdown.split("\n"); const LIST_KEY_WORD_LEN = opts.list_key_word_len || 10; const result = {}; const heading_stack = []; const heading_lines = {}; const heading_counts = {}; const sub_block_counts = {}; const subheading_counts = {}; const task_lines = []; const tasks = {}; let current_list_item = null; let current_content_block = null; let in_frontmatter = false; let frontmatter_started = false; const root_heading_key = "#"; let in_code_block = false; const codeblock_ranges = []; let codeblock_start = null; sub_block_counts[root_heading_key] = 0; for (let i = 0; i < lines.length; i++) { const line_number = i + start_index; const line = lines[i]; const trimmed_line = line.trim(); if (trimmed_line === "---") { if (!frontmatter_started && line_number === 1) { frontmatter_started = true; in_frontmatter = true; heading_lines["#---frontmatter---"] = [line_number, null]; continue; } else if (in_frontmatter) { in_frontmatter = false; heading_lines["#---frontmatter---"][1] = line_number; continue; } } if (in_frontmatter) { continue; } if (!in_code_block && /^[-*+]\s+\[(?: |x|X)\]/.test(trimmed_line)) { task_lines.push(line_number); if (/^[-*+]\s+\[ \]/.test(trimmed_line)) { if (!tasks.incomplete) tasks.incomplete = { all: [], top: [] }; tasks.incomplete.all.push(line_number); } if (/^[-*+]\s+\[ \]/.test(line)) { tasks.incomplete.top.push(line_number); } } if (trimmed_line.startsWith("```")) { in_code_block = !in_code_block; if (in_code_block && !codeblock_start) codeblock_start = line_number; else if (!in_code_block && codeblock_start) { codeblock_ranges.push([codeblock_start, line_number]); codeblock_start = null; } if (!current_content_block) { const parent_key = heading_stack.length > 0 ? heading_stack[heading_stack.length - 1].key : root_heading_key; if (parent_key === root_heading_key && !heading_lines[root_heading_key]) { heading_lines[root_heading_key] = [line_number, null]; } if (parent_key === root_heading_key) { current_content_block = { key: root_heading_key, start_line: line_number }; if (heading_lines[root_heading_key][1] === null || heading_lines[root_heading_key][1] < line_number) { heading_lines[root_heading_key][1] = null; } } else { if (sub_block_counts[parent_key] === void 0) { sub_block_counts[parent_key] = 0; } sub_block_counts[parent_key] += 1; const n = sub_block_counts[parent_key]; const key = `${parent_key}#{${n}}`; heading_lines[key] = [line_number, null]; current_content_block = { key, start_line: line_number }; } } continue; } const heading_match = trimmed_line.match(/^(#{1,6})\s*(.+)$/); if (heading_match && !in_code_block) { const level = heading_match[1].length; let title = heading_match[2].trim(); while (heading_stack.length > 0 && heading_stack[heading_stack.length - 1].level >= level) { const finished_heading = heading_stack.pop(); if (heading_lines[finished_heading.key][1] === null) { heading_lines[finished_heading.key][1] = line_number - 1; } } if (heading_stack.length === 0 && heading_lines[root_heading_key] && heading_lines[root_heading_key][1] === null) { heading_lines[root_heading_key][1] = line_number - 1; } if (current_content_block) { if (heading_lines[current_content_block.key][1] === null) { heading_lines[current_content_block.key][1] = line_number - 1; } current_content_block = null; } if (current_list_item) { if (heading_lines[current_list_item.key][1] === null) { heading_lines[current_list_item.key][1] = line_number - 1; } current_list_item = null; } let parent_key = ""; let parent_level = 0; if (heading_stack.length > 0) { parent_key = heading_stack[heading_stack.length - 1].key; parent_level = heading_stack[heading_stack.length - 1].level; } else { parent_key = ""; parent_level = 0; } if (heading_stack.length === 0) { heading_counts[title] = (heading_counts[title] || 0) + 1; if (heading_counts[title] > 1) { title += `[${heading_counts[title]}]`; } } else { if (!subheading_counts[parent_key]) { subheading_counts[parent_key] = {}; } subheading_counts[parent_key][title] = (subheading_counts[parent_key][title] || 0) + 1; const count = subheading_counts[parent_key][title]; if (count > 1) { title += `#{${count}}`; } } const level_diff = level - parent_level; const hashes = "#".repeat(level_diff); const key = parent_key + hashes + title; heading_lines[key] = [line_number, null]; sub_block_counts[key] = 0; heading_stack.push({ level, title, key }); continue; } const list_match = line.match(/^(\s*)([-*]|\d+\.) (.+)$/); if (list_match && !in_code_block) { const indentation = list_match[1].length; if (indentation === 0) { if (current_list_item) { if (heading_lines[current_list_item.key][1] === null) { heading_lines[current_list_item.key][1] = line_number - 1; } current_list_item = null; } if (current_content_block && current_content_block.key !== root_heading_key) { if (heading_lines[current_content_block.key][1] === null) { heading_lines[current_content_block.key][1] = line_number - 1; } current_content_block = null; } let parent_key = heading_stack.length > 0 ? heading_stack[heading_stack.length - 1].key : root_heading_key; if (parent_key === root_heading_key && !heading_lines[root_heading_key]) { heading_lines[root_heading_key] = [line_number, null]; } if (sub_block_counts[parent_key] === void 0) { sub_block_counts[parent_key] = 0; } sub_block_counts[parent_key] += 1; const n = sub_block_counts[parent_key]; let key; if (line_keys) { const content_without_task = list_match[3].replace(/^\[(?: |x|X)\]\s*/, ""); const words = get_longest_words_in_order(content_without_task, LIST_KEY_WORD_LEN); key = `${parent_key}#${words}`; } else { key = `${parent_key}#{${n}}`; } heading_lines[key] = [line_number, null]; current_list_item = { key, start_line: line_number }; continue; } if (current_list_item) { continue; } } if (trimmed_line === "") { continue; } if (!current_content_block) { if (current_list_item) { if (heading_lines[current_list_item.key][1] === null) { heading_lines[current_list_item.key][1] = line_number - 1; } current_list_item = null; } let parent_key = heading_stack.length > 0 ? heading_stack[heading_stack.length - 1].key : root_heading_key; if (parent_key === root_heading_key) { if (!heading_lines[root_heading_key]) { heading_lines[root_heading_key] = [line_number, null]; } if (heading_lines[root_heading_key][1] === null || heading_lines[root_heading_key][1] < line_number) { heading_lines[root_heading_key][1] = null; } current_content_block = { key: root_heading_key, start_line: line_number }; } else { if (sub_block_counts[parent_key] === void 0) { sub_block_counts[parent_key] = 0; } sub_block_counts[parent_key] += 1; const n = sub_block_counts[parent_key]; const key = `${parent_key}#{${n}}`; heading_lines[key] = [line_number, null]; current_content_block = { key, start_line: line_number }; } } } const total_lines = lines.length; while (heading_stack.length > 0) { const finished_heading = heading_stack.pop(); if (heading_lines[finished_heading.key][1] === null) { heading_lines[finished_heading.key][1] = total_lines + start_index - 1; } } if (current_list_item) { if (heading_lines[current_list_item.key][1] === null) { heading_lines[current_list_item.key][1] = total_lines + start_index - 1; } current_list_item = null; } if (current_content_block) { if (heading_lines[current_content_block.key][1] === null) { heading_lines[current_content_block.key][1] = total_lines + start_index - 1; } current_content_block = null; } if (heading_lines[root_heading_key] && heading_lines[root_heading_key][1] === null) { heading_lines[root_heading_key][1] = total_lines + start_index - 1; } for (const key in heading_lines) { result[key] = heading_lines[key]; } return { blocks: result, task_lines, tasks, codeblock_ranges }; } function get_longest_words_in_order(line, n = 3) { const words = line.split(/\s+/).sort((a, b) => b.length - a.length).slice(0, n); return words.sort((a, b) => line.indexOf(a) - line.indexOf(b)).join(" "); } // node_modules/obsidian-smart-env/node_modules/smart-sources/adapters/_file.js var FileSourceContentAdapter = class extends SourceContentAdapter { static async init_items(collection) { if (collection.fs_items_initialized) return; collection._fs = null; await collection.fs.init(); await collection.init_fs(); for (const file of Object.values(collection.fs.files)) { const item = collection.init_file_path(file.path); if (item) item.init_file_mtime = file.stat.mtime; } collection.fs_items_initialized = Date.now(); } /** * @name fs * @type {Object} * @readonly * @description * Access the file system interface used by this adapter. Typically derived * from `this.item.collection.fs`. */ get fs() { return this.item.collection.fs; } /** * @name file_path * @type {string} * @readonly * @description * The file path on disk corresponding to the source. Used for read/write operations. */ get file_path() { return this.item.file_path; } /** * @async * @method create * @param {string|null} [content=null] Initial content for the new file. * @description * Create a new file on disk. If content is not provided, attempts to use * `this.item.data.content` as fallback. */ async create(content = null) { if (!content) content = this.item.data.content || ""; await this.fs.write(this.file_path, content); } /** * @async * @method update * @param {string} content The full new content to write to the file. * @description * Overwrite the entire file content on disk. */ async update(content) { await this.fs.write(this.file_path, content); } /** * @async * @method read * @returns {Promise} The content of the file. * @description * Read the file content from disk. Updates `last_read` hash and timestamp on the entity’s data. * If file is large or special handling is needed, override this method. */ async read() { const content = await this.fs.read(this.file_path); this.data.last_read = { hash: this.create_hash(content || ""), at: Date.now() }; return content; } /** * @async * @method remove * @returns {Promise} * @description * Delete the file from disk. After removal, the source item should also be deleted or updated accordingly. */ async remove() { await this.fs.remove(this.file_path); } async move_to(move_to_ref) { if (!move_to_ref) { throw new Error("Invalid entity reference for move_to operation"); } const move_content = await this.read(); let has_existing = false; if (typeof move_to_ref === "string") { const existing = this.item.collection.get(move_to_ref); if (existing) { move_to_ref = existing; has_existing = true; } } else { has_existing = true; } if (has_existing) { await move_to_ref.append(move_content); } else { move_to_ref = await this.item.collection.create(move_to_ref, move_content); } if (this.item.key !== move_to_ref.key) { await this.remove(); this.item.delete(); } else { console.log(`did not delete ${this.item.key} because it was moved to ${move_to_ref.key}`); } return move_to_ref; } /** * Merge content into the source * @param {string} content - The content to merge into the source * @param {Object} opts - Options for the merge operation * @param {string} opts.mode - The mode to use for the merge operation. Defaults to 'append_blocks' (may also be 'replace_blocks') */ async merge(content, opts = {}) { const { mode = "append_blocks" } = opts; const { blocks: blocks_obj, task_lines } = parse_markdown_blocks(content); if (typeof blocks_obj !== "object" || Array.isArray(blocks_obj)) { console.warn("merge error: Expected an object from parse_markdown_blocks, but received:", blocks_obj); throw new Error("merge error: parse_markdown_blocks did not return an object as expected."); } const { new_blocks, new_with_parent_blocks, changed_blocks, same_blocks } = await this.get_changes(blocks_obj, content); for (const block of new_blocks) { await this.append(block.content); } for (const block of new_with_parent_blocks) { const parent_block = this.item.block_collection.get(block.parent_key); await parent_block.append(block.content); } for (const block of changed_blocks) { const changed_block = this.item.block_collection.get(block.key); if (mode === "replace_blocks") { await changed_block.update(block.content); } else { await changed_block.append(block.content); } } } async get_changes(blocks_obj, content) { const new_blocks = []; const new_with_parent_blocks = []; const changed_blocks = []; const same_blocks = []; const existing_blocks = this.source.data.blocks || {}; for (const [sub_key, line_range] of Object.entries(blocks_obj)) { const has_existing = !!existing_blocks[sub_key]; const block_key = `${this.source.key}${sub_key}`; const block_content = get_line_range(content, line_range[0], line_range[1]); if (!has_existing) { new_blocks.push({ key: block_key, state: "new", content: block_content }); continue; } let has_parent; let headings = sub_key.split("#"); let parent_key; while (!has_parent && headings.length > 0) { headings.pop(); parent_key = headings.join("#"); has_parent = !!existing_blocks[parent_key]; } if (has_parent) { new_with_parent_blocks.push({ key: block_key, parent_key: `${this.source.key}${parent_key}`, state: "new", content: block_content }); continue; } const block = this.item.block_collection.get(block_key); const content_hash = await this.create_hash(block_content); if (content_hash !== block.last_read?.hash) { changed_blocks.push({ key: block_key, state: "changed", content: block_content }); continue; } same_blocks.push({ key: block_key, state: "same", content: block_content }); } return { new_blocks, new_with_parent_blocks, changed_blocks, same_blocks }; } /** * Append new content to the source file, placing it at the end of the file. * @async * @param {string} content - The content to append. * @returns {Promise} */ async append(content) { const current_content = await this.read(); const new_content = [ current_content, "", content ].join("\n").trim(); await this.update(new_content); } get size() { return this.item.file?.stat?.size || 0; } }; // node_modules/obsidian-smart-env/node_modules/smart-sources/utils/get_markdown_links.js function get_markdown_links(content) { const result = []; const markdown_link_re = /\[([^\]]+?)\]\(([^)]+?)\)/g; const wikilink_re = /\[\[([^\|\]]+?)(?:\|([^\]]+?))?\]\]/g; const normalise_target = (raw) => { const trimmed = raw.trim(); if (/^[a-zA-Z][\w+\-.]*:\/\//.test(trimmed)) return trimmed; try { return decodeURIComponent(trimmed); } catch (_) { return trimmed.replace(/%20/gi, " "); } }; const is_embedded = (index) => { if (index <= 0) return false; return content[index - 1] === "!"; }; let m; while ((m = markdown_link_re.exec(content)) !== null) { const title = m[1]; const target = normalise_target(m[2]); const line_no = content.slice(0, m.index).split("\n").length; const embedded = is_embedded(m.index); const record = { title, target, line: line_no }; if (embedded) record.embedded = true; result.push(record); } while ((m = wikilink_re.exec(content)) !== null) { const target_raw = m[1]; const title = m[2] || target_raw; const target = normalise_target(target_raw); const line_no = content.slice(0, m.index).split("\n").length; const embedded = is_embedded(m.index); const record = { title, target, line: line_no }; if (embedded) record.embedded = true; result.push(record); } return result.sort( (a, b) => a.line - b.line || a.target.localeCompare(b.target) ); } // node_modules/obsidian-smart-env/node_modules/smart-sources/utils/parse_frontmatter.js function parse_value(raw_value) { const trimmed = raw_value.trim(); if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) { return trimmed.slice(1, -1); } const lower = trimmed.toLowerCase(); if (lower === "true") return true; if (lower === "false") return false; if (!isNaN(trimmed) && trimmed !== "") { return Number(trimmed); } return trimmed; } function parse_yaml_block(yaml_block) { const lines = yaml_block.split(/\r?\n/); const data = {}; let i = 0; while (i < lines.length) { const line = lines[i]; i++; if (!line.trim() || line.trim().startsWith("#")) { continue; } const match = line.match(/^([^:]+)\s*:\s*(.*)$/); if (!match) { continue; } const key = match[1].trim(); let value = match[2].trim(); if (value === ">" || value === "|") { const multiline_lines = []; while (i < lines.length) { const next_line = lines[i]; if (!/^\s+/.test(next_line) || next_line.trim().startsWith("#")) { break; } multiline_lines.push(next_line.replace(/^\s+/, "")); i++; } const joined = multiline_lines.join("\n"); data[key] = parse_value(joined); } else if (value === "") { const arr = []; let array_consumed = false; while (i < lines.length) { const next_line = lines[i]; if (!next_line.trim().startsWith("- ")) { break; } const item_value = next_line.trim().slice(2); arr.push(parse_value(item_value)); i++; array_consumed = true; } if (array_consumed) { data[key] = arr; } else { data[key] = ""; } } else { data[key] = parse_value(value); } } return data; } function parse_frontmatter(content) { if (!content.startsWith("---")) { return { frontmatter: {}, body: content }; } const lines = content.split(/\r?\n/); let end_index = -1; for (let i = 1; i < lines.length; i++) { if (lines[i].trim() === "---") { end_index = i; break; } } if (end_index === -1) { return { frontmatter: {}, body: content }; } const frontmatter_lines = lines.slice(1, end_index); const frontmatter_block = frontmatter_lines.join("\n"); const frontmatter = parse_yaml_block(frontmatter_block); const body_lines = lines.slice(end_index + 1); const body = body_lines.join("\n"); return { frontmatter, body }; } // node_modules/obsidian-smart-env/node_modules/smart-sources/utils/get_markdown_tags.js var get_markdown_tags = (content = "") => { const tag_re = /(?} */ async import() { if (!this.can_import) return; if (!this.outdated) { this.item.blocks.forEach((block) => { if (!block.vec) block.queue_embed(); }); return; } const content = await this.read(); if (!content) { return; } if (!this.item.vec) { this.item.data.last_import = null; } if (this.data.last_import?.hash === this.data.last_read?.hash) { if (this.data.blocks) return; } this.data.blocks = null; await this.parse_content(content); await this.item.parse_content(content); const { mtime, size } = this.item.file.stat; this.data.last_import = { mtime, size, at: Date.now(), hash: this.data.last_read.hash }; this.item.loaded_at = Date.now(); this.item.queue_save(); if (this.item.should_embed) this.item.queue_embed(); } // // WIP: move block parsing here // async read() { // const current_last_read_hash = this.data.last_read?.hash; // const content = await super.read(); // if(!content) return console.warn(`MarkdownSourceContentAdapter: Skipping missing-file: ${this.file_path}`); // if(current_last_read_hash === this.data.last_read?.hash) return content; // const {blocks: blocks, task_lines} = parse_markdown_blocks(content); // this.handle_excluded_headings(blocks); // } // Runs before configured content_parsers (for example, templates uses outlinks) async parse_content(content) { const outlinks = await this.get_links(content); this.data.outlinks = outlinks; const metadata = await this.get_metadata(content); this.data.metadata = metadata; } async get_links(content = null) { if (!content) content = await this.read(); if (!content) return; return get_markdown_links(content); } async get_metadata(content = null) { if (!content) content = await this.read(); if (!content) return; const { frontmatter, body } = parse_frontmatter(content); const tag_set = /* @__PURE__ */ new Set(); let fm_tags = frontmatter.tags; if (typeof fm_tags === "string") { fm_tags = fm_tags.replace(/[\[\]]/g, "").split(",").map((t) => t.trim()).filter(Boolean); } if (Array.isArray(fm_tags)) { fm_tags.forEach((tag) => tag_set.add(tag.startsWith("#") ? tag : `#${tag}`)); } get_markdown_tags(body).forEach((tag) => tag_set.add(tag)); if (tag_set.size) frontmatter.tags = [...tag_set]; return frontmatter; } // Erroneous reasons to skip import (logs to console) get can_import() { if (!this.item.file) { console.warn(`MarkdownSourceContentAdapter: Skipping missing-file: ${this.file_path}`); return false; } if (this.item.size > (this.settings?.max_import_size || 3e5)) { console.warn(`MarkdownSourceContentAdapter: Skipping large file: ${this.file_path}`); return false; } return true; } /** * @deprecated use outdated instead */ get should_import() { return this.outdated; } get outdated() { try { if (!this.data.last_import) { if (this.data.mtime && this.data.size && this.data.hash) { this.data.last_import = { mtime: this.data.mtime, size: this.data.size, at: Date.now(), hash: this.data.hash }; delete this.data.mtime; delete this.data.size; delete this.data.hash; } else { return true; } } if (this.data.last_read.at > this.data.last_import.at) { if (this.data.last_import?.hash !== this.data.last_read?.hash) return true; } if (this.data.last_import.mtime < this.item.mtime) { if (!this.data.last_import.size) return true; const size_diff = Math.abs(this.data.last_import.size - this.item.size); const size_diff_ratio = size_diff / (this.data.last_import.size || 1); if (size_diff_ratio > 0.01) return true; } return false; } catch (e) { console.warn(`MarkdownSourceContentAdapter: error getting should_import for ${this.file_path}: ${e}`); return true; } } }; // node_modules/obsidian-smart-env/adapters/smart-sources/obsidian_markdown.js var import_obsidian2 = require("obsidian"); function merge_tags(fm_tags, cache_tags = []) { const tag_set = /* @__PURE__ */ new Set(); if (typeof fm_tags === "string") { fm_tags = fm_tags.replace(/[\[\]]/g, "").split(",").map((t) => t.trim()).filter(Boolean); } if (Array.isArray(fm_tags)) { fm_tags.forEach((tag) => tag_set.add(tag.startsWith("#") ? tag : `#${tag}`)); } cache_tags.forEach(({ tag }) => tag_set.add(tag)); return [...tag_set]; } var ObsidianMarkdownSourceContentAdapter = class extends MarkdownSourceContentAdapter { /** * Returns metadata using Obsidian's metadataCache, merging frontmatter and tags. * @async * @returns {Promise} */ async get_metadata() { const app = this.item.env.main.app; const cache = app.metadataCache.getFileCache(this.item.file) || {}; const tags = merge_tags(cache.frontmatter?.tags, cache.tags); if (cache.frontmatter) { if (tags.length) cache.frontmatter.tags = tags; return cache.frontmatter; } return tags.length ? { tags } : void 0; } /** * Reads the file content. If opts.render_output is true, attempts to use * Obsidian's MarkdownRenderer to render the file to HTML, then convert it * back to markdown via htmlToMarkdown. * @async * @param {Object} [opts={}] - Options for reading. * @param {boolean} [opts.render_output=false] - If true, render MD -> HTML -> MD. * @returns {Promise} The file content (possibly rendered). */ async read(opts = {}) { const content = await super.read(opts); if (!opts.render_output) { return content; } const app = this.item.env.main.app; if (!app || !import_obsidian2.MarkdownRenderer || !import_obsidian2.htmlToMarkdown) { console.warn("Obsidian environment not found; cannot render markdown."); return content; } const container = document.createElement("div"); await import_obsidian2.MarkdownRenderer.render(app, content, container, this.item.path, new import_obsidian2.Component()); let last_html = container.innerHTML; const max_wait = 1e4; let wait_time = 0; let conseq_same = 0; let changed = true; while (conseq_same < 7) { await new Promise((resolve) => setTimeout(resolve, 100)); changed = last_html !== container.innerHTML; last_html = container.innerHTML; if (!changed) conseq_same++; else conseq_same = 0; wait_time += 100; if (wait_time > max_wait) { console.warn("ObsidianMarkdownSourceContentAdapter: Timeout waiting for markdown to render."); break; } } const newMd = (0, import_obsidian2.htmlToMarkdown)(container); return newMd; } }; // node_modules/obsidian-smart-env/adapters/smart-sources/bases.js var BasesSourceContentAdapter = class extends FileSourceContentAdapter { static extensions = ["base"]; async import() { } }; // node_modules/obsidian-smart-env/adapters/smart-sources/canvas.js var CanvasSourceContentAdapter = class extends FileSourceContentAdapter { static extensions = ["canvas"]; async import() { } }; // node_modules/obsidian-smart-env/adapters/smart-sources/excalidraw.js var ExcalidrawSourceContentAdapter = class extends ObsidianMarkdownSourceContentAdapter { static extensions = ["excalidraw.md"]; is_media = true; // Excalidraw files are treated as media for rendering async read(opts = {}) { const full_content = await super.read(opts); const BEGIN_LINE_MATCHER = "# Text Elements"; const END_LINE_MATCHER = "# Drawing"; const text_elements_start = full_content.indexOf(BEGIN_LINE_MATCHER); const drawing_lines_start = full_content.indexOf(END_LINE_MATCHER); if (text_elements_start === -1 || drawing_lines_start === -1) { console.warn("Excalidraw file does not contain expected sections. File: " + this.item.key); this.item.data.last_read.size = 0; return ""; } const text_content = full_content.slice(text_elements_start + BEGIN_LINE_MATCHER.length, drawing_lines_start).trim(); const stripped_refs = text_content.split("\n").map((line) => { if (line.trim() === "%%") return ""; if (line.trim() === "#") return ""; return line.replace(/\^[a-z0-9]+$/i, "").trim(); }).filter(Boolean).join("\n"); this.item.data.last_read.size = stripped_refs.length; return stripped_refs; } get size() { if (this.item.data?.last_read?.size) { return this.item.data.last_read.size; } return this.file?.stat?.size || 0; } }; // node_modules/obsidian-smart-env/node_modules/smart-blocks/utils/get_block_display_name.js function get_block_display_name(key, show_full_path) { const [source_key, ...path_parts] = key.split("#").filter(Boolean); const source_name = get_item_display_name(source_key, show_full_path); if (show_full_path) return [source_name, ...path_parts].join(" > "); const last_heading = path_parts.findLast((part) => part && part[0] !== "{"); return [source_name, last_heading].join(" > "); } // node_modules/obsidian-smart-env/node_modules/smart-blocks/smart_block.js var SmartBlock = class extends SmartEntity { /** * Provides default values for a SmartBlock instance. * @static * @readonly * @returns {Object} The default values. */ static get defaults() { return { data: { text: null, length: 0, last_read: { hash: null, at: 0 } }, _embed_input: "" // Stored temporarily }; } get block_adapter() { if (!this._block_adapter) { this._block_adapter = new this.collection.opts.block_adapters.md(this); } return this._block_adapter; } /** * Initializes the SmartBlock instance by queuing an embed if embedding is enabled. * @returns {void} */ init() { if (this.settings.embed_blocks) super.init(); } /** * Queues the entity for embedding. * @returns {void} */ queue_embed() { this._queue_embed = this.should_embed; this.source?.queue_embed(); } /** * Queues the block for import via the source. * @returns {void} */ queue_import() { this.source?.queue_import(); } /** * Prepares the embed input for the SmartBlock by reading content and generating a hash. * @async * @returns {Promise} The embed input string or `false` if already embedded. */ async get_embed_input(content = null) { if (typeof this._embed_input !== "string" || !this._embed_input.length) { if (!content) content = await this.read(); this._embed_input = this.breadcrumbs + "\n" + content; } return this._embed_input; } // CRUD /** * @method read * @description Reads the block content by delegating to the block adapter. * @async * @returns {Promise} The block content. */ async read() { try { return await this.block_adapter.read(); } catch (e) { if (e.message.includes("BLOCK NOT FOUND")) { return 'BLOCK NOT FOUND (run "Prune" to remove)'; } else { throw e; } } } /** * @method append * @description Appends content to this block by delegating to the block adapter. * @async * @param {string} content * @returns {Promise} */ async append(content) { await this.block_adapter.append(content); this.queue_save(); } /** * @method update * @description Updates the block content by delegating to the block adapter. * @async * @param {string} new_block_content * @param {Object} [opts={}] * @returns {Promise} */ async update(new_block_content, opts = {}) { await this.block_adapter.update(new_block_content, opts); this.queue_save(); } /** * @method remove * @description Removes the block by delegating to the block adapter. * @async * @returns {Promise} */ async remove() { await this.block_adapter.remove(); this.queue_save(); } /** * @method move_to * @description Moves the block to another location by delegating to the block adapter. * @async * @param {string} to_key * @returns {Promise} */ async move_to(to_key) { await this.block_adapter.move_to(to_key); this.queue_save(); } // Getters /** * Retrieves the breadcrumbs representing the block's path within the source. * @readonly * @returns {string} The breadcrumbs string. */ get breadcrumbs() { return this.key.split("/").join(" > ").split("#").slice(0, -1).join(" > ").replace(".md", ""); } /** * Determines if the block is excluded from embedding based on headings. * @readonly * @returns {boolean} `true` if excluded, `false` otherwise. */ get excluded() { const block_headings = this.path.split("#").slice(1); if (this.source_collection.excluded_headings.some((heading) => block_headings.includes(heading))) return true; return this.source?.excluded; } /** * Retrieves the file path of the SmartSource associated with the block. * @readonly * @returns {string} The file path. */ get file_path() { return this.source?.file_path; } /** * Retrieves the file type of the SmartSource associated with the block. * @readonly * @returns {string} The file type. */ get file_type() { return this.source.file_type; } /** * Retrieves the folder path of the block. * @readonly * @returns {string} The folder path. */ get folder() { return this.path.split("/").slice(0, -1).join("/"); } /** * Retrieves the embed link for the block. * @readonly * @returns {string} The embed link. */ get embed_link() { return `![[${this.link}]]`; } /** * Determines if the block has valid line range information. * @readonly * @returns {boolean} `true` if the block has both start and end lines, `false` otherwise. */ get has_lines() { return this.lines && this.lines.length === 2; } /** * Determines if the entity is a block based on its key. * @readonly * @returns {boolean} `true` if it's a block, `false` otherwise. */ get is_block() { return this.key.includes("#"); } /** * Determines if the block is gone (i.e., the source file or block data no longer exists). * @readonly * @returns {boolean} `true` if gone, `false` otherwise. */ get is_gone() { if (!this.source?.file) return true; if (!this.source?.data?.blocks?.[this.sub_key]) return true; return false; } get last_read() { return this.data.last_read; } /** * Retrieves the sub-key of the block. * @readonly * @returns {string} The sub-key. */ get sub_key() { return "#" + this.key.split("#").slice(1).join("#"); } /** * Retrieves the lines range of the block. * @readonly * @returns {Array|undefined} An array containing the start and end lines or `undefined` if not set. */ // get lines() { return this.source?.data?.blocks?.[this.sub_key]; } get lines() { return this.data.lines; } /** * Retrieves the starting line number of the block. * @readonly * @returns {number|undefined} The starting line number or `undefined` if not set. */ get line_start() { return this.lines?.[0]; } /** * Retrieves the ending line number of the block. * @readonly * @returns {number|undefined} The ending line number or `undefined` if not set. */ get line_end() { return this.lines?.[1]; } /** * Retrieves the link associated with the block, handling page numbers if present. * @readonly * @deprecated was specific to PDFs and removed this sort of PDF handling * @returns {string} The block link. */ get link() { if (/^.*page\s*(\d+).*$/i.test(this.sub_key)) { const number = this.sub_key.match(/^.*page\s*(\d+).*$/i)[1]; return `${this.source.path}#page=${number}`; } else { return this.source?.path || "MISSING SOURCE"; } } // uses data.lines to get next block get next_block() { if (!this.data.lines) return null; const next_line = this.data.lines[1] + 1; return this.source.blocks?.find((block) => next_line === block.data?.lines?.[0]); } /** * Retrieves the paths of outlinks from the block. * @readonly * @returns {Array} An array of outlink paths. */ get outlinks() { return this.source.outlinks; } /** * Retrieves the path of the SmartBlock. * @readonly * @returns {string} The path of the SmartBlock. */ get path() { return this.key; } /** * Determines if the block should be embedded based on its coverage and size. * @readonly * @returns {boolean} `true` if it should be embedded, `false` otherwise. */ get should_embed() { try { if (this.settings?.min_chars && this.size < this.settings.min_chars) return false; const match_line_start = this.line_start + 1; const match_line_end = this.line_end; const { has_line_start, has_line_end } = Object.entries(this.source?.data?.blocks || {}).reduce((acc, [key, range]) => { if (!key.startsWith(this.sub_key + "#")) return acc; if (range[0] === match_line_start) acc.has_line_start = key; if (range[1] === match_line_end) acc.has_line_end = key; return acc; }, { has_line_start: null, has_line_end: null }); if (has_line_start && has_line_end) { const start_block = this.collection.get(this.source_key + has_line_start); if (start_block?.should_embed) { const end_block = this.collection.get(this.source_key + has_line_end); if (end_block?.should_embed) return false; } } return true; } catch (e) { console.error(e, e.stack); console.error(`Error getting should_embed for ${this.key}: ` + JSON.stringify(e || {}, null, 2)); } } /** * Retrieves the size of the SmartBlock. * @readonly * @returns {number} The size of the SmartBlock. */ get size() { return this.data.size; } /** * Retrieves the SmartSource associated with the block. * @readonly * @returns {import("smart-sources").SmartSource} The associated SmartSource instance. */ get source() { return this.source_collection.get(this.source_key); } /** * Retrieves the SmartSources collection instance. * @readonly * @returns {import("smart-sources").SmartSources} The SmartSources collection. */ get source_collection() { return this.env.smart_sources; } get source_key() { return this.key.split("#")[0]; } get sub_blocks() { return this.source?.blocks?.filter((block) => block.key.startsWith(this.key + "#") && block.line_start > this.line_start && block.line_end <= this.line_end) || []; } // source dependent get excluded_lines() { return this.source.excluded_lines; } get file() { return this.source.file; } get is_media() { return this.source.is_media; } get mtime() { return this.source.mtime; } // DEPRECATED /** * Retrieves the display name of the block. * @readonly * @returns {string} The display name. */ get name() { return get_block_display_name( this.key, this.env.settings.smart_view_filter?.show_full_path ); } /** * @deprecated Use `source` instead. Removing after 2025-09-01. * @readonly * @returns {SmartSource} The associated SmartSource instance. */ get note() { return this.source; } /** * @deprecated Use `source.key` instead. Removing after 2025-09-01. * @readonly * @returns {string} The source key. */ get note_key() { return this.key.split("#")[0]; } }; var smart_block_default = { class: SmartBlock, actions: { find_connections } }; // node_modules/obsidian-smart-env/node_modules/smart-blocks/smart_blocks.js var SmartBlocks = class extends SmartEntities { /** * Initializes the SmartBlocks instance. Currently muted as processing is handled by SmartSources. * @returns {void} */ init() { } get fs() { return this.env.smart_sources.fs; } /** * Retrieves the embedding model associated with the SmartSources collection. * @readonly * @returns {Object|undefined} The embedding model instance or `undefined` if not set. */ get embed_model() { return this.source_collection?.embed_model; } /** * Retrieves the embedding model key from the SmartSources collection. * @readonly * @returns {string|undefined} The embedding model key or `undefined` if not set. */ get embed_model_key() { return this.source_collection?.embed_model_key; } /** * Calculates the expected number of blocks based on the SmartSources collection. * @readonly * @returns {number} The expected count of blocks. */ get expected_blocks_ct() { return Object.values(this.source_collection.items).reduce((acc, item) => acc += Object.keys(item.data.blocks || {}).length, 0); } /** * Retrieves the notices system from the environment. * @readonly * @returns {Object} The notices object. */ get notices() { return this.env.smart_connections_plugin?.notices || this.env.main?.notices; } /** * Retrieves the settings configuration for SmartBlocks. * @readonly * @returns {Object} The settings configuration object. */ get settings_config() { return this.process_settings_config({ "embed_blocks": { name: "Embed blocks", type: "toggle", description: "Blocks represent parts/sections of notes. Get more granular results.", default: true }, ...super.settings_config }); } render_settings(container, opts = {}) { return this.render_collection_settings(container, opts); } get data_dir() { return "multi"; } /** * Retrieves the SmartSources collection instance. * @readonly * @returns {SmartSources} The SmartSources collection. */ get source_collection() { return this.env.smart_sources; } /** * Processes the embed queue. Currently handled by SmartSources, so this method is muted. * @async * @returns {Promise} */ async process_embed_queue() { } /** * Processes the load queue. Currently muted as processing is handled by SmartSources. * @async * @returns {Promise} */ async process_load_queue() { } // TEMP: Methods in sources not implemented in blocks /** * @async * @abstract * @throws {Error} Throws an error indicating the method is not implemented. * @returns {Promise} */ async prune() { throw "Not implemented: prune"; } /** * @throws {Error} Throws an error indicating the method is not implemented. * @abstract * @returns {void} */ build_links_map() { throw "Not implemented: build_links_map"; } /** * @async * @abstract * @throws {Error} Throws an error indicating the method is not implemented. * @returns {Promise} */ async refresh() { throw "Not implemented: refresh"; } /** * @async * @abstract * @throws {Error} Throws an error indicating the method is not implemented. * @returns {Promise} */ async search() { throw "Not implemented: search"; } /** * @async * @abstract * @throws {Error} Throws an error indicating the method is not implemented. * @returns {Promise} */ async run_refresh() { throw "Not implemented: run_refresh"; } /** * @async * @abstract * @throws {Error} Throws an error indicating the method is not implemented. * @returns {Promise} */ async run_force_refresh() { throw "Not implemented: run_force_refresh"; } // clear expired blocks // TODO/future: replaced by storing block data within source data async cleanup_blocks() { const expired_blocks = Object.values(this.items).filter((i) => i.is_gone); console.log(`Removing ${expired_blocks.length} expired blocks`); expired_blocks.forEach((i) => i.delete()); await this.process_save_queue(); expired_blocks.forEach((i) => { delete this.items[i.key]; }); this.emit_event("blocks:cleaned", { expired_blocks_ct: expired_blocks.length }); } }; // node_modules/obsidian-smart-env/node_modules/smart-blocks/adapters/data/ajson_multi_file.js var AjsonMultiFileBlocksDataAdapter = class extends AjsonMultiFileCollectionDataAdapter { ItemDataAdapter = AjsonMultiFileBlockDataAdapter; /** * Transforms the item key into a safe filename. * Replaces spaces, slashes, and dots with underscores. * @returns {string} safe file name */ // get_data_file_name(key) { // return super.get_data_file_name(key.split('#')[0]); // } get_data_file_name(key) { return key.split("#")[0].replace(/[\s\/\.]/g, "_").replace(".md", ""); } /** * Process any queued save operations. * @async * @returns {Promise} */ async process_save_queue() { this.collection.emit_event("collection:save_started"); this.collection.show_process_notice("saving_collection"); const save_queue = Object.values(this.collection.items).filter((item) => item._queue_save); console.log(`Saving ${this.collection.collection_key}: ${save_queue.length} items`); const time_start = Date.now(); const save_files = Object.entries(save_queue.reduce((acc, item) => { const file_name = this.get_item_data_path(item.key); acc[file_name] = acc[file_name] || []; acc[file_name].push(item); return acc; }, {})); for (let i = 0; i < save_files.length; i++) { const [file_name, items] = save_files[i]; await this.fs.append( file_name, items.map((item) => this.get_item_ajson(item)).join("\n") + "\n" ); items.forEach((item) => item._queue_save = false); } console.log(`Saved ${this.collection.collection_key} in ${Date.now() - time_start}ms`); this.collection.clear_process_notice("saving_collection"); this.collection.emit_event("collection:save_completed"); } process_load_queue() { console.log(`Skipping loading ${this.collection.collection_key}...`); } }; var AjsonMultiFileBlockDataAdapter = class extends AjsonMultiFileItemDataAdapter { }; // node_modules/obsidian-smart-env/node_modules/smart-blocks/adapters/_adapter.js var BlockContentAdapter = class { /** * @constructor * @param {Object} item - The SmartBlock instance this adapter operates on. * The `item` should at least provide `data` and references to its parent source. */ constructor(item) { this.item = item; } /** * @async * @method read * @abstract * @returns {Promise} The content of the block. * @throws {Error} If not implemented by subclass. */ async read() { throw new Error("Not implemented"); } /** * @async * @method append * @abstract * @param {string} content Content to append to the block. * @returns {Promise} * @throws {Error} If not implemented by subclass. */ async append(content) { throw new Error("Not implemented"); } /** * @async * @method update * @abstract * @param {string} new_content The new content for the block. * @param {Object} [opts={}] Additional update options. * @returns {Promise} * @throws {Error} If not implemented by subclass. */ async update(new_content, opts = {}) { throw new Error("Not implemented"); } /** * @async * @method remove * @abstract * @returns {Promise} * @throws {Error} If not implemented by subclass. */ async remove() { throw new Error("Not implemented"); } /** * @async * @method move_to * @abstract * @param {string} to_key The destination key (source or block reference). * @returns {Promise} * @throws {Error} If not implemented by subclass. */ async move_to(to_key) { throw new Error("Not implemented"); } /** * @name data * @type {Object} * @readonly * @description Access the block’s data object. Useful for updating metadata like line references or hashes. */ get data() { return this.item.data; } /** * @async * @method update_last_read * @param {string} content The current content of the block. * @returns {Promise} * @description Update the block’s `last_read` hash and timestamp based on the given content. */ async update_last_read(content) { this.data.last_read = { hash: this.create_hash(content), at: Date.now() }; } /** * @method create_hash * @param {string} content The content to hash. * @returns {Promise} The computed hash of the content. * @description Hash the block content to detect changes and prevent unnecessary re-embeddings. */ create_hash(content) { return murmur_hash_32_alphanumeric(content); } }; // node_modules/obsidian-smart-env/node_modules/smart-sources/utils/get_line_range.js function get_line_range2(content, start_line, end_line) { const lines = content.split("\n"); return lines.slice(start_line - 1, end_line).join("\n"); } // node_modules/obsidian-smart-env/node_modules/smart-blocks/adapters/markdown_block.js var MarkdownBlockContentAdapter = class extends BlockContentAdapter { /** * Read the content of the block. * @async * @returns {Promise} The block content as a string. * @throws {Error} If the block cannot be found. */ async read() { const source_content = await this.item.source?.read(); if (!source_content) { console.warn(`BLOCK NOT FOUND: ${this.item.key} has no source content.`); return ""; } const content = this._extract_block(source_content); this.update_last_read(content); return content; } /** * Append content to the existing block. * This method inserts additional lines after the block's end, then re-parses the file to update line references. * @async * @param {string} content Content to append to the block. * @returns {Promise} * @throws {Error} If the block cannot be found. */ async append(content) { let full_content = await this.item.source.read(); const { line_start, line_end } = this.item; if (!line_start || !line_end) { throw new Error(`Cannot append to block ${this.item.key}: invalid line references.`); } const lines = full_content.split("\n"); lines.splice(line_end, 0, "", content); const updated_content = lines.join("\n"); await this.item.source._update(updated_content); await this._reparse_source(); } /** * Update the block with new content, replacing its current lines. * @async * @param {string} new_content New content for the block. * @param {Object} [opts={}] Additional options. * @returns {Promise} * @throws {Error} If the block cannot be found. */ async update(new_content, opts = {}) { let full_content = await this.item.source.read(); const { line_start, line_end } = this.item; if (!line_start || !line_end) { throw new Error(`Cannot update block ${this.item.key}: invalid line references.`); } const lines = full_content.split("\n"); const updated_lines = [ ...lines.slice(0, line_start - 1), ...new_content.split("\n"), ...lines.slice(line_end) ]; const updated_content = updated_lines.join("\n"); await this.item.source._update(updated_content); await this._reparse_source(); } /** * Remove the block entirely from the source. * @async * @returns {Promise} * @throws {Error} If the block cannot be found. */ async remove() { let full_content = await this.item.source.read(); const { line_start, line_end } = this.item; if (!line_start || !line_end) { throw new Error(`Cannot remove block ${this.item.key}: invalid line references.`); } const lines = full_content.split("\n"); const updated_lines = [ ...lines.slice(0, line_start - 1), ...lines.slice(line_end) ]; const updated_content = updated_lines.join("\n"); await this.item.source._update(updated_content); await this._reparse_source(); } /** * Move the block to a new location (another source or heading). * This involves reading the block content, removing it from the current source, and appending it to the target. * @async * @param {string} to_key The destination path or entity reference. * @returns {Promise} * @throws {Error} If the block or target is invalid. */ async move_to(to_key) { const content = await this.read(); await this.remove(); const is_block_ref = to_key.includes("#"); let target_source_key = is_block_ref ? to_key.split("#")[0] : to_key; const target_source = this.item.env.smart_sources.get(target_source_key); if (!target_source) { await this.item.env.smart_sources.create(target_source_key, content); return; } if (is_block_ref) { const target_block = this.item.env.smart_blocks.get(to_key); if (target_block) { await target_block.append(content); } else { await target_source.append(content); } } else { await target_source.append(content); } } /** * Extract the block content using current line references from a full source content. * @private * @param {string} source_content Full source file content. * @returns {string} Extracted block content. * @throws {Error} If the block cannot be found. */ _extract_block(source_content) { if (!source_content) { console.warn(`BLOCK NOT FOUND: ${this.item.key} has no source content.`); return ""; } const { line_start, line_end } = this.item; if (!line_start || !line_end) { throw new Error(`BLOCK NOT FOUND: ${this.item.key} has invalid line references.`); } return get_line_range2(source_content, line_start, line_end); } /** * Re-parse the source file after a CRUD operation to update line references for all blocks. * @private * @async * @returns {Promise} */ async _reparse_source() { await this.item.source.import(); } }; // node_modules/obsidian-smart-env/node_modules/smart-model/smart_model.js var SmartModel = class { scope_name = "smart_model"; static defaults = { // override in sub-class if needed }; /** * Create a SmartModel instance. * @param {Object} opts - Configuration options * @param {Object} opts.adapters - Map of adapter names to adapter classes * @param {Object} opts.settings - Model settings configuration * @param {string} [opts.model_key] - Optional model identifier to override settings * @throws {Error} If required options are missing */ constructor(opts = {}) { this.opts = opts; this.validate_opts(opts); this.state = "unloaded"; this._adapter = null; this.data = opts; } /** * Initialize the model by loading the configured adapter. * @async * @returns {Promise} */ async initialize() { this.load_adapter(this.adapter_name); await this.load(); } /** * Validate required options. * @param {Object} opts - Configuration options */ validate_opts(opts) { if (!opts.adapters) throw new Error("opts.adapters is required"); if (!opts.settings) throw new Error("opts.settings is required"); } /** * Get the current settings * @returns {Object} Current settings */ get settings() { if (!this.opts.settings) this.opts.settings = { ...this.constructor.defaults }; return this.opts.settings; } /** * Get the current adapter name * @returns {string} Current adapter name */ get adapter_name() { let adapter_key = this.opts.adapter || this.settings.adapter || Object.keys(this.adapters)[0]; if (!adapter_key || !this.adapters[adapter_key]) { console.warn(`Platform "${adapter_key}" not supported`); adapter_key = Object.keys(this.adapters)[0]; } return adapter_key; } /** * Get available models. * @returns {Object} Map of model objects */ get models() { return this.adapter.models; } /** * Get default model key. * @returns {string} Default model key */ get default_model_key() { return this.adapter.constructor.defaults.default_model; } /** * Get the current model key * @returns {string} Current model key */ get model_key() { return this.opts.model_key || this.settings.model_key || this.default_model_key; } /** * Load the current adapter and transition to loaded state. * @async * @returns {Promise} */ async load() { this.set_state("loading"); try { if (!this.adapter?.is_loaded) { await this.invoke_adapter_method("load"); } } catch (err) { this.set_state("unloaded"); if (!this.reload_model_timeout) { this.reload_model_timeout = setTimeout(async () => { this.reload_model_timeout = null; await this.load(); this.set_state("loaded"); this.env?.events?.emit("model:loaded", { model_key: this.model_key }); this.notices?.show("Loaded model: " + this.model_key); }, 6e4); } throw new Error(`Failed to load model: ${err.message}`); } this.set_state("loaded"); } /** * Unload the current adapter and transition to unloaded state. * @async * @returns {Promise} */ async unload() { if (this.adapter?.is_loaded) { this.set_state("unloading"); await this.invoke_adapter_method("unload"); this.set_state("unloaded"); } } /** * Set the model's state. * @param {('unloaded'|'loading'|'loaded'|'unloading')} new_state - The new state * @throws {Error} If the state is invalid */ set_state(new_state) { const valid_states = ["unloaded", "loading", "loaded", "unloading"]; if (!valid_states.includes(new_state)) { throw new Error(`Invalid state: ${new_state}`); } this.state = new_state; } get is_loading() { return this.state === "loading"; } get is_loaded() { return this.state === "loaded"; } get is_unloading() { return this.state === "unloading"; } get is_unloaded() { return this.state === "unloaded"; } // ADAPTERS /** * Get the map of available adapters * @returns {Object} Map of adapter names to adapter classes */ get adapters() { return this.opts.adapters || {}; } /** * Load a specific adapter by name. * @async * @param {string} adapter_name - Name of the adapter to load * @throws {Error} If adapter not found or loading fails * @returns {Promise} */ async load_adapter(adapter_name) { this.set_adapter(adapter_name); if (!this._adapter.loaded) { this.set_state("loading"); try { await this.invoke_adapter_method("load"); this.set_state("loaded"); } catch (err) { this.set_state("unloaded"); throw new Error(`Failed to load adapter: ${err.message}`); } } } /** * Set an adapter instance by name without loading it. * @param {string} adapter_name - Name of the adapter to set * @throws {Error} If adapter not found */ set_adapter(adapter_name) { const AdapterClass = this.adapters[adapter_name]; if (!AdapterClass) { throw new Error(`Adapter "${adapter_name}" not found.`); } if (this._adapter?.constructor.name.toLowerCase() === adapter_name.toLowerCase()) { return; } this._adapter = new AdapterClass(this); } /** * Get the current active adapter instance * @returns {Object} The active adapter instance * @throws {Error} If adapter not found */ get adapter() { const adapter_name = this.adapter_name; if (!adapter_name) { throw new Error(`Adapter not set for model.`); } if (!this._adapter) { this.load_adapter(adapter_name); } return this._adapter; } /** * Ensure the adapter is ready to execute a method. * @param {string} method - Name of the method to check * @throws {Error} If adapter not loaded or method not implemented */ ensure_adapter_ready(method) { if (!this.adapter) { throw new Error("No adapter loaded."); } if (typeof this.adapter[method] !== "function") { throw new Error(`Adapter does not implement method: ${method}`); } } /** * Invoke a method on the current adapter. * @async * @param {string} method - Name of the method to call * @param {...any} args - Arguments to pass to the method * @returns {Promise} Result from the adapter method * @throws {Error} If adapter not ready or method fails */ async invoke_adapter_method(method, ...args) { this.ensure_adapter_ready(method); return await this.adapter[method](...args); } /** * Get platforms as dropdown options. * @returns {Array} Array of {value, name} option objects */ get_platforms_as_options() { return Object.entries(this.adapters).map(([key, AdapterClass]) => ({ value: key, name: AdapterClass.defaults.description || key })); } // SETTINGS /** * Get the settings configuration schema * @returns {Object} Settings configuration object */ get settings_config() { return this.process_settings_config({ adapter: { name: "Model Platform", type: "dropdown", description: "Select a model platform to use with Smart Model.", options_callback: "get_platforms_as_options", is_scope: true, // trigger re-render of settings when changed callback: "adapter_changed", default: "default" } }); } /** * Process settings configuration with conditionals and prefixes. * @param {Object} _settings_config - Raw settings configuration * @param {string} [prefix] - Optional prefix for setting keys * @returns {Object} Processed settings configuration */ process_settings_config(_settings_config, prefix = null) { return Object.entries(_settings_config).reduce((acc, [key, val]) => { const new_key = (prefix ? prefix + "." : "") + this.process_setting_key(key); acc[new_key] = val; return acc; }, {}); } /** * Process an individual setting key. * Example: replace placeholders with actual adapter names. * @param {string} key - The setting key with placeholders. * @returns {string} Processed setting key. */ process_setting_key(key) { return key.replace(/\[ADAPTER\]/g, this.adapter_name); } re_render_settings() { if (typeof this.opts.re_render_settings === "function") this.opts.re_render_settings(); else console.warn("re_render_settings is not a function (must be passed in model opts)"); } /** * Reload model. */ reload_model() { if (typeof this.opts.reload_model === "function") this.opts.reload_model(); else console.warn("reload_model is not a function (must be passed in model opts)"); } adapter_changed() { this.reload_model(); this.re_render_settings(); } model_changed() { this.reload_model(); this.re_render_settings(); } }; // node_modules/obsidian-smart-env/node_modules/smart-embed-model/smart_embed_model.js var SmartEmbedModel = class extends SmartModel { scope_name = "smart_embed_model"; static defaults = { adapter: "transformers" }; /** * Create a SmartEmbedModel instance * @param {Object} opts - Configuration options * @param {Object} [opts.adapters] - Map of available adapter implementations * @param {number} [opts.batch_size] - Default batch size for processing * @param {Object} [opts.settings] - User settings * @param {string} [opts.settings.api_key] - API key for remote models * @param {number} [opts.settings.min_chars] - Minimum text length to embed */ constructor(opts = {}) { super(opts); } /** * Count tokens in an input string * @param {string} input - Text to tokenize * @returns {Promise} Token count result * @property {number} tokens - Number of tokens in input * * @example * ```javascript * const result = await model.count_tokens("Hello world"); * console.log(result.tokens); // 2 * ``` */ async count_tokens(input) { return await this.invoke_adapter_method("count_tokens", input); } /** * Generate embeddings for a single input * @param {string|Object} input - Text or object with embed_input property * @returns {Promise} Embedding result * @property {number[]} vec - Embedding vector * @property {number} tokens - Token count * * @example * ```javascript * const result = await model.embed("Hello world"); * console.log(result.vec); // [0.1, 0.2, ...] * ``` */ async embed(input) { if (typeof input === "string") input = { embed_input: input }; return (await this.embed_batch([input]))[0]; } /** * Generate embeddings for multiple inputs in batch * @param {Array} inputs - Array of texts or objects with embed_input * @returns {Promise>} Array of embedding results * @property {number[]} vec - Embedding vector for each input * @property {number} tokens - Token count for each input * * @example * ```javascript * const results = await model.embed_batch([ * { embed_input: "First text" }, * { embed_input: "Second text" } * ]); * ``` */ async embed_batch(inputs) { return await this.invoke_adapter_method("embed_batch", inputs); } /** * Get the current batch size based on GPU settings * @returns {number} Current batch size for processing */ get batch_size() { return this.adapter.batch_size || 1; } /** * Get settings configuration schema * @returns {Object} Settings configuration object */ get settings_config() { const _settings_config = { adapter: { name: "Embedding model platform", type: "dropdown", description: "Select an embedding model platform. The default 'transformers' utilizes built-in local models.", options_callback: "get_platforms_as_options", callback: "adapter_changed", default: this.constructor.defaults.adapter }, ...this.adapter.settings_config || {} }; return this.process_settings_config(_settings_config); } process_setting_key(key) { return key.replace(/\[ADAPTER\]/g, this.adapter_name); } /** * Get available embedding model options * @returns {Array} Array of model options with value and name */ get_embedding_model_options() { return Object.entries(this.models).map(([key, model]) => ({ value: key, name: key })); } // /** // * Get embedding model options including 'None' option // * @returns {Array} Array of model options with value and name // */ // get_block_embedding_model_options() { // const options = this.get_embedding_model_options(); // options.unshift({ value: 'None', name: 'None' }); // return options; // } }; // node_modules/obsidian-smart-env/node_modules/smart-model/adapters/_adapter.js var SmartModelAdapter = class { /** * Create a SmartModelAdapter instance. * @param {SmartModel} model - The parent SmartModel instance */ constructor(model) { this.model = model; this.state = "unloaded"; } /** * Load the adapter. * @async * @returns {Promise} */ async load() { this.set_state("loaded"); } /** * Unload the adapter. * @returns {void} */ unload() { this.set_state("unloaded"); } /** * Get all settings. * @returns {Object} All settings */ get settings() { return this.model.settings; } /** * Get the current model key. * @returns {string} Current model identifier */ get model_key() { return this.model.model_key; } /** * Get the models. * @returns {Object} Map of model objects */ get models() { const models = this.model.data.provider_models; if (typeof models === "object" && Object.keys(models || {}).length > 0) return models; else { return {}; } } /** * Get available models from the API. * @abstract * @param {boolean} [refresh=false] - Whether to refresh cached models * @returns {Promise} Map of model objects */ async get_models(refresh = false) { throw new Error("get_models not implemented"); } /** * Get available models as dropdown options synchronously. * @returns {Array} Array of model options. */ get_models_as_options() { const models = this.models; if (!Object.keys(models || {}).length) { this.get_models(true); return [{ value: "", name: "No models currently available" }]; } return Object.entries(models).map(([id, model]) => ({ value: id, name: model.name || id })).sort((a, b) => a.name.localeCompare(b.name)); } /** * Set the adapter's state. * @deprecated should be handled in SmartModel (only handle once) * @param {('unloaded'|'loading'|'loaded'|'unloading')} new_state - The new state * @throws {Error} If the state is invalid */ set_state(new_state) { const valid_states = ["unloaded", "loading", "loaded", "unloading"]; if (!valid_states.includes(new_state)) { throw new Error(`Invalid state: ${new_state}`); } this.state = new_state; } // Replace individual state getters/setters with a unified state management get is_loading() { return this.state === "loading"; } get is_loaded() { return this.state === "loaded"; } get is_unloading() { return this.state === "unloading"; } get is_unloaded() { return this.state === "unloaded"; } }; // node_modules/obsidian-smart-env/node_modules/smart-embed-model/adapters/_adapter.js var SmartEmbedAdapter = class extends SmartModelAdapter { /** * @override in sub-class with adapter-specific default configurations * @property {string} id - The adapter identifier * @property {string} description - Human-readable description * @property {string} type - Adapter type ("API") * @property {string} endpoint - API endpoint * @property {string} adapter - Adapter identifier * @property {string} default_model - Default model to use */ static defaults = {}; /** * Count tokens in input text * @abstract * @param {string} input - Text to tokenize * @returns {Promise} Token count result * @property {number} tokens - Number of tokens in input * @throws {Error} If not implemented by subclass */ async count_tokens(input) { throw new Error("count_tokens method not implemented"); } /** * Generate embeddings for single input * @abstract * @param {string|Object} input - Text to embed * @returns {Promise} Embedding result * @property {number[]} vec - Embedding vector * @property {number} tokens - Number of tokens in input * @throws {Error} If not implemented by subclass */ async embed(input) { if (typeof input === "string") input = { embed_input: input }; return (await this.embed_batch([input]))[0]; } /** * Generate embeddings for multiple inputs * @abstract * @param {Array} inputs - Texts to embed * @returns {Promise>} Array of embedding results * @property {number[]} vec - Embedding vector for each input * @property {number} tokens - Number of tokens in each input * @throws {Error} If not implemented by subclass */ async embed_batch(inputs) { throw new Error("embed_batch method not implemented"); } get settings_config() { return { "[ADAPTER].model_key": { name: "Embedding model", type: "dropdown", description: "Select an embedding model.", options_callback: "adapter.get_models_as_options", callback: "model_changed", default: this.constructor.defaults.default_model } }; } get dims() { return this.model.data.dims; } get max_tokens() { return this.model.data.max_tokens; } get batch_size() { return this.model.data.batch_size || 1; } }; // node_modules/obsidian-smart-env/node_modules/smart-http-request/smart_http_request.js var SmartHttpRequest = class { /** * @param {object} opts - Options for the SmartHttpRequest class * @param {SmartHttpRequestAdapter} opts.adapter - The adapter constructor to use for making HTTP requests * @param {Obsidian.requestUrl} opts.obsidian_request_adapter - For use with Obsidian adapter */ constructor(opts = {}) { this.opts = opts; if (!opts.adapter) throw new Error("HttpRequestAdapter is required"); this.adapter = new opts.adapter(this); } /** * Returns a well-formed response object * @param {object} request_params - Parameters for the HTTP request * @param {string} request_params.url - The URL to make the request to * @param {string} [request_params.method='GET'] - The HTTP method to use * @param {object} [request_params.headers] - Headers to include in the request * @param {*} [request_params.body] - The body of the request (for POST, PUT, etc.) * @returns {SmartHttpResponseAdapter} instance of the SmartHttpResponseAdapter class * @example * const response = await smart_http_request.request({ * url: 'https://api.example.com/data', * method: 'GET', * headers: { 'Content-Type': 'application/json' } * }); * console.log(await response.json()); */ async request(request_params, throw_on_error = false) { return await this.adapter.request(request_params, throw_on_error); } }; // node_modules/obsidian-smart-env/node_modules/smart-http-request/adapters/_adapter.js var SmartHttpRequestAdapter = class { constructor(main) { this.main = main; } /** * Execute an HTTP request using adapter-specific transport. * @abstract * @param {Object} request_params - Parameters for the outbound request. * @returns {Promise} Adapter-specific response wrapper. */ async request(request_params) { throw new Error("request not implemented"); } }; var SmartHttpResponseAdapter = class { constructor(response) { this.response = response; } /** * Retrieve response headers. * @abstract * @returns {Promise} Headers object for the response. */ async headers() { throw new Error("headers not implemented"); } /** * Parse the response body as JSON. * @abstract * @returns {Promise<*>} Parsed JSON payload. */ async json() { throw new Error("json not implemented"); } /** * Get the HTTP status code. * @abstract * @returns {Promise} Response status code. */ async status() { throw new Error("status not implemented"); } /** * Read the raw text body. * @abstract * @returns {Promise} Response body as text. */ async text() { throw new Error("text not implemented"); } }; // node_modules/obsidian-smart-env/node_modules/smart-http-request/adapters/obsidian.js var SmartHttpObsidianRequestAdapter = class extends SmartHttpRequestAdapter { async request(request_params, throw_on_error = false) { let response; try { if (!this.main.opts.obsidian_request_url) { throw new Error("obsidian_request_url is required in SmartHttp constructor opts"); } response = await this.main.opts.obsidian_request_url({ ...request_params, throw: throw_on_error }); if (throw_on_error && response.status === 400) throw new Error("Obsidian request failed"); return new SmartHttpObsidianResponseAdapter(response); } catch (error) { console.error("Error in SmartHttpObsidianRequestAdapter.request():"); console.error(JSON.stringify(request_params, null, 2)); console.error(response); console.error(error); return null; } } }; var SmartHttpObsidianResponseAdapter = class extends SmartHttpResponseAdapter { async status() { return this.response.status; } async json() { return await this.response.json; } async text() { return await this.response.text; } async headers() { return this.response.headers; } }; // node_modules/obsidian-smart-env/node_modules/smart-http-request/adapters/fetch.js var SmartHttpRequestFetchAdapter = class extends SmartHttpRequestAdapter { async request(request_params) { const { url, ...opts } = request_params; const resp = await fetch(url, opts); return new SmartHttpResponseFetchAdapter(resp); } }; var SmartHttpResponseFetchAdapter = class extends SmartHttpResponseAdapter { async headers() { return this.response.headers; } async json() { if (!this._json) { this._json = await this.response.json(); } return this._json; } async status() { return this.response.status; } async text() { if (!this._text) { this._text = await this.response.text(); } return this._text; } }; // node_modules/obsidian-smart-env/node_modules/js-tiktoken/dist/chunk-ZDNLBERF.js var import_base64_js = __toESM(require_base64_js(), 1); var __defProp2 = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => { __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); return value; }; function bytePairMerge(piece, ranks) { let parts = Array.from( { length: piece.length }, (_, i) => ({ start: i, end: i + 1 }) ); while (parts.length > 1) { let minRank = null; for (let i = 0; i < parts.length - 1; i++) { const slice = piece.slice(parts[i].start, parts[i + 1].end); const rank = ranks.get(slice.join(",")); if (rank == null) continue; if (minRank == null || rank < minRank[0]) { minRank = [rank, i]; } } if (minRank != null) { const i = minRank[1]; parts[i] = { start: parts[i].start, end: parts[i + 1].end }; parts.splice(i + 1, 1); } else { break; } } return parts; } function bytePairEncode(piece, ranks) { if (piece.length === 1) return [ranks.get(piece.join(","))]; return bytePairMerge(piece, ranks).map((p) => ranks.get(piece.slice(p.start, p.end).join(","))).filter((x) => x != null); } function escapeRegex(str) { return str.replace(/[\\^$*+?.()|[\]{}]/g, "\\$&"); } var _Tiktoken = class { /** @internal */ specialTokens; /** @internal */ inverseSpecialTokens; /** @internal */ patStr; /** @internal */ textEncoder = new TextEncoder(); /** @internal */ textDecoder = new TextDecoder("utf-8"); /** @internal */ rankMap = /* @__PURE__ */ new Map(); /** @internal */ textMap = /* @__PURE__ */ new Map(); constructor(ranks, extendedSpecialTokens) { this.patStr = ranks.pat_str; const uncompressed = ranks.bpe_ranks.split("\n").filter(Boolean).reduce((memo, x) => { const [_, offsetStr, ...tokens] = x.split(" "); const offset = Number.parseInt(offsetStr, 10); tokens.forEach((token, i) => memo[token] = offset + i); return memo; }, {}); for (const [token, rank] of Object.entries(uncompressed)) { const bytes = import_base64_js.default.toByteArray(token); this.rankMap.set(bytes.join(","), rank); this.textMap.set(rank, bytes); } this.specialTokens = { ...ranks.special_tokens, ...extendedSpecialTokens }; this.inverseSpecialTokens = Object.entries(this.specialTokens).reduce((memo, [text, rank]) => { memo[rank] = this.textEncoder.encode(text); return memo; }, {}); } encode(text, allowedSpecial = [], disallowedSpecial = "all") { const regexes = new RegExp(this.patStr, "ug"); const specialRegex = _Tiktoken.specialTokenRegex( Object.keys(this.specialTokens) ); const ret = []; const allowedSpecialSet = new Set( allowedSpecial === "all" ? Object.keys(this.specialTokens) : allowedSpecial ); const disallowedSpecialSet = new Set( disallowedSpecial === "all" ? Object.keys(this.specialTokens).filter( (x) => !allowedSpecialSet.has(x) ) : disallowedSpecial ); if (disallowedSpecialSet.size > 0) { const disallowedSpecialRegex = _Tiktoken.specialTokenRegex([ ...disallowedSpecialSet ]); const specialMatch = text.match(disallowedSpecialRegex); if (specialMatch != null) { throw new Error( `The text contains a special token that is not allowed: ${specialMatch[0]}` ); } } let start = 0; while (true) { let nextSpecial = null; let startFind = start; while (true) { specialRegex.lastIndex = startFind; nextSpecial = specialRegex.exec(text); if (nextSpecial == null || allowedSpecialSet.has(nextSpecial[0])) break; startFind = nextSpecial.index + 1; } const end = nextSpecial?.index ?? text.length; for (const match of text.substring(start, end).matchAll(regexes)) { const piece = this.textEncoder.encode(match[0]); const token2 = this.rankMap.get(piece.join(",")); if (token2 != null) { ret.push(token2); continue; } ret.push(...bytePairEncode(piece, this.rankMap)); } if (nextSpecial == null) break; let token = this.specialTokens[nextSpecial[0]]; ret.push(token); start = nextSpecial.index + nextSpecial[0].length; } return ret; } decode(tokens) { const res = []; let length = 0; for (let i2 = 0; i2 < tokens.length; ++i2) { const token = tokens[i2]; const bytes = this.textMap.get(token) ?? this.inverseSpecialTokens[token]; if (bytes != null) { res.push(bytes); length += bytes.length; } } const mergedArray = new Uint8Array(length); let i = 0; for (const bytes of res) { mergedArray.set(bytes, i); i += bytes.length; } return this.textDecoder.decode(mergedArray); } }; var Tiktoken = _Tiktoken; __publicField(Tiktoken, "specialTokenRegex", (tokens) => { return new RegExp(tokens.map((i) => escapeRegex(i)).join("|"), "g"); }); // node_modules/obsidian-smart-env/node_modules/smart-embed-model/utils/fetch_cache.js async function fetch_json_cached(url, cache_key = url) { const is_browser = typeof window !== "undefined" && typeof window.document !== "undefined"; if (is_browser) { const cached_text = window.localStorage.getItem(cache_key); if (cached_text) return JSON.parse(cached_text); const remote2 = await do_fetch(url); window.localStorage.setItem(cache_key, JSON.stringify(remote2)); return remote2; } const fs = await import("node:fs/promises"); const path = await import("node:path"); const os = await import("node:os"); const cache_dir = path.join(os.homedir(), ".cache", "smart-embed-model"); const cache_file = path.join(cache_dir, cache_key); try { const txt = await fs.readFile(cache_file, "utf8"); return JSON.parse(txt); } catch { } const remote = await do_fetch(url); await fs.mkdir(cache_dir, { recursive: true }); await fs.writeFile(cache_file, JSON.stringify(remote), "utf8"); return remote; } async function do_fetch(url) { const resp = await fetch(url); if (!resp.ok) throw new Error(`failed to download ${url} \u2013 ${resp.status}`); return await resp.json(); } // node_modules/obsidian-smart-env/node_modules/smart-utils/normalize_error.js function is_json_compatible(value) { if (value === null) { return true; } const type = typeof value; if (type === "string" || type === "number" || type === "boolean") { return true; } if (Array.isArray(value)) { return value.every(is_json_compatible); } if (type === "object") { const obj = ( /** @type {Record} */ value ); return Object.values(obj).every(is_json_compatible); } return false; } function extract_json_details(source, exclude_keys) { const details = {}; for (const [key, value] of Object.entries(source)) { if (exclude_keys.includes(key)) { continue; } if (!is_json_compatible(value)) { continue; } details[key] = value; } return details; } function is_empty_object(obj) { return Object.keys(obj).length === 0; } function merge_details(first, second) { if (is_empty_object(first)) { return second; } if (is_empty_object(second)) { return first; } return { ...first, ...second }; } function get_message_from_object(value) { const raw = value.message; if (typeof raw === "string") { const trimmed = raw.trim(); if (trimmed.length > 0) { return trimmed; } } return null; } function normalize_error(error, http_status = null) { if (Array.isArray(error) && error.length > 0) { return normalize_error(error[0], http_status); } if (error == null) { return { message: "Unknown error", details: null, http_status }; } if (typeof error === "string") { return { message: error, details: null, http_status }; } if (error instanceof Error) { const message = (error.message || "").trim() || "Unknown error"; const extra_details = extract_json_details( /** @type {Record} */ error, ["message"] ); return { message, details: is_empty_object(extra_details) ? null : extra_details, http_status }; } if (typeof error === "object") { const obj = ( /** @type {Record} */ error ); if ("error" in obj && obj.error != null) { const nested_error = obj.error; if (typeof nested_error === "object") { const nested_obj = ( /** @type {Record} */ nested_error ); const nested_message = get_message_from_object(nested_obj); const nested_details = extract_json_details(nested_obj, ["message"]); const outer_details = extract_json_details(obj, ["message", "error"]); const combined_details = merge_details(outer_details, nested_details); const message = nested_message || get_message_from_object(obj) || "Unknown error"; return { message, details: is_empty_object(combined_details) ? null : combined_details, http_status }; } return normalize_error(nested_error); } const object_message = get_message_from_object(obj); if (object_message) { const details = extract_json_details(obj, ["message"]); return { message: object_message, details: is_empty_object(details) ? null : details, http_status }; } } return { message: "Unknown error", details: null, http_status }; } // node_modules/obsidian-smart-env/node_modules/smart-embed-model/adapters/_api.js var CL100K_URL = "https://raw.githubusercontent.com/brianpetro/jsbrains/refs/heads/main/smart-embed-model/cl100k_base.json"; var SmartEmbedModelApiAdapter = class extends SmartEmbedAdapter { /** * Get the request adapter class. * @returns {SmartEmbedModelRequestAdapter} The request adapter class */ get req_adapter() { return SmartEmbedModelRequestAdapter; } /** * Get the response adapter class. * @returns {SmartEmbedModelResponseAdapter} The response adapter class */ get res_adapter() { return SmartEmbedModelResponseAdapter; } /** @returns {string} API endpoint URL */ get endpoint() { return this.model.data.endpoint; } /** * Get HTTP request adapter instance * @returns {SmartHttpRequest} HTTP request handler */ get http_adapter() { if (!this._http_adapter) { if (this.model.opts.http_adapter) this._http_adapter = this.model.opts.http_adapter; else this._http_adapter = new SmartHttpRequest({ adapter: SmartHttpRequestFetchAdapter }); } return this._http_adapter; } /** * Get API key for authentication * @returns {string} API key */ get api_key() { return this.model.data.api_key; } /** * Count tokens in input text * @abstract * @param {string} input - Text to tokenize * @returns {Promise} Token count result * @throws {Error} If not implemented by subclass */ async count_tokens(input) { throw new Error("count_tokens not implemented"); } /** * Estimate token count for input text * Uses character-based estimation (3.7 chars per token) * @param {string|Object} input - Input to estimate tokens for * @returns {number} Estimated token count */ estimate_tokens(input) { if (typeof input === "object") input = JSON.stringify(input); return Math.ceil(input.length / 3.7); } /** * Process a batch of inputs for embedding * @param {Array} inputs - Array of input objects * @returns {Promise>} Processed inputs with embeddings * @throws {Error} If API key is not set */ async embed_batch(inputs) { if (!this.api_key) throw new Error("API key not set"); inputs = inputs.filter((item) => item.embed_input?.length > 0); if (inputs.length === 0) { console.log("Empty batch (or all items have empty embed_input)"); return []; } const embed_inputs = await Promise.all( inputs.map((item) => this.prepare_embed_input(item.embed_input)) ); const _req = new this.req_adapter(this, embed_inputs); const request_params = _req.to_platform(); const resp = await this.request(request_params); if (!resp) { console.error("No response received for embedding request."); return []; } if (resp.error) return [resp]; const _res = new this.res_adapter(this, resp); const embeddings = _res.to_openai(); if (!embeddings) { console.error("Failed to parse embeddings."); return []; } return inputs.map((item, i) => { item.vec = embeddings[i].vec; item.tokens = embeddings[i].tokens; return item; }); } /** * Prepare input text for embedding * @abstract * @param {string} embed_input - Raw input text * @returns {Promise} Processed input text * @throws {Error} If not implemented by subclass */ async prepare_embed_input(embed_input) { throw new Error("prepare_embed_input not implemented"); } /** * Prepare request headers * @returns {Object} Headers object with authorization */ prepare_request_headers() { let headers = { "Content-Type": "application/json" }; if (this.api_key) { headers["Authorization"] = `Bearer ${this.api_key}`; } return headers; } /** * Make API request with retry logic * @param {Object} req - Request configuration * @param {number} [retries=0] - Number of retries attempted * @returns {Promise} API response */ async request(req, retries = 0) { try { req.throw = false; const resp = await this.http_adapter.request({ url: this.endpoint, ...req }); const resp_json = await this.get_resp_json(resp); if (resp_json.error) { return { error: normalize_error(resp_json, resp.status()) }; } return resp_json; } catch (error) { console.warn("Request error:", error); return await this.handle_request_err(error, req, retries); } } /** * Handle API request errors with retry logic * @param {Error|Object} error - Error object * @param {Object} req - Original request * @param {number} retries - Number of retries attempted * @returns {Promise} Retry response or null */ async handle_request_err(error, req, retries) { if (error.status === 429 && retries < 3) { const backoff = Math.pow(retries + 1, 2); console.log(`Retrying request (429) in ${backoff} seconds...`); await new Promise((r) => setTimeout(r, 1e3 * backoff)); return await this.request(req, retries + 1); } console.error(error); return null; } /** * Parse response body as JSON * @param {Response} resp - Response object * @returns {Promise} Parsed JSON */ async get_resp_json(resp) { return typeof resp.json === "function" ? await resp.json() : await resp.json; } /** * Validate API key by making test request * @returns {Promise} True if API key is valid */ async validate_api_key() { const resp = await this.embed_batch([{ embed_input: "test" }]); return Array.isArray(resp) && resp.length > 0 && resp[0].vec !== null; } /** * Trim input text to satisfy `max_tokens`. * @param {string} embed_input - Input text * @param {number} tokens_ct - Existing token count * @returns {Promise} Trimmed text */ async trim_input_to_max_tokens(embed_input, tokens_ct) { const reduce_ratio = (tokens_ct - this.max_tokens) / tokens_ct; const new_length = Math.floor(embed_input.length * (1 - reduce_ratio)); let trimmed_input = embed_input.slice(0, new_length); const last_space_index = trimmed_input.lastIndexOf(" "); if (last_space_index > 0) trimmed_input = trimmed_input.slice(0, last_space_index); const prepared = await this.prepare_embed_input(trimmed_input); if (prepared === null) return null; return prepared; } async load_tiktoken() { const cl100k_base = await fetch_json_cached(CL100K_URL, "cl100k_base.json"); this.tiktoken = new Tiktoken(cl100k_base); } }; var SmartEmbedModelRequestAdapter = class { /** * @constructor * @param {SmartEmbedModelApiAdapter} adapter - The SmartEmbedModelApiAdapter instance * @param {Array} embed_inputs - The array of input texts */ constructor(adapter, embed_inputs) { this.adapter = adapter; this.embed_inputs = embed_inputs; } get model_id() { return this.adapter.model.data.model_key; } get model_dims() { return this.adapter.model.data.dims; } /** * Get request headers * @returns {Object} Headers object */ get_headers() { return this.adapter.prepare_request_headers(); } /** * Convert request to platform-specific format * @returns {Object} Platform-specific request parameters */ to_platform() { return { method: "POST", headers: this.get_headers(), body: JSON.stringify(this.prepare_request_body()) }; } /** * Prepare request body for API call * @abstract * @returns {Object} Request body object * @throws {Error} If not implemented by subclass */ prepare_request_body() { throw new Error("prepare_request_body not implemented"); } }; var SmartEmbedModelResponseAdapter = class { /** * @constructor * @param {SmartEmbedModelApiAdapter} adapter - The SmartEmbedModelApiAdapter instance * @param {Object} response - The response object */ constructor(adapter, response) { this.adapter = adapter; this.response = response; } /** * Convert response to standard format * @returns {Array} Array of embedding results */ to_openai() { return this.parse_response(); } /** * Parse API response * @abstract * @returns {Array} Parsed embedding results * @throws {Error} If not implemented by subclass */ parse_response() { throw new Error("parse_response not implemented"); } }; // node_modules/obsidian-smart-env/node_modules/smart-embed-model/adapters/openai.js var SmartEmbedOpenAIAdapter = class extends SmartEmbedModelApiAdapter { static defaults = { adapter: "openai", description: "OpenAI (API)", default_model: "text-embedding-3-small", endpoint: "https://api.openai.com/v1/embeddings" }; /** * Count tokens in input text using OpenAI's tokenizer * @param {string} input - Text to tokenize * @returns {Promise} Token count result */ async count_tokens(input) { if (!this.tiktoken) await this.load_tiktoken(); return { tokens: this.tiktoken.encode(input).length }; } /** * Prepare input text for embedding * Handles token limit truncation * @param {string} embed_input - Raw input text * @returns {Promise} Processed input text */ async prepare_embed_input(embed_input) { if (typeof embed_input !== "string") { throw new TypeError("embed_input must be a string"); } if (embed_input.length === 0) { console.log("Warning: prepare_embed_input received an empty string"); return null; } const { tokens } = await this.count_tokens(embed_input); if (tokens <= this.max_tokens) { return embed_input; } return await this.trim_input_to_max_tokens(embed_input, tokens); } /** * Trim input text to fit token limit * @private * @param {string} embed_input - Input text to trim * @param {number} tokens_ct - Current token count * @returns {Promise} Trimmed input text */ async trim_input_to_max_tokens(embed_input, tokens_ct) { const reduce_ratio = (tokens_ct - this.max_tokens) / tokens_ct; const new_length = Math.floor(embed_input.length * (1 - reduce_ratio)); let trimmed_input = embed_input.slice(0, new_length); const last_space_index = trimmed_input.lastIndexOf(" "); if (last_space_index > 0) { trimmed_input = trimmed_input.slice(0, last_space_index); } const prepared_input = await this.prepare_embed_input(trimmed_input); if (prepared_input === null) { console.log( "Warning: prepare_embed_input resulted in an empty string after trimming" ); return null; } return prepared_input; } /** * Get the request adapter class. * @returns {SmartEmbedOpenAIRequestAdapter} The request adapter class */ get req_adapter() { return SmartEmbedOpenAIRequestAdapter; } /** * Get the response adapter class. * @returns {SmartEmbedOpenAIResponseAdapter} The response adapter class */ get res_adapter() { return SmartEmbedOpenAIResponseAdapter; } /** @returns {number} Maximum tokens per input */ get max_tokens() { return this.model.data.max_tokens || 8191; } /** @returns {Object} Settings configuration for OpenAI adapter */ get settings_config() { return { ...super.settings_config, "[ADAPTER].api_key": { name: "OpenAI API key for embeddings", type: "password", description: "Required for OpenAI embedding models.", placeholder: "Enter OpenAI API key" } }; } /** * Get available models (hardcoded list) * @returns {Promise} Map of model objects */ get_models() { return Promise.resolve(this.models); } get models() { return { "text-embedding-3-small": { "id": "text-embedding-3-small", "batch_size": 50, "dims": 1536, "max_tokens": 8191, "name": "OpenAI Text-3 Small", "description": "API, 8,191 tokens, 1,536 dim", "endpoint": "https://api.openai.com/v1/embeddings", "adapter": "openai" }, "text-embedding-3-large": { "id": "text-embedding-3-large", "batch_size": 50, "dims": 3072, "max_tokens": 8191, "name": "OpenAI Text-3 Large", "description": "API, 8,191 tokens, 3,072 dim", "endpoint": "https://api.openai.com/v1/embeddings", "adapter": "openai" }, // "text-embedding-3-small-512": { // "id": "text-embedding-3-small", // "batch_size": 50, // "dims": 512, // "max_tokens": 8191, // "name": "OpenAI Text-3 Small - 512", // "description": "API, 8,191 tokens, 512 dim", // "endpoint": "https://api.openai.com/v1/embeddings", // "adapter": "openai" // }, // "text-embedding-3-large-256": { // "id": "text-embedding-3-large", // "batch_size": 50, // "dims": 256, // "max_tokens": 8191, // "name": "OpenAI Text-3 Large - 256", // "description": "API, 8,191 tokens, 256 dim", // "endpoint": "https://api.openai.com/v1/embeddings", // "adapter": "openai" // }, "text-embedding-ada-002": { "id": "text-embedding-ada-002", "batch_size": 50, "dims": 1536, "max_tokens": 8191, "name": "OpenAI Ada", "description": "API, 8,191 tokens, 1,536 dim", "endpoint": "https://api.openai.com/v1/embeddings", "adapter": "openai" } }; } }; var SmartEmbedOpenAIRequestAdapter = class extends SmartEmbedModelRequestAdapter { /** * Prepare request body for OpenAI API * @returns {Object} Request body for API */ prepare_request_body() { const body = { model: this.model_id, input: this.embed_inputs }; if (this.model_id.startsWith("text-embedding-3")) { body.dimensions = this.model_dims; } return body; } }; var SmartEmbedOpenAIResponseAdapter = class extends SmartEmbedModelResponseAdapter { /** * Parse OpenAI API response * @returns {Array} Parsed embedding results */ parse_response() { const resp = this.response; if (!resp || !resp.data || !resp.usage) { console.error("Invalid response format", resp); return []; } const avg_tokens = resp.usage.total_tokens / resp.data.length; return resp.data.map((item) => ({ vec: item.embedding, tokens: avg_tokens // OpenAI doesn't provide tokens per item in batch requests })); } }; // node_modules/obsidian-smart-env/node_modules/smart-embed-model/adapters/_message.js var SmartEmbedMessageAdapter = class extends SmartEmbedAdapter { /** * Create message adapter instance */ constructor(model) { super(model); this.message_queue = {}; this.message_id = 0; this.connector = null; this.message_prefix = `msg_${Math.random().toString(36).substr(2, 9)}_`; } /** * Send message and wait for response * @protected * @param {string} method - Method name to call * @param {Object} params - Method parameters * @returns {Promise} Response data */ async _send_message(method, params) { return new Promise((resolve, reject) => { const id = `${this.message_prefix}${this.message_id++}`; this.message_queue[id] = { resolve, reject }; this._post_message({ method, params, id }); }); } /** * Handle response message from worker/iframe * @protected * @param {string} id - Message ID * @param {*} result - Response result * @param {Error} [error] - Response error */ _handle_message_result(id, result, error) { if (!id.startsWith(this.message_prefix)) return; if (result?.model_loaded) { console.log("model loaded"); this.state = "loaded"; this.model.model_loaded = true; this.model.load_result = result; } if (this.message_queue[id]) { if (error) { this.message_queue[id].reject(new Error(error)); } else { this.message_queue[id].resolve(result); } delete this.message_queue[id]; } } /** * Count tokens in input text * @param {string} input - Text to tokenize * @returns {Promise} Token count result */ async count_tokens(input) { return this._send_message("count_tokens", { input }); } /** * Generate embeddings for multiple inputs * @param {Array} inputs - Array of input objects * @returns {Promise>} Processed inputs with embeddings */ async embed_batch(inputs) { inputs = inputs.filter((item) => item.embed_input?.length > 0); if (!inputs.length) return []; const embed_inputs = inputs.map((item) => ({ embed_input: item.embed_input })); const result = await this._send_message("embed_batch", { inputs: embed_inputs }); return inputs.map((item, i) => { item.vec = result[i].vec; item.tokens = result[i].tokens; return item; }); } /** * Post message to worker/iframe * @abstract * @protected * @param {Object} message_data - Message to send * @throws {Error} If not implemented by subclass */ _post_message(message_data) { throw new Error("_post_message must be implemented by subclass"); } }; // node_modules/obsidian-smart-env/node_modules/smart-embed-model/adapters/iframe.js var SmartEmbedIframeAdapter = class extends SmartEmbedMessageAdapter { /** * Create iframe adapter instance */ constructor(model) { super(model); this.iframe = null; this.origin = window.location.origin; this.iframe_id = `smart_embed_iframe`; } /** * Initialize iframe and load model * @returns {Promise} */ async load() { const existing_iframe = document.getElementById(this.iframe_id); if (existing_iframe) { existing_iframe.remove(); } this.iframe = document.createElement("iframe"); this.iframe.style.display = "none"; this.iframe.id = this.iframe_id; document.body.appendChild(this.iframe); window.addEventListener("message", this._handle_message.bind(this)); this.iframe.srcdoc = ` `; await new Promise((resolve) => this.iframe.onload = resolve); const load_opts = { // ...this.model.opts, model_key: this.model.model_key, adapters: null, // cannot clone classes settings: null, batch_size: this.batch_size, use_gpu: this.use_gpu }; await this._send_message("load", load_opts); return new Promise((resolve) => { const check_model_loaded = () => { if (this.model.model_loaded) { resolve(); } else { setTimeout(check_model_loaded, 100); } }; check_model_loaded(); }); } /** * Post message to iframe * @protected * @param {Object} message_data - Message to send */ _post_message(message_data) { this.iframe.contentWindow.postMessage({ ...message_data, iframe_id: this.iframe_id }, this.origin); } /** * Handle message from iframe * @private * @param {MessageEvent} event - Message event */ _handle_message(event) { if (event.origin !== this.origin || event.data.iframe_id !== this.iframe_id) return; const { id, result, error } = event.data; this._handle_message_result(id, result, error); } }; // node_modules/obsidian-smart-env/node_modules/smart-embed-model/connectors/transformers_iframe.js var transformers_connector = 'var __defProp = Object.defineProperty;\nvar __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;\nvar __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);\n\n// ../smart-model/adapters/_adapter.js\nvar SmartModelAdapter = class {\n /**\n * Create a SmartModelAdapter instance.\n * @param {SmartModel} model - The parent SmartModel instance\n */\n constructor(model2) {\n this.model = model2;\n this.state = "unloaded";\n }\n /**\n * Load the adapter.\n * @async\n * @returns {Promise}\n */\n async load() {\n this.set_state("loaded");\n }\n /**\n * Unload the adapter.\n * @returns {void}\n */\n unload() {\n this.set_state("unloaded");\n }\n /**\n * Get all settings.\n * @returns {Object} All settings\n */\n get settings() {\n return this.model.settings;\n }\n /**\n * Get the current model key.\n * @returns {string} Current model identifier\n */\n get model_key() {\n return this.model.model_key;\n }\n /**\n * Get the models.\n * @returns {Object} Map of model objects\n */\n get models() {\n const models = this.model.data.provider_models;\n if (typeof models === "object" && Object.keys(models || {}).length > 0) return models;\n else {\n return {};\n }\n }\n /**\n * Get available models from the API.\n * @abstract\n * @param {boolean} [refresh=false] - Whether to refresh cached models\n * @returns {Promise} Map of model objects\n */\n async get_models(refresh = false) {\n throw new Error("get_models not implemented");\n }\n /**\n * Get available models as dropdown options synchronously.\n * @returns {Array} Array of model options.\n */\n get_models_as_options() {\n const models = this.models;\n if (!Object.keys(models || {}).length) {\n this.get_models(true);\n return [{ value: "", name: "No models currently available" }];\n }\n return Object.entries(models).map(([id, model2]) => ({ value: id, name: model2.name || id })).sort((a, b) => a.name.localeCompare(b.name));\n }\n /**\n * Set the adapter\'s state.\n * @deprecated should be handled in SmartModel (only handle once)\n * @param {(\'unloaded\'|\'loading\'|\'loaded\'|\'unloading\')} new_state - The new state\n * @throws {Error} If the state is invalid\n */\n set_state(new_state) {\n const valid_states = ["unloaded", "loading", "loaded", "unloading"];\n if (!valid_states.includes(new_state)) {\n throw new Error(`Invalid state: ${new_state}`);\n }\n this.state = new_state;\n }\n // Replace individual state getters/setters with a unified state management\n get is_loading() {\n return this.state === "loading";\n }\n get is_loaded() {\n return this.state === "loaded";\n }\n get is_unloading() {\n return this.state === "unloading";\n }\n get is_unloaded() {\n return this.state === "unloaded";\n }\n};\n\n// adapters/_adapter.js\nvar SmartEmbedAdapter = class extends SmartModelAdapter {\n /**\n * Count tokens in input text\n * @abstract\n * @param {string} input - Text to tokenize\n * @returns {Promise} Token count result\n * @property {number} tokens - Number of tokens in input\n * @throws {Error} If not implemented by subclass\n */\n async count_tokens(input) {\n throw new Error("count_tokens method not implemented");\n }\n /**\n * Generate embeddings for single input\n * @abstract\n * @param {string|Object} input - Text to embed\n * @returns {Promise} Embedding result\n * @property {number[]} vec - Embedding vector\n * @property {number} tokens - Number of tokens in input\n * @throws {Error} If not implemented by subclass\n */\n async embed(input) {\n if (typeof input === "string") input = { embed_input: input };\n return (await this.embed_batch([input]))[0];\n }\n /**\n * Generate embeddings for multiple inputs\n * @abstract\n * @param {Array} inputs - Texts to embed\n * @returns {Promise>} Array of embedding results\n * @property {number[]} vec - Embedding vector for each input\n * @property {number} tokens - Number of tokens in each input\n * @throws {Error} If not implemented by subclass\n */\n async embed_batch(inputs) {\n throw new Error("embed_batch method not implemented");\n }\n get settings_config() {\n return {\n "[ADAPTER].model_key": {\n name: "Embedding model",\n type: "dropdown",\n description: "Select an embedding model.",\n options_callback: "adapter.get_models_as_options",\n callback: "model_changed",\n default: this.constructor.defaults.default_model\n }\n };\n }\n get dims() {\n return this.model.data.dims;\n }\n get max_tokens() {\n return this.model.data.max_tokens;\n }\n get batch_size() {\n return this.model.data.batch_size || 1;\n }\n};\n/**\n * @override in sub-class with adapter-specific default configurations\n * @property {string} id - The adapter identifier\n * @property {string} description - Human-readable description\n * @property {string} type - Adapter type ("API")\n * @property {string} endpoint - API endpoint\n * @property {string} adapter - Adapter identifier\n * @property {string} default_model - Default model to use\n */\n__publicField(SmartEmbedAdapter, "defaults", {});\n\n// adapters/transformers.js\nvar transformers_defaults = {\n adapter: "transformers",\n description: "Transformers (Local, built-in)",\n default_model: "TaylorAI/bge-micro-v2",\n models: transformers_models\n};\nvar DEVICE_CONFIGS = {\n // // WebGPU: high quality first\n webgpu_fp16: {\n device: "webgpu",\n dtype: "fp16",\n quantized: false\n },\n webgpu_fp32: {\n device: "webgpu",\n dtype: "fp32",\n quantized: false\n },\n // WebGPU: quantized tiers\n webgpu_q8: {\n device: "webgpu",\n dtype: "q8",\n quantized: true\n },\n webgpu_q4: {\n device: "webgpu",\n dtype: "q4",\n quantized: true\n },\n // Optional, if you use it\n webgpu_q4f16: {\n device: "webgpu",\n dtype: "q4f16",\n quantized: true\n },\n webgpu_bnb4: {\n device: "webgpu",\n dtype: "bnb4",\n quantized: true\n },\n // WASM: quantized CPU\n wasm_q8: {\n dtype: "q8",\n quantized: true\n },\n wasm_q4: {\n dtype: "q4",\n quantized: true\n },\n // Final universal fallback: WASM CPU, dtype = auto\n wasm_auto: {\n // NOTE: leaving out device to avoid Linux issues with \'wasm\'\n // transformers.js will pick CPU/WASM backend itself\n quantized: false\n }\n};\nvar is_webgpu_available = async () => {\n if (!("gpu" in navigator)) return false;\n const adapter = await navigator.gpu.requestAdapter();\n if (!adapter) return false;\n return true;\n};\nvar SmartEmbedTransformersAdapter = class extends SmartEmbedAdapter {\n /**\n * @param {import("../smart_embed_model.js").SmartEmbedModel} model\n */\n constructor(model2) {\n super(model2);\n this.pipeline = null;\n this.tokenizer = null;\n this.active_config_key = null;\n this.has_gpu = false;\n }\n /**\n * Load the underlying transformers pipeline with WebGPU \u2192 WASM fallback.\n * @returns {Promise}\n */\n async load() {\n this.has_gpu = await is_webgpu_available();\n try {\n if (this.loading) {\n console.warn("[Transformers v2] load already in progress, waiting...");\n while (this.loading) {\n await new Promise((resolve) => setTimeout(resolve, 100));\n }\n } else {\n this.loading = true;\n if (this.pipeline) {\n this.loaded = true;\n this.loading = false;\n return;\n }\n await this.load_transformers_with_fallback();\n this.loading = false;\n this.loaded = true;\n console.log(`[Transformers v2] model loaded using ${this.active_config_key}`, this);\n }\n } catch (e) {\n this.loading = false;\n this.loaded = false;\n console.error("[Transformers v2] load failed", e);\n throw e;\n }\n }\n /**\n * Unload the pipeline and free resources.\n * @returns {Promise}\n */\n async unload() {\n try {\n if (this.pipeline) {\n if (typeof this.pipeline.destroy === "function") {\n this.pipeline.destroy();\n } else if (typeof this.pipeline.dispose === "function") {\n this.pipeline.dispose();\n }\n }\n } catch (err) {\n console.warn("[Transformers v2] error while disposing pipeline", err);\n }\n this.pipeline = null;\n this.tokenizer = null;\n this.active_config_key = null;\n this.loaded = false;\n }\n /**\n * Available models \u2013 reuses the v1 transformers model catalog.\n * @returns {Object}\n */\n get models() {\n return transformers_models;\n }\n /**\n * Maximum tokens per input.\n * @returns {number}\n */\n get max_tokens() {\n return this.model.data.max_tokens || 512;\n }\n /**\n * Effective batch size.\n * Prefers small deterministic batches when not explicitly configured.\n * @returns {number}\n */\n get batch_size() {\n const configured = this.model.data.batch_size;\n if (configured && configured > 0) return configured;\n return this.gpu_enabled ? 16 : 8;\n }\n get gpu_enabled() {\n if (this.has_gpu) {\n const explicit = typeof this.model.data.use_gpu === "boolean" ? this.model.data.use_gpu : null;\n if (explicit === false) return false;\n return true;\n } else {\n return false;\n }\n }\n /**\n * Initialize transformers pipeline with WebGPU \u2192 WASM fallback.\n * @private\n * @returns {Promise}\n */\n async load_transformers_with_fallback() {\n const { pipeline, env, AutoTokenizer } = await import("@huggingface/transformers");\n env.allowLocalModels = false;\n if (typeof env.useBrowserCache !== "undefined") {\n env.useBrowserCache = true;\n }\n let last_error = null;\n const CONFIG_LIST_ORDER = Object.keys(DEVICE_CONFIGS);\n const try_create = async (config_key) => {\n const pipe = await pipeline("feature-extraction", this.model_key, DEVICE_CONFIGS[config_key]);\n return pipe;\n };\n for (const config of CONFIG_LIST_ORDER) {\n if (this.pipeline) break;\n if (config.includes("gpu") && !this.gpu_enabled) {\n console.warn(`[Transformers v2: ${config}] skipping ${config} as GPU is disabled`);\n continue;\n }\n try {\n console.log(`[Transformers v2] trying to load pipeline on ${config}`);\n this.pipeline = await try_create(config);\n this.active_config_key = config;\n break;\n } catch (err) {\n console.warn(`[Transformers v2: ${config}] failed to load pipeline on ${config}`, err);\n last_error = err;\n }\n }\n if (this.pipeline) {\n console.log(`[Transformers v2: ${this.active_config_key}] pipeline initialized using ${this.active_config_key}`);\n } else {\n throw last_error || new Error("Failed to initialize transformers pipeline");\n }\n this.tokenizer = await AutoTokenizer.from_pretrained(this.model_key);\n }\n /**\n * Count tokens in input text.\n * @param {string} input\n * @returns {Promise<{tokens:number}>}\n */\n async count_tokens(input) {\n if (!this.tokenizer) {\n await this.load();\n }\n const { input_ids } = await this.tokenizer(input);\n return { tokens: input_ids.data.length };\n }\n /**\n * Generate embeddings for multiple inputs.\n * @param {Array} inputs\n * @returns {Promise>}\n */\n async embed_batch(inputs) {\n if (!this.pipeline) {\n await this.load();\n }\n const filtered_inputs = inputs.filter((item) => item.embed_input && item.embed_input.length > 0);\n if (!filtered_inputs.length) return [];\n const results = [];\n for (let i = 0; i < filtered_inputs.length; i += this.batch_size) {\n const batch = filtered_inputs.slice(i, i + this.batch_size);\n const batch_results = await this._process_batch(batch);\n results.push(...batch_results);\n }\n return results;\n }\n /**\n * Process a single batch \u2013 with per-item retry on failure.\n * @private\n * @param {Array} batch_inputs\n * @returns {Promise>}\n */\n async _process_batch(batch_inputs) {\n const prepared = await Promise.all(\n batch_inputs.map((item) => this._prepare_input(item.embed_input))\n );\n const embed_inputs = prepared.map((p) => p.text);\n const tokens = prepared.map((p) => p.tokens);\n try {\n const resp = await this.pipeline(embed_inputs, { pooling: "mean", normalize: true });\n return batch_inputs.map((item, i) => {\n const vec = Array.from(resp[i].data).map((val) => Math.round(val * 1e8) / 1e8);\n item.vec = vec;\n item.tokens = tokens[i];\n return item;\n });\n } catch (err) {\n console.error("[Transformers v2] batch embed failed \\u2013 retrying items individually", err);\n return await this._retry_items_individually(batch_inputs);\n }\n }\n /**\n * Prepare a single input by truncating to max_tokens if necessary.\n * @private\n * @param {string} embed_input\n * @returns {Promise<{text:string,tokens:number}>}\n */\n async _prepare_input(embed_input) {\n let { tokens } = await this.count_tokens(embed_input);\n if (tokens <= this.max_tokens) {\n return { text: embed_input, tokens };\n }\n let truncated = embed_input;\n while (tokens > this.max_tokens && truncated.length > 0) {\n const pct = this.max_tokens / tokens;\n const max_chars = Math.floor(truncated.length * pct * 0.9);\n truncated = truncated.slice(0, max_chars);\n const last_space = truncated.lastIndexOf(" ");\n if (last_space > 0) {\n truncated = truncated.slice(0, last_space);\n }\n tokens = (await this.count_tokens(truncated)).tokens;\n }\n return { text: truncated, tokens };\n }\n /**\n * Retry each item individually after a batch failure.\n * @private\n * @param {Array} batch_inputs\n * @returns {Promise>}\n */\n async _retry_items_individually(batch_inputs) {\n await this._reset_pipeline_after_error();\n const results = [];\n for (const item of batch_inputs) {\n try {\n const prepared = await this._prepare_input(item.embed_input);\n const resp = await this.pipeline(prepared.text, { pooling: "mean", normalize: true });\n const vec = Array.from(resp[0].data).map((val) => Math.round(val * 1e8) / 1e8);\n results.push({\n ...item,\n vec,\n tokens: prepared.tokens\n });\n } catch (single_err) {\n console.error("[Transformers v2] single item embed failed \\u2013 skipping", single_err);\n results.push({\n ...item,\n vec: [],\n tokens: 0,\n error: single_err.message\n });\n }\n }\n return results;\n }\n /**\n * Reset pipeline after a failure \u2013 falling back to WASM if needed.\n * @private\n * @returns {Promise}\n */\n async _reset_pipeline_after_error() {\n try {\n if (this.pipeline) {\n if (typeof this.pipeline.destroy === "function") {\n this.pipeline.destroy();\n } else if (typeof this.pipeline.dispose === "function") {\n this.pipeline.dispose();\n }\n }\n } catch (err) {\n console.warn("[Transformers v2] error while resetting pipeline", err);\n }\n this.pipeline = null;\n await this.load_transformers_with_fallback();\n }\n /**\n * V2 intentionally exposes only model selection in the settings UI.\n * @returns {Object}\n */\n get settings_config() {\n return super.settings_config;\n }\n};\n__publicField(SmartEmbedTransformersAdapter, "defaults", transformers_defaults);\nvar transformers_models = {\n "TaylorAI/bge-micro-v2": {\n "id": "TaylorAI/bge-micro-v2",\n "batch_size": 1,\n "dims": 384,\n "max_tokens": 512,\n "name": "BGE-micro-v2",\n "description": "Local, 512 tokens, 384 dim (recommended)",\n "adapter": "transformers"\n },\n "Snowflake/snowflake-arctic-embed-xs": {\n "id": "Snowflake/snowflake-arctic-embed-xs",\n "batch_size": 1,\n "dims": 384,\n "max_tokens": 512,\n "name": "Snowflake Arctic Embed XS",\n "description": "Local, 512 tokens, 384 dim",\n "adapter": "transformers"\n },\n "Snowflake/snowflake-arctic-embed-s": {\n "id": "Snowflake/snowflake-arctic-embed-s",\n "batch_size": 1,\n "dims": 384,\n "max_tokens": 512,\n "name": "Snowflake Arctic Embed Small",\n "description": "Local, 512 tokens, 384 dim",\n "adapter": "transformers"\n },\n "Snowflake/snowflake-arctic-embed-m": {\n "id": "Snowflake/snowflake-arctic-embed-m",\n "batch_size": 1,\n "dims": 768,\n "max_tokens": 512,\n "name": "Snowflake Arctic Embed Medium",\n "description": "Local, 512 tokens, 768 dim",\n "adapter": "transformers"\n },\n "TaylorAI/gte-tiny": {\n "id": "TaylorAI/gte-tiny",\n "batch_size": 1,\n "dims": 384,\n "max_tokens": 512,\n "name": "GTE-tiny",\n "description": "Local, 512 tokens, 384 dim",\n "adapter": "transformers"\n },\n "onnx-community/embeddinggemma-300m-ONNX": {\n "id": "onnx-community/embeddinggemma-300m-ONNX",\n "batch_size": 1,\n "dims": 768,\n "max_tokens": 2048,\n "name": "EmbeddingGemma-300M",\n "description": "Local, 2,048 tokens, 768 dim",\n "adapter": "transformers"\n },\n "Mihaiii/Ivysaur": {\n "id": "Mihaiii/Ivysaur",\n "batch_size": 1,\n "dims": 384,\n "max_tokens": 512,\n "name": "Ivysaur",\n "description": "Local, 512 tokens, 384 dim",\n "adapter": "transformers"\n },\n "andersonbcdefg/bge-small-4096": {\n "id": "andersonbcdefg/bge-small-4096",\n "batch_size": 1,\n "dims": 384,\n "max_tokens": 4096,\n "name": "BGE-small-4K",\n "description": "Local, 4,096 tokens, 384 dim",\n "adapter": "transformers"\n },\n // Too slow and persistent crashes\n // "jinaai/jina-embeddings-v2-base-de": {\n // "id": "jinaai/jina-embeddings-v2-base-de",\n // "batch_size": 1,\n // "dims": 768,\n // "max_tokens": 4096,\n // "name": "jina-embeddings-v2-base-de",\n // "description": "Local, 4,096 tokens, 768 dim, German",\n // "adapter": "transformers"\n // },\n "Xenova/jina-embeddings-v2-base-zh": {\n "id": "Xenova/jina-embeddings-v2-base-zh",\n "batch_size": 1,\n "dims": 768,\n "max_tokens": 8192,\n "name": "Jina-v2-base-zh-8K",\n "description": "Local, 8,192 tokens, 768 dim, Chinese/English bilingual",\n "adapter": "transformers"\n },\n "Xenova/jina-embeddings-v2-small-en": {\n "id": "Xenova/jina-embeddings-v2-small-en",\n "batch_size": 1,\n "dims": 512,\n "max_tokens": 8192,\n "name": "Jina-v2-small-en",\n "description": "Local, 8,192 tokens, 512 dim",\n "adapter": "transformers"\n },\n "nomic-ai/nomic-embed-text-v1.5": {\n "id": "nomic-ai/nomic-embed-text-v1.5",\n "batch_size": 1,\n "dims": 768,\n "max_tokens": 2048,\n "name": "Nomic-embed-text-v1.5",\n "description": "Local, 8,192 tokens, 768 dim",\n "adapter": "transformers"\n },\n "Xenova/bge-small-en-v1.5": {\n "id": "Xenova/bge-small-en-v1.5",\n "batch_size": 1,\n "dims": 384,\n "max_tokens": 512,\n "name": "BGE-small",\n "description": "Local, 512 tokens, 384 dim",\n "adapter": "transformers"\n },\n "nomic-ai/nomic-embed-text-v1": {\n "id": "nomic-ai/nomic-embed-text-v1",\n "batch_size": 1,\n "dims": 768,\n "max_tokens": 2048,\n "name": "Nomic-embed-text",\n "description": "Local, 2,048 tokens, 768 dim",\n "adapter": "transformers"\n }\n};\n\n// build/transformers_iframe_script.js\nvar model = null;\nasync function process_message(data) {\n const { method, params, id, iframe_id } = data;\n try {\n let result;\n switch (method) {\n case "init":\n console.log("init");\n break;\n case "load":\n const model_params = { data: params, ...params };\n console.log("load", { model_params });\n model = new SmartEmbedTransformersAdapter(model_params);\n await model.load();\n result = { model_loaded: true, model_config_key: model.active_config_key };\n break;\n case "embed_batch":\n if (!model) throw new Error("Model not loaded");\n result = await model.embed_batch(params.inputs);\n break;\n case "count_tokens":\n if (!model) throw new Error("Model not loaded");\n result = await model.count_tokens(params);\n break;\n default:\n throw new Error(`Unknown method: ${method}`);\n }\n return { id, result, iframe_id };\n } catch (error) {\n console.error("Error processing message:", error);\n return { id, error: error.message, iframe_id };\n }\n}\nprocess_message({ method: "init" });\n'; // node_modules/obsidian-smart-env/node_modules/smart-embed-model/adapters/transformers.js var transformers_defaults = { adapter: "transformers", description: "Transformers (Local, built-in)", default_model: "TaylorAI/bge-micro-v2", models: transformers_models }; var transformers_models = { "TaylorAI/bge-micro-v2": { "id": "TaylorAI/bge-micro-v2", "batch_size": 1, "dims": 384, "max_tokens": 512, "name": "BGE-micro-v2", "description": "Local, 512 tokens, 384 dim (recommended)", "adapter": "transformers" }, "Snowflake/snowflake-arctic-embed-xs": { "id": "Snowflake/snowflake-arctic-embed-xs", "batch_size": 1, "dims": 384, "max_tokens": 512, "name": "Snowflake Arctic Embed XS", "description": "Local, 512 tokens, 384 dim", "adapter": "transformers" }, "Snowflake/snowflake-arctic-embed-s": { "id": "Snowflake/snowflake-arctic-embed-s", "batch_size": 1, "dims": 384, "max_tokens": 512, "name": "Snowflake Arctic Embed Small", "description": "Local, 512 tokens, 384 dim", "adapter": "transformers" }, "Snowflake/snowflake-arctic-embed-m": { "id": "Snowflake/snowflake-arctic-embed-m", "batch_size": 1, "dims": 768, "max_tokens": 512, "name": "Snowflake Arctic Embed Medium", "description": "Local, 512 tokens, 768 dim", "adapter": "transformers" }, "TaylorAI/gte-tiny": { "id": "TaylorAI/gte-tiny", "batch_size": 1, "dims": 384, "max_tokens": 512, "name": "GTE-tiny", "description": "Local, 512 tokens, 384 dim", "adapter": "transformers" }, "onnx-community/embeddinggemma-300m-ONNX": { "id": "onnx-community/embeddinggemma-300m-ONNX", "batch_size": 1, "dims": 768, "max_tokens": 2048, "name": "EmbeddingGemma-300M", "description": "Local, 2,048 tokens, 768 dim", "adapter": "transformers" }, "Mihaiii/Ivysaur": { "id": "Mihaiii/Ivysaur", "batch_size": 1, "dims": 384, "max_tokens": 512, "name": "Ivysaur", "description": "Local, 512 tokens, 384 dim", "adapter": "transformers" }, "andersonbcdefg/bge-small-4096": { "id": "andersonbcdefg/bge-small-4096", "batch_size": 1, "dims": 384, "max_tokens": 4096, "name": "BGE-small-4K", "description": "Local, 4,096 tokens, 384 dim", "adapter": "transformers" }, // Too slow and persistent crashes // "jinaai/jina-embeddings-v2-base-de": { // "id": "jinaai/jina-embeddings-v2-base-de", // "batch_size": 1, // "dims": 768, // "max_tokens": 4096, // "name": "jina-embeddings-v2-base-de", // "description": "Local, 4,096 tokens, 768 dim, German", // "adapter": "transformers" // }, "Xenova/jina-embeddings-v2-base-zh": { "id": "Xenova/jina-embeddings-v2-base-zh", "batch_size": 1, "dims": 768, "max_tokens": 8192, "name": "Jina-v2-base-zh-8K", "description": "Local, 8,192 tokens, 768 dim, Chinese/English bilingual", "adapter": "transformers" }, "Xenova/jina-embeddings-v2-small-en": { "id": "Xenova/jina-embeddings-v2-small-en", "batch_size": 1, "dims": 512, "max_tokens": 8192, "name": "Jina-v2-small-en", "description": "Local, 8,192 tokens, 512 dim", "adapter": "transformers" }, "nomic-ai/nomic-embed-text-v1.5": { "id": "nomic-ai/nomic-embed-text-v1.5", "batch_size": 1, "dims": 768, "max_tokens": 2048, "name": "Nomic-embed-text-v1.5", "description": "Local, 8,192 tokens, 768 dim", "adapter": "transformers" }, "Xenova/bge-small-en-v1.5": { "id": "Xenova/bge-small-en-v1.5", "batch_size": 1, "dims": 384, "max_tokens": 512, "name": "BGE-small", "description": "Local, 512 tokens, 384 dim", "adapter": "transformers" }, "nomic-ai/nomic-embed-text-v1": { "id": "nomic-ai/nomic-embed-text-v1", "batch_size": 1, "dims": 768, "max_tokens": 2048, "name": "Nomic-embed-text", "description": "Local, 2,048 tokens, 768 dim", "adapter": "transformers" } }; var transformers_settings_config = { // "[ADAPTER].legacy_transformers": { // name: 'Legacy transformers (no GPU)', // type: "toggle", // description: "Use legacy transformers (v2) instead of v3. This may resolve issues if the local embedding isn't working.", // callback: 'embed_model_changed', // default: true, // }, }; var settings_config4 = { // "legacy_transformers": { // name: 'Legacy transformers (no GPU)', // type: "toggle", // description: "Use legacy transformers (v2) instead of v3. This may resolve issues if the local embedding isn't working.", // // callback: 'embed_model_changed', // // default: false, // }, }; // node_modules/obsidian-smart-env/node_modules/smart-embed-model/adapters/transformers_iframe.js var SmartEmbedTransformersIframeAdapter = class extends SmartEmbedIframeAdapter { static defaults = transformers_defaults; /** * Create transformers iframe adapter instance */ constructor(model) { super(model); this.connector = transformers_connector.replace("@huggingface/transformers", "https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.8.0"); console.log("transformers iframe connector", this.model); } /** @returns {Object} Settings configuration for transformers adapter */ get settings_config() { return { ...super.settings_config, ...transformers_settings_config }; } /** * Get available models (hardcoded list) * @returns {Promise} Map of model objects */ get_models() { return Promise.resolve(this.models); } get models() { return transformers_models; } }; // node_modules/obsidian-smart-env/node_modules/smart-embed-model/adapters/ollama.js var SmartEmbedOllamaAdapter = class extends SmartEmbedModelApiAdapter { static defaults = { description: "Ollama (Local)", type: "API", host: "http://localhost:11434", endpoint: "/api/embed", models_endpoint: "/api/tags", api_key: "na", // Not required for local instance streaming: false, // Ollama's embed API does not support streaming max_tokens: 512, // Example default, adjust based on model capabilities signup_url: null, // Not applicable for local instance batch_size: 30, models: {}, model_key: "nomic-embed-text" }; get host() { return this.model.data.host || this.constructor.defaults.host; } get endpoint() { return `${this.host}${this.constructor.defaults.endpoint}`; } get models_endpoint() { return `${this.host}${this.constructor.defaults.models_endpoint}`; } get model_show_endpoint() { return `${this.host}/api/show`; } async load() { await this.get_models(); await super.load(); } /** * Estimate token count for input text. * Ollama does not expose a tokenizer so we use a character based heuristic. * @param {string} input - Text to tokenize * @returns {Promise} Token count result */ async count_tokens(input) { return { tokens: this.estimate_tokens(input) }; } /** * Prepare input text and ensure it fits within `max_tokens`. * @param {string} embed_input - Raw input text * @returns {Promise} Processed input text */ async prepare_embed_input(embed_input) { if (typeof embed_input !== "string") throw new TypeError("embed_input must be a string"); if (embed_input.length === 0) return null; const { tokens } = await this.count_tokens(embed_input); if (tokens <= this.max_tokens) return embed_input; return await this.trim_input_to_max_tokens(embed_input, tokens); } /** * Trim input text to satisfy `max_tokens`. * @private * @param {string} embed_input - Input text * @param {number} tokens_ct - Existing token count * @returns {Promise} Trimmed text */ async trim_input_to_max_tokens(embed_input, tokens_ct) { const reduce_ratio = (tokens_ct - this.max_tokens) / tokens_ct; const new_length = Math.floor(embed_input.length * (1 - reduce_ratio)); let trimmed_input = embed_input.slice(0, new_length); const last_space_index = trimmed_input.lastIndexOf(" "); if (last_space_index > 0) trimmed_input = trimmed_input.slice(0, last_space_index); const prepared = await this.prepare_embed_input(trimmed_input); if (prepared === null) return null; return prepared; } /** @returns {number} Maximum tokens for an input */ get max_tokens() { return this.model.data.max_tokens || this.constructor.defaults.max_tokens; } /** * Get the request adapter class. * @returns {SmartEmbedModelOllamaRequestAdapter} The request adapter class */ get req_adapter() { return SmartEmbedModelOllamaRequestAdapter; } /** * Get the response adapter class. * @returns {SmartEmbedModelOllamaResponseAdapter} The response adapter class */ get res_adapter() { return SmartEmbedModelOllamaResponseAdapter; } /** * Get available models from local Ollama instance. * @param {boolean} [refresh=false] - Whether to refresh cached models * @returns {Promise} Map of model objects */ async get_models(refresh = false) { if (!this.model_data || refresh) { const list_resp = await this.http_adapter.request({ url: this.models_endpoint, method: "GET" }); if (list_resp.ok === false) { throw new Error(`Failed to fetch models list: ${list_resp.statusText}`); } const list_data = await list_resp.json(); const models_raw = []; for (const m of filter_embedding_models(list_data.models || [])) { const detail_resp = await this.http_adapter.request({ url: this.model_show_endpoint, method: "POST", body: JSON.stringify({ model: m.name }) }); models_raw.push({ ...await detail_resp.json(), name: m.name }); } const model_data = this.parse_model_data(models_raw); this.model_data = model_data; this.model.re_render_settings(); return model_data; } return this.model_data; } /** * Get available models as dropdown options synchronously. * @returns {Array} Array of model options. */ get_models_as_options() { const models = this.model_data; if (!Object.keys(models || {}).length) { this.get_models(true); return [{ value: "", name: "No models currently available" }]; } return Object.values(models).map((model) => ({ value: model.id, name: model.name || model.id })).sort((a, b) => a.name.localeCompare(b.name)); } /** * Parse model data from Ollama API response. * @param {Object} model_data - Raw model data from Ollama * @returns {Object} Map of model objects with capabilities and limits */ parse_model_data(model_data) { if (!Array.isArray(model_data)) { this.model_data = {}; console.error("Invalid model data format from Ollama:", model_data); return {}; } if (model_data.length === 0) { this.model_data = { "no_models_available": { id: "no_models_available", name: "No models currently available" } }; return this.model_data; } this.model_data = model_data.reduce((acc, model) => { const info = model.model_info || {}; const ctx = Object.entries(info).find(([k]) => k.includes("context_length"))?.[1]; const dims = Object.entries(info).find(([k]) => k.includes("embedding_length"))?.[1]; acc[model.name] = { model_name: model.name, id: model.name, multimodal: false, max_tokens: ctx || this.max_tokens, dims, description: model.description || `Model: ${model.name}` }; return acc; }, {}); this._models = this.model_data; return this.model_data; } /** * Get the models. * @returns {Object} Map of model objects */ get models() { if (typeof this._models === "object" && Object.keys(this._models || {}).length > 0) return this._models; else { return {}; } } /** * Override settings config to remove API key setting since not needed for local instance. * @returns {Object} Settings configuration object */ get settings_config() { const config = super.settings_config; delete config["[ADAPTER].api_key"]; config["[ADAPTER].host"] = { name: "Ollama host", type: "text", description: "Enter the host for your Ollama instance", default: this.constructor.defaults.host }; return config; } }; var SmartEmbedModelOllamaRequestAdapter = class extends SmartEmbedModelRequestAdapter { /** * Convert request to Ollama's embed API format. * @returns {Object} Request parameters in Ollama's format */ to_platform() { const ollama_body = { model: this.model_id, input: this.embed_inputs }; return { url: this.adapter.endpoint, method: "POST", headers: this.get_headers(), body: JSON.stringify(ollama_body) }; } /** * Prepare request headers for Ollama API. * @returns {Object} Headers object */ get_headers() { return { "Content-Type": "application/json" }; } }; var SmartEmbedModelOllamaResponseAdapter = class extends SmartEmbedModelResponseAdapter { /** * Convert Ollama's response to a standardized OpenAI-like format. * @returns {Array} Array of embedding results */ to_openai() { const resp = this.response; if (!resp || !resp.embeddings) { console.error("Invalid response format from Ollama:", resp); return []; } const tokens = Math.ceil(resp.prompt_eval_count / this.adapter.batch_size); const embeddings = resp.embeddings.map((vec) => ({ vec, tokens })); return embeddings; } /** * Parse the response object. * @returns {Array} Parsed embedding results */ parse_response() { return this.to_openai(); } }; var is_embedding_model = (mod) => { return ["embed", "embedding", "bge"].some((keyword) => mod.name.toLowerCase().includes(keyword)); }; var filter_embedding_models = (models) => { if (!Array.isArray(models)) { throw new TypeError("models must be an array"); } return models.filter(is_embedding_model); }; // node_modules/obsidian-smart-env/node_modules/smart-embed-model/adapters/gemini.js var GeminiEmbedModelAdapter = class extends SmartEmbedModelApiAdapter { static defaults = { adapter: "gemini", description: "Google Gemini (API)", default_model: "gemini-embedding-001", endpoint: "https://generativelanguage.googleapis.com/v1beta/models/gemini-embedding-001:batchEmbedContents" }; /** * Count tokens in input text using tokenizer * @param {string} input - Text to tokenize * @returns {Promise} Token count result */ async count_tokens(input) { if (!this.tiktoken) await this.load_tiktoken(); return { tokens: this.tiktoken.encode(input).length }; } /** * Prepare input text for embedding * Handles token limit truncation * @param {string} embed_input - Raw input text * @returns {Promise} Processed input text */ async prepare_embed_input(embed_input) { if (typeof embed_input !== "string") { throw new TypeError("embed_input must be a string"); } if (embed_input.length === 0) { console.log("Warning: prepare_embed_input received an empty string"); return null; } const { tokens } = await this.count_tokens(embed_input); if (tokens <= this.max_tokens) { return embed_input; } return await this.trim_input_to_max_tokens(embed_input, tokens); } /** * Trim input text to fit token limit * @private * @param {string} embed_input - Input text to trim * @param {number} tokens_ct - Current token count * @returns {Promise} Trimmed input text */ async trim_input_to_max_tokens(embed_input, tokens_ct) { const reduce_ratio = (tokens_ct - this.max_tokens) / tokens_ct; const new_length = Math.floor(embed_input.length * (1 - reduce_ratio)); let trimmed_input = embed_input.slice(0, new_length); const last_space_index = trimmed_input.lastIndexOf(" "); if (last_space_index > 0) { trimmed_input = trimmed_input.slice(0, last_space_index); } const prepared_input = await this.prepare_embed_input(trimmed_input); if (prepared_input === null) { console.log( "Warning: prepare_embed_input resulted in an empty string after trimming" ); return null; } return prepared_input; } /** * Get the request adapter class. * @returns {SmartEmbedGeminiRequestAdapter} The request adapter class */ get req_adapter() { return SmartEmbedGeminiRequestAdapter; } /** * Get the response adapter class. * @returns {SmartEmbedGeminiResponseAdapter} The response adapter class */ get res_adapter() { return SmartEmbedGeminiResponseAdapter; } /** @returns {Object} Settings configuration for Gemini adapter */ get settings_config() { return { ...super.settings_config, "[ADAPTER].api_key": { name: "Google API Key for Gemini embeddings", type: "password", description: "Required for Gemini embedding models", placeholder: "Enter Google API Key" } }; } /** * Get available models (hardcoded list) * @returns {Promise} Map of model objects */ get_models() { return Promise.resolve(this.models); } get models() { return { "gemini-embedding-001": { "id": "models/gemini-embedding-001", "batch_size": 50, "dims": 768, "max_tokens": 2048, "name": "Gemini Embedding", "description": "API, 2,048 tokens, 768 dim", "endpoint": "https://generativelanguage.googleapis.com/v1beta/models/gemini-embedding-001:batchEmbedContents", "adapter": "gemini" } }; } prepare_request_headers() { return { "Content-Type": "application/json", "x-goog-api-key": this.api_key }; } }; var SmartEmbedGeminiRequestAdapter = class extends SmartEmbedModelRequestAdapter { /** * Prepare request body for Gemini API * @returns {Object} Request body for API */ prepare_request_body() { const requests = this.embed_inputs.map((input) => { const [title, ...content] = input.split("\n"); const doc_content = content.join("\n").trim() || ""; if (doc_content.length) { return { model: this.model_id, content: { parts: [{ text: doc_content }] }, outputDimensionality: this.model_dims, taskType: "RETRIEVAL_DOCUMENT", title }; } else { return { model: this.model_id, content: { parts: [{ text: title }] }, outputDimensionality: this.model_dims, taskType: "RETRIEVAL_DOCUMENT" }; } }); return { requests }; } }; var SmartEmbedGeminiResponseAdapter = class extends SmartEmbedModelResponseAdapter { /** * Parse Gemini API response * @returns {Array} Parsed embedding results */ parse_response() { const resp = this.response; console.log("Gemini response:", resp); if (!resp || !resp.embeddings || !resp.embeddings[0].values) { console.error("Invalid Gemini embedding response format", resp); return []; } return resp.embeddings.map((embedding, i) => { if (!embedding.values || embedding.values.length === 0) { console.warn(`No values for embedding at index ${i}`); return { vec: [], tokens: 0 }; } return { vec: embedding.values, tokens: 500 // not provided }; }); } }; // node_modules/obsidian-smart-env/node_modules/smart-embed-model/adapters/lm_studio.js function parse_lm_studio_models(list, adapter_key = "lm_studio") { if (list.object !== "list" || !Array.isArray(list.data)) { return { _: { id: "No models found." } }; } console.log("LM Studio models", list); return list.data.filter((m) => m.id && m.type === "embeddings").reduce((acc, m) => { acc[m.id] = { id: m.id, model_name: m.id, max_tokens: m.loaded_context_length || 512, description: `LM Studio model: ${m.id}`, adapter: adapter_key }; return acc; }, {}); } var LmStudioEmbedModelAdapter = class extends SmartEmbedModelApiAdapter { static key = "lm_studio"; static defaults = { description: "LM Studio", type: "API", host: "http://localhost:1234", // endpoint: "/v1/embeddings", endpoint: "/api/v0/embeddings", models_endpoint: "/api/v0/models", default_model: "", // user picks from dropdown streaming: false, api_key: "na", // not used batch_size: 10, max_tokens: 512 }; get req_adapter() { return LmStudioEmbedModelRequestAdapter; } get res_adapter() { return LmStudioEmbedModelResponseAdapter; } get host() { return this.model.data.host || this.constructor.defaults.host; } get endpoint() { return `${this.host}${this.constructor.defaults.endpoint}`; } get models_endpoint() { return `${this.host}${this.constructor.defaults.models_endpoint}`; } get settings_config() { const cfg = { ...super.settings_config }; delete cfg["[ADAPTER].api_key"]; cfg["[ADAPTER].refresh_models"] = { name: "Refresh Models", type: "button", description: "Refresh the list of available models.", callback: "adapter.refresh_models" }; cfg["[ADAPTER].current_model"] = { type: "html", value: `

Embedding Model Max Tokens: ${this.max_tokens} (may be configured in LM Studio)

` }; cfg["[ADAPTER].batch_size"] = { name: "Embedding Batch Size", type: "number", description: "Number of embeddings to process in parallel. Adjusting this may improve performance.", value: this.batch_size, default: this.constructor.defaults.batch_size }; cfg["[ADAPTER].cors_note"] = { name: "CORS required", type: "html", // The renderer treats `value` as innerHTML. value: `

Before you can use LM Studio you must Enable CORS inside LM Studio \u2192 Developer \u2192 Settings

` }; return cfg; } async get_models(refresh = false) { if (!refresh && this.model.data.provider_models) return this.model.data.provider_models; const resp = await this.http_adapter.request({ url: this.models_endpoint, method: "GET" }); const raw = await resp.json(); const parsed = this.parse_model_data(raw); this.model.data.provider_models = parsed; this.model.re_render_settings(); return parsed; } parse_model_data(list) { return parse_lm_studio_models(list, this.constructor.key); } async count_tokens(input) { return { tokens: this.estimate_tokens(input) }; } /** * Prepare input text and ensure it fits within `max_tokens`. * @param {string} embed_input - Raw input text * @returns {Promise} Processed input text */ async prepare_embed_input(embed_input) { if (typeof embed_input !== "string") throw new TypeError("embed_input must be a string"); if (embed_input.length === 0) return null; const { tokens } = await this.count_tokens(embed_input); if (tokens <= this.max_tokens) return embed_input; return await this.trim_input_to_max_tokens(embed_input, tokens); } /** * Refresh available models. */ refresh_models() { console.log("refresh_models"); this.get_models(true); } // no usaqge stats from LM Studio so need to estimate tokens async embed_batch(inputs) { const token_cts = inputs.map((item) => this.estimate_tokens(item.embed_input)); const resp = await super.embed_batch(inputs); resp.forEach((item, idx) => { item.tokens = token_cts[idx]; }); return resp; } }; var LmStudioEmbedModelRequestAdapter = class extends SmartEmbedModelRequestAdapter { /** * Prepare request body for LM Studio API * @returns {Object} Request body for API */ prepare_request_body() { const body = { model: this.model_id, input: this.embed_inputs }; return body; } }; var LmStudioEmbedModelResponseAdapter = class extends SmartEmbedModelResponseAdapter { /** * Parse LM Studio API response * @returns {Array} Parsed embedding results */ parse_response() { const resp = this.response; if (!resp || !resp.data) { console.error("Invalid response format", resp); return []; } return resp.data.map((item) => ({ vec: item.embedding, tokens: null // LM Studio doesn't provide token usage })); } }; // node_modules/obsidian-smart-env/node_modules/smart-chat-model/smart_chat_model.js var SmartChatModel = class extends SmartModel { scope_name = "smart_chat_model"; static defaults = { adapter: "openai" }; /** * Create a SmartChatModel instance. * @param {Object} opts - Configuration options * @param {string} opts.adapter - Adapter to use * @param {Object} opts.adapters - Map of adapter names to adapter classes * @param {Object} opts.settings - Model settings configuration */ constructor(opts = {}) { super(opts); } /** * Get available models. * @returns {Object} Map of model objects */ get models() { return this.adapter.models; } get can_stream() { return this.adapter.constructor.defaults.streaming; } /** * Complete a chat request. * @param {Object} req - Request parameters * @returns {Promise} Completion result */ async complete(req) { const resp = await this.invoke_adapter_method("complete", req); if (resp.error) { throw normalize_error(resp.error); } return resp; } /** * Stream chat responses. * @param {Object} req - Request parameters * @param {Object} handlers - Event handlers for streaming * @param {Function} handlers.chunk - Handler for chunks: receives response object * @param {Function} handlers.error - Handler for errors: receives error object * @param {Function} handlers.done - Handler for completion: receives final response object * @returns {Promise} Complete response text */ async stream(req, handlers = {}) { return await this.invoke_adapter_method("stream", req, handlers); } /** * Stop active stream. */ stop_stream() { this.invoke_adapter_method("stop_stream"); } /** * Count tokens in input text. * @param {string|Object} input - Text to count tokens for * @returns {Promise} Token count */ async count_tokens(input) { return await this.invoke_adapter_method("count_tokens", input); } /** * Test if API key is valid. * @returns {Promise} True if API key is valid */ async test_api_key() { await this.invoke_adapter_method("test_api_key"); this.re_render_settings(); } /** * Get default model key. * @returns {string} Default model key */ get default_model_key() { return this.adapter.constructor.defaults.default_model; } /** * Get current settings. * @returns {Object} Settings object */ get settings() { return this.opts.settings; } /** * Get settings configuration. * @returns {Object} Settings configuration object */ get settings_config() { const _settings_config = { adapter: { name: "Chat Model Platform", type: "dropdown", description: "Select a platform/provider for chat models.", options_callback: "get_platforms_as_options", is_scope: true, // trigger re-render of settings when changed callback: "adapter_changed" }, // Merge adapter-specific settings ...this.adapter.settings_config || {} }; return this.process_settings_config(_settings_config); } /** * Process setting key. * @param {string} key - Setting key * @returns {string} Processed key */ process_setting_key(key) { return key.replace(/\[CHAT_ADAPTER\]/g, this.adapter_name); } }; // node_modules/obsidian-smart-env/node_modules/smart-chat-model/streamer.js var SmartStreamer = class { constructor(url, options = {}) { const { method = "GET", headers = {}, body = null, withCredentials = false } = options; this.url = url; this.method = method; this.headers = headers; this.body = body; this.withCredentials = withCredentials; this.listeners = {}; this.readyState = this.CONNECTING; this.progress = 0; this.chunk = ""; this.last_event_id = ""; this.xhr = null; this.FIELD_SEPARATOR = ":"; this.INITIALIZING = -1; this.CONNECTING = 0; this.OPEN = 1; this.CLOSED = 2; this.chunk_accumulator = ""; this.chunk_splitting_regex = options.chunk_splitting_regex || /(\r\n|\n|\r)/g; } /** * Adds an event listener for the specified event type. * * @param {string} type - The type of the event. * @param {Function} listener - The listener function to be called when the event is triggered. */ addEventListener(type, listener) { if (!this.listeners[type]) this.listeners[type] = []; if (!this.listeners[type].includes(listener)) this.listeners[type].push(listener); } /** * Removes an event listener from the SmartStreamer instance. * * @param {string} type - The type of event to remove the listener from. * @param {Function} listener - The listener function to remove. */ removeEventListener(type, listener) { if (!this.listeners[type]) return; this.listeners[type] = this.listeners[type].filter((callback) => callback !== listener); if (this.listeners[type].length === 0) delete this.listeners[type]; } /** * Dispatches an event to the appropriate event handlers. * * @param {Event} event - The event to be dispatched. * @returns {boolean} - Returns true if the event was successfully dispatched, false otherwise. */ dispatchEvent(event) { if (!event) return true; event.source = this; const onHandler = "on" + event.type; if (Object.prototype.hasOwnProperty.call(this, onHandler)) { this[onHandler].call(this, event); if (event.defaultPrevented) return false; } if (this.listeners[event.type]) { this.listeners[event.type].forEach((callback) => { callback(event); return !event.defaultPrevented; }); } return true; } /** * Initiates the streaming process. */ stream() { this.#setReadyState(this.CONNECTING); this.xhr = new XMLHttpRequest(); this.xhr.addEventListener("progress", this.#onStreamProgress.bind(this)); this.xhr.addEventListener("load", this.#onStreamLoaded.bind(this)); this.xhr.addEventListener("readystatechange", this.#checkStreamClosed.bind(this)); this.xhr.addEventListener("error", this.#onStreamFailure.bind(this)); this.xhr.addEventListener("abort", this.#onStreamAbort.bind(this)); this.xhr.open(this.method, this.url); for (const header in this.headers) { this.xhr.setRequestHeader(header, this.headers[header]); } if (this.last_event_id) this.xhr.setRequestHeader("Last-Event-ID", this.last_event_id); this.xhr.withCredentials = this.withCredentials; this.xhr.send(this.body); } /** * Ends the streamer connection. * Aborts the current XHR request and sets the ready state to CLOSED. */ end() { if (this.readyState === this.CLOSED) return; this.xhr.abort(); this.xhr = null; this.#setReadyState(this.CLOSED); } // private methods #setReadyState(state) { const event = new CustomEvent("readyStateChange"); event.readyState = state; this.readyState = state; this.dispatchEvent(event); } #onStreamFailure(e) { const event = new CustomEvent("error"); try { const parsed = JSON.parse(e.currentTarget.response); if (typeof parsed === "object") { event.data = parsed; } else { event.data = e.currentTarget.response; } } catch { event.data = e.currentTarget.response; } this.dispatchEvent(event); this.end(); } #onStreamAbort(e) { const event = new CustomEvent("abort"); this.end(); } #onStreamProgress(e) { if (!this.xhr) return; if (this.xhr.status !== 200) { this.#onStreamFailure(e); return; } if (this.readyState === this.CONNECTING) { this.dispatchEvent(new CustomEvent("open")); this.#setReadyState(this.OPEN); } const data = this.xhr.responseText.substring(this.progress); this.progress += data.length; const parts = data.split(this.chunk_splitting_regex); parts.forEach((part, index) => { if (part.trim().length === 0) { if (this.chunk) { this.dispatchEvent(this.#parseEventChunk(this.chunk.trim())); this.chunk = ""; } } else { this.chunk += part; if (index === parts.length - 1 && this.xhr.readyState === XMLHttpRequest.DONE) { this.dispatchEvent(this.#parseEventChunk(this.chunk.trim())); this.chunk = ""; } } }); } #onStreamLoaded(e) { this.#onStreamProgress(e); this.dispatchEvent(this.#parseEventChunk(this.chunk)); this.chunk = ""; } #parseEventChunk(chunk) { if (!chunk) return console.log("no chunk"); const event = new CustomEvent("message"); event.data = chunk; event.last_event_id = this.last_event_id; return event; } #checkStreamClosed() { if (!this.xhr) return; if (this.xhr.readyState === XMLHttpRequest.DONE) this.#setReadyState(this.CLOSED); } }; // node_modules/obsidian-smart-env/node_modules/smart-chat-model/adapters/_adapter.js var SmartChatModelAdapter = class extends SmartModelAdapter { /** * @override in sub-class with adapter-specific default configurations * @property {string} id - The adapter identifier * @property {string} description - Human-readable description * @property {string} type - Adapter type ("API") * @property {string} endpoint - API endpoint * @property {boolean} streaming - Whether streaming is supported * @property {string} adapter - Adapter identifier * @property {string} models_endpoint - Endpoint for retrieving models * @property {string} default_model - Default model to use * @property {string} signup_url - URL for API key signup */ static defaults = {}; /** * Create a SmartChatModelAdapter instance. * @param {SmartChatModel} model - The parent SmartChatModel instance */ constructor(model) { super(model); this.smart_chat = model; this.main = model; } /** * Complete a chat request. * @abstract * @param {Object} req - Request parameters * @returns {Promise} Completion result */ async complete(req) { throw new Error("complete not implemented"); } /** * Count tokens in input text. * @abstract * @param {string|Object} input - Text to count tokens for * @returns {Promise} Token count */ async count_tokens(input) { throw new Error("count_tokens not implemented"); } /** * Stream chat responses. * @abstract * @param {Object} req - Request parameters * @param {Object} handlers - Event handlers for streaming * @returns {Promise} Complete response text */ async stream(req, handlers = {}) { throw new Error("stream not implemented"); } /** * Test if API key is valid. * @abstract * @returns {Promise} True if API key is valid */ async test_api_key() { throw new Error("test_api_key not implemented"); } /** * Refresh available models. */ refresh_models() { console.log("refresh_models"); this.get_models(true); } /** * Get settings configuration. * @returns {Object} Settings configuration object */ get settings_config() { return { "[CHAT_ADAPTER].model_key": { name: "Chat Model", type: "dropdown", description: "Select a chat model.", options_callback: "adapter.get_models_as_options", callback: "reload_model", default: this.constructor.defaults.default_model }, "[CHAT_ADAPTER].refresh_models": { name: "Refresh Models", type: "button", description: "Refresh the list of available models.", callback: "adapter.refresh_models" } }; } }; // node_modules/obsidian-smart-env/node_modules/smart-chat-model/adapters/_api.js var MODEL_ADAPTER_CACHE = {}; var MODELS_DEV_CACHE = { data: null, fetched_at: 0 }; var SmartChatModelApiAdapter = class extends SmartChatModelAdapter { constructor(model) { super(model); this.model_data_loaded_at = 0; } /** * Get the request adapter class. * @returns {SmartChatModelRequestAdapter} The request adapter class */ get req_adapter() { return SmartChatModelRequestAdapter; } /** * Get the response adapter class. * @returns {SmartChatModelResponseAdapter} The response adapter class */ get res_adapter() { return SmartChatModelResponseAdapter; } /** * Get or initialize the HTTP adapter. * @returns {SmartHttpRequest} The HTTP adapter instance */ get http_adapter() { if (!this._http_adapter) { if (this.model.http_adapter) this._http_adapter = this.model.http_adapter; else if (this.model.opts.http_adapter) this._http_adapter = this.model.opts.http_adapter; else this._http_adapter = new SmartHttpRequest({ adapter: SmartHttpRequestFetchAdapter }); } return this._http_adapter; } /** * Get the settings configuration for the API adapter. * @deprecated migrating to module export * @returns {Object} Settings configuration object with API key and other settings */ get settings_config() { return { ...super.settings_config, "[CHAT_ADAPTER].api_key": { name: "API Key", type: "password", description: "Enter your API key for the chat model platform.", callback: "test_api_key", is_scope: true // trigger re-render of settings when changed (reload models dropdown) } }; } /** * Count tokens in the input text. * @abstract * @param {string|Object} input - Text or message object to count tokens for * @returns {Promise} Number of tokens in the input */ async count_tokens(input) { throw new Error("count_tokens not implemented"); } /** * Get the parameters for requesting available models. * @returns {Object} Request parameters for models endpoint */ get models_request_params() { return { url: this.models_endpoint, method: this.models_endpoint_method, headers: { "Authorization": `Bearer ${this.api_key}` } }; } async get_enriched_model_data() { const provider_key = this.constructor.models_dev_key || this.constructor.key; await this.get_models_dev_index(); const provider_data = MODELS_DEV_CACHE.data[provider_key] || {}; const get_limit_i = (model) => model.limit?.context || 1e4; const get_limit_o = (model) => model.limit?.output || 1e4; const get_multimodal = (model) => model.modalities?.input?.includes("image") || false; if (Object.keys(this.model_data || {}).length > 0) { for (const [key, model] of Object.entries(this.model_data)) { const enriched = provider_data?.models?.[model.id]; if (!enriched) continue; this.model_data[key].models_dev = enriched; this.model_data[key].name = enriched.name || model.name; this.model_data[key].max_input_tokens = get_limit_i(enriched); this.model_data[key].max_output_tokens = get_limit_o(enriched); this.model_data[key].multimodal = get_multimodal(enriched); this.model_data[key].cost = enriched.cost; } } else { for (const [key, model] of Object.entries(provider_data?.models || {})) { this.model_data[key] = { ...model, model_name: model.name, description: model.name, max_input_tokens: get_limit_i(model), max_output_tokens: get_limit_o(model), multimodal: get_multimodal(model) }; } } return this.model_data; } valid_model_data() { return typeof this.model_data === "object" && Object.keys(this.model_data || {}).length > 0 && this.model_data_loaded_at && Date.now() - this.model_data_loaded_at < 1 * 60 * 60 * 1e3; } /** * Get available models from the API. * @param {boolean} [refresh=false] - Whether to refresh cached models * @returns {Promise} Map of model objects */ async get_models(refresh = false) { if (!refresh && this.valid_model_data()) return this.model_data; if (this.api_key) { let response; try { response = await this.http_adapter.request(this.models_request_params); this.model_data = this.parse_model_data(await response.json()); } catch (error) { console.error("Failed to fetch model data:", { error, response }); } } this.model_data = await this.get_enriched_model_data(); this.model_data_loaded_at = Date.now(); if (this.model.data) { this.model.data.provider_models = this.model_data; } if (this.valid_model_data() && typeof this.model.re_render_settings === "function") setTimeout(() => { this.model.re_render_settings(); }, 100); else console.warn("Invalid model data, not re-rendering settings"); return this.model_data; } /** * Parses the raw model data from OpenAI API and transforms it into a more usable format. * @param {Object} model_data - The raw model data received from OpenAI API. * @abstract * @returns {Array} An array of parsed model objects with the following properties: * @property {string} model_name - The name/ID of the model as returned by the API. * @property {string} id - The id used to identify the model (usually same as model_name). * @property {boolean} multimodal - Indicates if the model supports multimodal inputs. * @property {number} [max_input_tokens] - The maximum number of input tokens the model can process. * @property {string} [description] - A description of the model's context and output capabilities. */ parse_model_data(model_data) { throw new Error("parse_model_data not implemented"); } /** * Complete a chat request. * @param {Object} req - Request parameters * @returns {Promise} Completion response in OpenAI format */ async complete(req) { const _req = new this.req_adapter(this, { ...req, stream: false }); const request_params = _req.to_platform(); const http_resp = await this.http_adapter.request(request_params); if (!http_resp) return null; const _res = new this.res_adapter(this, await http_resp.json()); try { const resp = _res.to_openai(); return resp; } catch (error) { const normalized_error = normalize_error(error?.data || error); console.error("Error in SmartChatModelApiAdapter.complete():", { normalized_error, error }); console.error(http_resp); return normalized_error; } } // STREAMING /** * Stream chat responses. * @param {Object} req - Request parameters * @param {Object} handlers - Event handlers for streaming * @param {Function} handlers.chunk - Handler for response objects * @param {Function} handlers.error - Handler for errors * @param {Function} handlers.done - Handler for completion * @returns {Promise} Complete response object */ async stream(req, handlers = {}) { const _req = new this.req_adapter(this, req); const request_params = _req.to_platform(true); if (this.streaming_chunk_splitting_regex) request_params.chunk_splitting_regex = this.streaming_chunk_splitting_regex; return await new Promise((resolve, reject) => { try { this.active_stream = new SmartStreamer(this.endpoint_streaming, request_params); const resp_adapter = new this.res_adapter(this); this.active_stream.addEventListener("message", async (e) => { if (this.is_end_of_stream(e)) { await resp_adapter.handle_chunk(e.data); this.stop_stream(); const final_resp = resp_adapter.to_openai(); handlers.done && await handlers.done(final_resp); resolve(final_resp); return; } try { const raw = resp_adapter.handle_chunk(e.data); handlers.chunk && await handlers.chunk({ ...resp_adapter.to_openai(), raw }); } catch (error) { const normalized_error = normalize_error({ ...e.data, ...error }); console.error("Error processing stream chunk:", { e, error, normalized_error }); handlers.error && handlers.error(normalized_error); this.stop_stream(); reject(normalized_error); } }); this.active_stream.addEventListener("error", (e) => { console.error("Stream error:", e); const normalized_error = normalize_error(e?.data || e); handlers.error && handlers.error(normalized_error); this.stop_stream(); reject(normalized_error); }); this.active_stream.stream(); } catch (err) { console.error("Failed to start stream:", err); const normalized_error = normalize_error(err?.data || err); handlers.error && handlers.error(normalized_error); this.stop_stream(); reject(normalized_error); } }); } /** * Check if a stream event indicates end of stream. * @param {Event} event - Stream event * @returns {boolean} True if end of stream */ is_end_of_stream(event) { return event.data === "data: [DONE]"; } /** * Stop active stream. */ stop_stream() { if (this.active_stream) { this.active_stream.end(); this.active_stream = null; } } /** * Get the API key. * @returns {string} The API key. */ get api_key() { return this.model.api_key || this.main.opts.api_key; } get models_endpoint() { return this.constructor.defaults.models_endpoint; } get models_endpoint_method() { return "POST"; } /** * Get the endpoint URL. * @returns {string} The endpoint URL. */ get endpoint() { return this.constructor.defaults.endpoint; } /** * Get the streaming endpoint URL. * @returns {string} The streaming endpoint URL. */ get endpoint_streaming() { return this.constructor.defaults.endpoint_streaming || this.endpoint; } /** * Get the maximum output tokens. * @returns {number} The maximum output tokens. */ get max_output_tokens() { return this.model.data.max_output_tokens || 3e3; } async get_models_dev_index(ttl_ms = 60 * 60 * 1e3) { const now = Date.now(); if (MODELS_DEV_CACHE?.data && now - MODELS_DEV_CACHE?.fetched_at < ttl_ms) { return MODELS_DEV_CACHE.data; } try { const req = { url: "https://models.dev/api.json", method: "GET", headers: { "Content-Type": "application/json" } }; const resp = await this.http_adapter.request(req); const data = await resp.json(); MODELS_DEV_CACHE.data = data; MODELS_DEV_CACHE.fetched_at = now; console.log({ MODELS_DEV_CACHE }); return data; } catch (err) { console.warn("models.dev fetch failed; continuing without enrichment", err); return MODELS_DEV_CACHE.data || []; } } /** * Get available models as dropdown options synchronously. * @returns {Array} Array of model options. */ get_models_as_options() { if (Object.keys(this.model_data || {}).length) { return Object.entries(this.model_data).map(([id, model]) => ({ value: id, name: model.name || id })).sort((a, b) => a.name.localeCompare(b.name)); } this.get_models(true); return [{ value: "", name: "No models currently available" }]; } get model_data() { if (!MODEL_ADAPTER_CACHE[this.constructor.key]) MODEL_ADAPTER_CACHE[this.constructor.key] = {}; return MODEL_ADAPTER_CACHE[this.constructor.key]; } set model_data(data) { if (!MODEL_ADAPTER_CACHE[this.constructor.key]) MODEL_ADAPTER_CACHE[this.constructor.key] = {}; MODEL_ADAPTER_CACHE[this.constructor.key] = data; } }; var SmartChatModelRequestAdapter = class { /** * @constructor * @param {SmartChatModelAdapter} adapter - The SmartChatModelAdapter instance * @param {Object} req - The incoming request object */ constructor(adapter, req = {}) { this.adapter = adapter; this._req = req; } /** * Get the messages array from the request * @returns {Array} Array of message objects */ get messages() { return this._req.messages || []; } /** * Get the model identifier * @returns {string} Model ID */ get model_id() { return this._req.model || this.adapter.model.model_key || this.adapter.model.data.id; } /** * Get the temperature setting * @returns {number} Temperature value */ get temperature() { return this._req.temperature; } /** * Get the maximum tokens setting * @returns {number} Max tokens value */ get max_tokens() { return this._req.max_tokens || this.adapter.max_output_tokens; } /** * Get the streaming flag * @returns {boolean} Whether to stream responses */ get stream() { return this._req.stream; } /** * Get the tools array * @returns {Array|null} Array of tool objects or null */ get tools() { return this._req.tools || null; } /** * Get the tool choice setting * @returns {string|Object|null} Tool choice configuration */ get tool_choice() { return this._req.tool_choice || null; } get frequency_penalty() { return this._req.frequency_penalty; } get presence_penalty() { return this._req.presence_penalty; } get top_p() { return this._req.top_p; } /** * Get request headers * @returns {Object} Headers object */ get_headers() { const headers = { "Content-Type": "application/json", ...this.adapter.constructor.defaults.headers || {} }; const api_key_header = this.adapter.constructor.defaults.api_key_header; if (api_key_header !== "none") { if (api_key_header) { headers[api_key_header] = this.adapter.api_key; } else if (this.adapter.api_key) { headers["Authorization"] = `Bearer ${this.adapter.api_key}`; } } return headers; } /** * Convert request to platform-specific format * @returns {Object} Platform-specific request parameters */ to_platform(streaming = false) { return this.to_openai(streaming); } /** * Convert request to OpenAI format * @returns {Object} Request parameters in OpenAI format */ to_openai(streaming = false) { const body = { messages: this._transform_messages_to_openai(), model: this.model_id, // TODO max_completion_tokens temperature: this.temperature, stream: streaming, ...this.tools && { tools: this._transform_tools_to_openai() } }; if (body.tools?.length > 0 && this.tool_choice && this.tool_choice !== "none") { body.tool_choice = this.tool_choice; } if (this.model_id?.startsWith("o1-")) { body.messages = body.messages.filter((m) => m.role !== "system"); delete body.temperature; } if (typeof this._req.top_p === "number") body.top_p = this._req.top_p; if (typeof this._req.presence_penalty === "number") body.presence_penalty = this._req.presence_penalty; if (typeof this._req.frequency_penalty === "number") body.frequency_penalty = this._req.frequency_penalty; return { url: this.adapter.endpoint, method: "POST", headers: this.get_headers(), body: JSON.stringify(body) }; } /** * Transform messages to OpenAI format * @returns {Array} Transformed messages array * @private */ _transform_messages_to_openai() { return this.messages.map((message) => this._transform_single_message_to_openai(message)); } /** * Transform a single message to OpenAI format * @param {Object} message - Message object to transform * @returns {Object} Transformed message object * @private */ _transform_single_message_to_openai(message) { const transformed = { role: this._get_openai_role(message.role), content: this._get_openai_content(message) }; if (message.name) transformed.name = message.name; if (message.tool_calls) transformed.tool_calls = this._transform_tool_calls_to_openai(message.tool_calls); if (message.image_url) transformed.image_url = message.image_url; if (message.tool_call_id) transformed.tool_call_id = message.tool_call_id; return transformed; } /** * Get the OpenAI role for a given role. * @param {string} role - The role to transform. * @returns {string} The transformed role. * @private */ _get_openai_role(role) { return role; } /** * Get the OpenAI content for a given content. * @param {string} content - The content to transform. * @returns {string} The transformed content. * @private */ _get_openai_content(message) { return message.content; } /** * Transform tool calls to OpenAI format. * @param {Array} tool_calls - Array of tool call objects. * @returns {Array} Transformed tool calls array. * @private */ _transform_tool_calls_to_openai(tool_calls) { return tool_calls.map((tool_call) => ({ id: tool_call.id, type: tool_call.type, function: { name: tool_call.function.name, arguments: tool_call.function.arguments } })); } /** * Transform tools to OpenAI format. * @returns {Array} Transformed tools array. * @private */ _transform_tools_to_openai() { return this.tools.map((tool) => ({ type: tool.type, function: { name: tool.function.name, description: tool.function.description, parameters: tool.function.parameters } })); } }; var SmartChatModelResponseAdapter = class { // must be getter to prevent erroneous assignment static get platform_res() { return { id: "", object: "chat.completion", created: 0, model: "", choices: [], usage: {} }; } /** * @constructor * @param {SmartChatModelAdapter} adapter - The SmartChatModelAdapter instance * @param {Object} res - The response object */ constructor(adapter, res, status = null) { this.adapter = adapter; this._res = res || this.constructor.platform_res; this.status = status; } /** * Get response ID * @returns {string|null} Response ID */ get id() { return this._res.id || null; } /** * Get response object type * @returns {string|null} Object type */ get object() { return this._res.object || null; } /** * Get creation timestamp * @returns {number|null} Creation timestamp */ get created() { return this._res.created || null; } /** * Get response choices * @returns {Array} Array of choice objects */ get choices() { return this._res.choices || []; } /** * Get first tool call if present * @returns {Object|null} Tool call object */ get tool_call() { return this.message.tool_calls?.[0] || null; } /** * Get tool name from first tool call * @returns {string|null} Tool name */ get tool_name() { return this.tool_call?.tool_name || null; } /** * Get tool call parameters * @returns {Object|null} Tool parameters */ get tool_call_content() { return this.tool_call?.parameters || null; } /** * Get token usage statistics * @returns {Object|null} Usage statistics */ get usage() { return this._res.usage || null; } get error() { return this._res.error || null; } /** * Convert response to OpenAI format * @returns {Object} Response in OpenAI format */ to_openai() { if (this.error) return { error: normalize_error(this.error, this.status) }; const res = { id: this.id, object: this.object, created: this.created, choices: this._transform_choices_to_openai(), usage: this._transform_usage_to_openai(), raw: this._res }; return res; } /** * Parse chunk adds delta to content as expected output format */ handle_chunk(chunk) { if (chunk === "data: [DONE]") return; chunk = JSON.parse(chunk.split("data: ")[1] || "{}"); if (Object.keys(chunk).length === 0) return; if (!this._res.choices[0]) { this._res.choices.push({ message: { index: 0, role: "assistant", content: "" } }); } if (!this._res.id) { this._res.id = chunk.id; } let raw; if (chunk.choices?.[0]?.delta?.content) { const content = chunk.choices[0].delta.content; raw = content; this._res.choices[0].message.content += content; } if (chunk.choices?.[0]?.delta?.tool_calls) { if (!this._res.choices[0].message.tool_calls) { this._res.choices[0].message.tool_calls = [{ id: "", type: "function", function: { name: "", arguments: "" } }]; } if (chunk.choices[0].delta.tool_calls[0].id) { this._res.choices[0].message.tool_calls[0].id += chunk.choices[0].delta.tool_calls[0].id; } if (chunk.choices[0].delta.tool_calls[0].function.name) { this._res.choices[0].message.tool_calls[0].function.name += chunk.choices[0].delta.tool_calls[0].function.name; } if (chunk.choices[0].delta.tool_calls[0].function.arguments) { this._res.choices[0].message.tool_calls[0].function.arguments += chunk.choices[0].delta.tool_calls[0].function.arguments; } } return raw; } /** * Transform choices to OpenAI format. * @returns {Array} Transformed choices array. * @private */ _transform_choices_to_openai() { return this.choices.map((choice) => ({ index: choice.index, message: this._transform_message_to_openai(choice.message), finish_reason: this._get_openai_finish_reason(choice.finish_reason) })); } /** * Transform a single message to OpenAI format. * @param {Object} message - The message object to transform. * @returns {Object} Transformed message object. * @private */ _transform_message_to_openai(message = {}) { const transformed = { role: this._get_openai_role(message.role), content: this._get_openai_content(message) }; if (message.name) transformed.name = message.name; if (message.tool_calls) transformed.tool_calls = this._transform_tool_calls_to_openai(message.tool_calls); if (message.image_url) transformed.image_url = message.image_url; return transformed; } /** * Get the OpenAI role for a given role. * @param {string} role - The role to transform. * @returns {string} The transformed role. * @private */ _get_openai_role(role) { return role; } /** * Get the OpenAI content for a given content. * @param {string} content - The content to transform. * @returns {string} The transformed content. * @private */ _get_openai_content(message) { return message.content; } /** * Get the OpenAI finish reason for a given finish reason. * @param {string} finish_reason - The finish reason to transform. * @returns {string} The transformed finish reason. * @private */ _get_openai_finish_reason(finish_reason) { return finish_reason; } /** * Transform usage to OpenAI format. * @returns {Object} Transformed usage object. * @private */ _transform_usage_to_openai() { return this.usage; } /** * Transform tool calls to OpenAI format. * @param {Array} tool_calls - Array of tool call objects. * @returns {Array} Transformed tool calls array. * @private */ _transform_tool_calls_to_openai(tool_calls) { return tool_calls.map((tool_call) => ({ id: tool_call.id, type: tool_call.type, function: { name: tool_call.function.name, arguments: tool_call.function.arguments } })); } }; // node_modules/obsidian-smart-env/node_modules/smart-chat-model/adapters/anthropic.js var SmartChatModelAnthropicAdapter = class extends SmartChatModelApiAdapter { static key = "anthropic"; static defaults = { description: "Anthropic Claude", type: "API", endpoint: "https://api.anthropic.com/v1/messages", streaming: true, api_key_header: "x-api-key", headers: { "anthropic-version": "2023-06-01", "anthropic-beta": "tools-2024-04-04", "anthropic-dangerous-direct-browser-access": true }, adapter: "Anthropic", models_endpoint: false, default_model: "claude-opus-4-1-20250805", signup_url: "https://console.anthropic.com/login?returnTo=%2Fsettings%2Fkeys" }; /** * Get request adapter class * @returns {typeof SmartChatModelAnthropicRequestAdapter} Request adapter class */ get req_adapter() { return SmartChatModelAnthropicRequestAdapter; } /** * Get response adapter class * @returns {typeof SmartChatModelAnthropicResponseAdapter} Response adapter class */ res_adapter = SmartChatModelAnthropicResponseAdapter; /** * Get available models (hardcoded list) and enrich via models.dev * @returns {Promise} Map of model objects */ async get_models() { try { this.model_data = await this.get_enriched_model_data(); this.model_data_loaded_at = Date.now(); this.model.data.provider_models = this.model_data; setTimeout(() => { this.model.re_render_settings(); }, 100); return this.model_data; } catch { return this.anthropic_models; } } is_end_of_stream(event) { return event.data.includes("message_stop"); } /** * Get hardcoded list of available models * @deprecated use get_enriched_model_data() instead (remove after no-incidents) * @returns {Object} Map of model objects with capabilities and limits */ get anthropic_models() { return { // ── Claude 4 family ────────────────────────────────────────────────────── "claude-opus-4-1-20250805": { name: "Claude Opus 4.1 (2025-08-05)", id: "claude-opus-4-1-20250805", model_name: "claude-opus-4-1-20250805", description: "Anthropic Claude Opus 4.1 snapshot (2025-08-05)", max_input_tokens: 2e5, max_output_tokens: 32e3, multimodal: true }, "claude-opus-4-20250514": { name: "Claude Opus 4 (2025-05-14)", id: "claude-opus-4-20250514", model_name: "claude-opus-4-20250514", description: "Anthropic Claude Opus 4 snapshot (2025-05-14)", max_input_tokens: 2e5, max_output_tokens: 32e3, multimodal: true }, "claude-sonnet-4-20250514": { name: "Claude Sonnet 4 (2025-05-14)", id: "claude-sonnet-4-20250514", model_name: "claude-sonnet-4-20250514", description: "Anthropic Claude Sonnet 4 snapshot (2025-05-14)", max_input_tokens: 2e5, max_output_tokens: 64e3, multimodal: true }, // ── Claude 3.7 family ─────────────────────────────────────────────────── "claude-3-7-sonnet-latest": { name: "Claude 3.7 Sonnet (latest)", id: "claude-3-7-sonnet-latest", model_name: "claude-3-7-sonnet-latest", description: "Anthropic Claude 3.7 Sonnet (rolling-latest)", max_input_tokens: 2e5, max_output_tokens: 64e3, multimodal: true }, "claude-3-7-sonnet-20250219": { name: "Claude 3.7 Sonnet (2025-02-19)", id: "claude-3-7-sonnet-20250219", model_name: "claude-3-7-sonnet-20250219", description: "Anthropic Claude 3.7 Sonnet snapshot (2025-02-19)", max_input_tokens: 2e5, max_output_tokens: 64e3, multimodal: true }, // ── Claude 3.5 family ─────────────────────────────────────────────────── "claude-3-5-sonnet-latest": { name: "Claude 3.5 Sonnet (latest)", id: "claude-3-5-sonnet-latest", model_name: "claude-3-5-sonnet-latest", description: "Anthropic Claude 3.5 Sonnet (rolling-latest)", max_input_tokens: 2e5, max_output_tokens: 8192, multimodal: true }, "claude-3-5-sonnet-20241022": { name: "Claude 3.5 Sonnet (2024-10-22)", id: "claude-3-5-sonnet-20241022", model_name: "claude-3-5-sonnet-20241022", description: "Anthropic Claude 3.5 Sonnet snapshot (2024-10-22)", max_input_tokens: 2e5, max_output_tokens: 8192, multimodal: true }, "claude-3-5-haiku-latest": { name: "Claude 3.5 Haiku (latest)", id: "claude-3-5-haiku-latest", model_name: "claude-3-5-haiku-latest", description: "Anthropic Claude 3.5 Haiku (rolling-latest)", max_input_tokens: 2e5, max_output_tokens: 8192 }, "claude-3-5-haiku-20241022": { name: "Claude 3.5 Haiku (2024-10-22)", id: "claude-3-5-haiku-20241022", model_name: "claude-3-5-haiku-20241022", description: "Anthropic Claude 3.5 Haiku snapshot (2024-10-22)", max_input_tokens: 2e5, max_output_tokens: 8192 }, // ── Claude 3 family ───────────────────────────────────────────────────── "claude-3-opus-latest": { name: "Claude 3 Opus (latest)", id: "claude-3-opus-latest", model_name: "claude-3-opus-latest", description: "Anthropic Claude 3 Opus (rolling-latest)", max_input_tokens: 2e5, max_output_tokens: 4096, multimodal: true }, "claude-3-opus-20240229": { name: "Claude 3 Opus (2024-02-29)", id: "claude-3-opus-20240229", model_name: "claude-3-opus-20240229", description: "Anthropic Claude 3 Opus snapshot (2024-02-29)", max_input_tokens: 2e5, max_output_tokens: 4096, multimodal: true }, "claude-3-sonnet-20240229": { name: "Claude 3 Sonnet (2024-02-29)", id: "claude-3-sonnet-20240229", model_name: "claude-3-sonnet-20240229", description: "Anthropic Claude 3 Sonnet snapshot (2024-02-29)", max_input_tokens: 2e5, max_output_tokens: 4096, multimodal: true }, "claude-3-haiku-20240307": { name: "Claude 3 Haiku (2024-03-07)", id: "claude-3-haiku-20240307", model_name: "claude-3-haiku-20240307", description: "Anthropic Claude 3 Haiku snapshot (2024-03-07)", max_input_tokens: 2e5, max_output_tokens: 4096, multimodal: true } }; } }; var SmartChatModelAnthropicRequestAdapter = class extends SmartChatModelRequestAdapter { /** * Convert request to Anthropic format * @returns {Object} Request parameters in Anthropic format */ to_platform(streaming = false) { return this.to_anthropic(streaming); } /** * Convert request to Anthropic format * @returns {Object} Request parameters in Anthropic format */ to_anthropic(streaming = false) { this.anthropic_body = { model: this.model_id, max_tokens: this.max_tokens, temperature: this.temperature, stream: streaming }; this.anthropic_body.messages = this._transform_messages_to_anthropic(); if (this.tools) { this.anthropic_body.tools = this._transform_tools_to_anthropic(); } if (this.tool_choice) { this.anthropic_body.tool_choice = this.tool_choice === "auto" ? { type: "auto" } : { type: "tool", name: this.tool_choice.function.name }; } return { url: this.adapter.endpoint, method: "POST", headers: this.get_headers(), body: JSON.stringify(this.anthropic_body) }; } /** * Transform messages to Anthropic format * @returns {Array} Messages in Anthropic format * @private */ _transform_messages_to_anthropic() { let anthropic_messages = []; for (const message of this.messages) { if (message.role === "system") { if (!this.anthropic_body.system) this.anthropic_body.system = ""; else this.anthropic_body.system += "\n\n"; this.anthropic_body.system += Array.isArray(message.content) ? message.content.map((part) => part.text).join("\n") : message.content; } else if (message.role === "tool") { const msg = { role: "user", content: [ { type: "tool_result", tool_use_id: message.tool_call_id, content: message.content } ] }; anthropic_messages.push(msg); } else { const msg = { role: this._get_anthropic_role(message.role), content: this._get_anthropic_content(message.content) }; if (message.tool_calls?.length > 0) msg.content = this._transform_tool_calls_to_content(message.tool_calls); anthropic_messages.push(msg); } } return anthropic_messages; } /** * Transform tool calls to Anthropic format * @param {Array} tool_calls - Tool calls * @returns {Array} Tool calls in Anthropic format * @private */ _transform_tool_calls_to_content(tool_calls) { return tool_calls.map((tool_call) => ({ type: "tool_use", id: tool_call.id, name: tool_call.function.name, input: JSON.parse(tool_call.function.arguments) })); } /** * Transform role to Anthropic format * @param {string} role - Original role * @returns {string} Role in Anthropic format * @private */ _get_anthropic_role(role) { const role_map = { function: "assistant", // Anthropic doesn't have a function role, so we'll treat it as assistant tool: "user" }; return role_map[role] || role; } /** * Transform content to Anthropic format * @param {string|Array} content - Original content * @returns {string|Array} Content in Anthropic format * @private */ _get_anthropic_content(content) { if (Array.isArray(content)) { return content.map((item) => { if (item.type === "text") return { type: "text", text: item.text }; if (item.type === "image_url") { return { type: "image", source: { type: "base64", media_type: item.image_url.url.split(";")[0].split(":")[1], data: item.image_url.url.split(",")[1] } }; } if (item.type === "file" && item.file?.filename?.toLowerCase().endsWith(".pdf")) { if (item.file?.file_data) { return { type: "document", source: { type: "base64", media_type: "application/pdf", data: item.file.file_data.split(",")[1] } }; } } return item; }); } return content; } /** * Transform tools to Anthropic format * @returns {Array} Tools in Anthropic format * @private */ _transform_tools_to_anthropic() { if (!this.tools) return void 0; return this.tools.map((tool) => ({ name: tool.function.name, description: tool.function.description, input_schema: tool.function.parameters })); } }; var SmartChatModelAnthropicResponseAdapter = class extends SmartChatModelResponseAdapter { static get platform_res() { return { content: [], id: "", model: "", role: "assistant", stop_reason: null, stop_sequence: null, type: "message", usage: { input_tokens: 0, output_tokens: 0 } }; } /** * Convert response to OpenAI format * @returns {Object} Response in OpenAI format */ to_openai() { if (this.error) return { error: normalize_error(this.error, this.status) }; return { id: this._res.id, object: "chat.completion", created: Date.now(), choices: [ { index: 0, message: this._transform_message_to_openai(), finish_reason: this._get_openai_finish_reason(this._res.stop_reason) } ], usage: this._transform_usage_to_openai() }; } /** * Transform message to OpenAI format * @returns {Object} Message in OpenAI format * @private */ _transform_message_to_openai() { const message = { role: "assistant", content: "", tool_calls: [] }; if (Array.isArray(this._res.content)) { for (const content of this._res.content) { if (content.type === "text") { message.content += (message.content ? "\n\n" : "") + content.text; } else if (content.type === "tool_use") { message.tool_calls.push({ id: content.id, type: "function", function: { name: content.name, arguments: JSON.stringify(content.input) } }); } } } else { message.content = this._res.content; } if (message.tool_calls.length === 0) { delete message.tool_calls; } return message; } /** * Transform finish reason to OpenAI format * @param {string} stop_reason - Original finish reason * @returns {string} Finish reason in OpenAI format * @private */ _get_openai_finish_reason(stop_reason) { const reason_map = { "end_turn": "stop", "max_tokens": "length", "tool_use": "function_call" }; return reason_map[stop_reason] || stop_reason; } /** * Transform usage statistics to OpenAI format * @returns {Object} Usage statistics in OpenAI format * @private */ _transform_usage_to_openai() { if (!this._res.usage) { return { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }; } return { prompt_tokens: this._res.usage.input_tokens || 0, completion_tokens: this._res.usage.output_tokens || 0, total_tokens: (this._res.usage.input_tokens || 0) + (this._res.usage.output_tokens || 0) }; } handle_chunk(chunk) { if (!chunk.startsWith("data: ")) return; chunk = JSON.parse(chunk.slice(6)); if (!this._res.content.length) { this._res.content = [ { type: "text", text: "" } ]; } if (chunk.message?.id) { this._res.id = chunk.message.id; } if (chunk.message?.model) { this._res.model = chunk.message.model; } if (chunk.message?.role) { this._res.role = chunk.message.role; } let raw; if (chunk.delta?.type === "text_delta") { const content = chunk.delta?.text; raw = content; this._res.content[0].text += content; } if (chunk.delta?.stop_reason) { this._res.stop_reason = chunk.delta.stop_reason; } if (chunk.usage) { this._res.usage = { ...this._res.usage, ...chunk.usage }; } return raw; } }; // node_modules/obsidian-smart-env/node_modules/smart-chat-model/adapters/openai.js var EXCLUDED_PREFIXES = [ "text-", "davinci", "babbage", "ada", "curie", "dall-e", "whisper", "omni", "tts", "gpt-4o-mini-tts", "computer-use", "codex", "gpt-4o-transcribe", "gpt-4o-mini-transcribe", "gpt-4o-mini-realtime", "gpt-4o-realtime", "o4-mini-deep-research", "o3-deep-research", "gpt-image" ]; var SmartChatModelOpenaiAdapter = class extends SmartChatModelApiAdapter { static key = "openai"; static defaults = { description: "OpenAI", type: "API", endpoint: "https://api.openai.com/v1/chat/completions", streaming: true, models_endpoint: "https://api.openai.com/v1/models", default_model: "gpt-5-nano", signup_url: "https://platform.openai.com/api-keys" }; res_adapter = SmartChatModelOpenaiResponseAdapter; /** * Parse model data from OpenAI API response. * Filters for GPT models and adds context window information. * @param {Object} model_data - Raw model data from OpenAI * @returns {Object} Map of model objects with capabilities and limits */ parse_model_data(model_data) { return model_data.data.filter((model) => !EXCLUDED_PREFIXES.some((m) => model.id.startsWith(m)) && !model.id.includes("-instruct")).reduce((acc, model) => { const out = { model_name: model.id, id: model.id, multimodal: true, max_input_tokens: get_max_input_tokens(model.id) }; acc[model.id] = out; return acc; }, {}); } /** * Override the HTTP method for fetching models. */ models_endpoint_method = "GET"; /** * Test the API key by attempting to fetch models. * @returns {Promise} True if API key is valid */ async test_api_key() { const models = await this.get_models(); return models.length > 0; } /** * Get settings configuration for OpenAI adapter. * Adds image resolution setting for multimodal models. * @returns {Object} Settings configuration object */ get settings_config() { const config = super.settings_config; config["[CHAT_ADAPTER].open_ai_note"] = { name: "Note about using OpenAI", type: "html", value: "OpenAI models: Some models require extra verification steps in your OpenAI account for them to appear in the model list." }; return config; } }; function get_max_input_tokens(model_id) { if (model_id.startsWith("gpt-4.1")) { return 1e6; } if (model_id.startsWith("o")) { return 2e5; } if (model_id.startsWith("gpt-5")) { return 4e5; } if (model_id.startsWith("gpt-4o") || model_id.startsWith("gpt-4.5") || model_id.startsWith("gpt-4-turbo")) { return 128e3; } if (model_id.startsWith("gpt-4")) { return 8192; } if (model_id.startsWith("gpt-3")) { return 16385; } return 8e3; } var SmartChatModelOpenaiResponseAdapter = class extends SmartChatModelResponseAdapter { }; // node_modules/obsidian-smart-env/node_modules/smart-chat-model/adapters/azure.js var SmartChatModelAzureAdapter = class extends SmartChatModelOpenaiAdapter { static key = "azure"; static defaults = { description: "Azure OpenAI", type: "API", adapter: "AzureOpenAI", streaming: true, api_key_header: "api-key", azure_resource_name: "", azure_deployment_name: "", azure_api_version: "2024-10-01-preview", default_model: "gpt-35-turbo", signup_url: "https://learn.microsoft.com/azure/cognitive-services/openai/quickstart?tabs=command-line", models_endpoint: "https://{azure_resource_name}.openai.azure.com/openai/deployments?api-version={azure_api_version}" }; /** * Override the settings configuration to include Azure-specific fields. */ get settings_config() { return { ...super.settings_config, "[CHAT_ADAPTER].azure_resource_name": { name: "Azure Resource Name", type: "text", description: "The name of your Azure OpenAI resource (e.g. 'my-azure-openai').", default: "" }, "[CHAT_ADAPTER].azure_deployment_name": { name: "Azure Deployment Name", type: "text", description: "The name of your specific model deployment (e.g. 'gpt35-deployment').", default: "" }, "[CHAT_ADAPTER].azure_api_version": { name: "Azure API Version", type: "text", description: "The API version for Azure OpenAI (e.g. '2024-10-01-preview').", default: "2024-10-01-preview" } }; } /** * Build the endpoint dynamically based on Azure settings. * Example: * https://.openai.azure.com/openai/deployments//chat/completions?api-version=2023-05-15 */ get endpoint() { const { azure_resource_name, azure_deployment_name, azure_api_version } = this.model.data; return `https://${azure_resource_name}.openai.azure.com/openai/deployments/${azure_deployment_name}/chat/completions?api-version=${azure_api_version}`; } /** * For streaming, we can reuse the same endpoint. * The request body includes `stream: true` which the base class uses. */ get endpoint_streaming() { return this.endpoint; } /** * The models endpoint for retrieving a list of your deployments. * E.g.: * https://.openai.azure.com/openai/deployments?api-version=2023-05-15 */ get models_endpoint() { const { azure_resource_name, azure_api_version } = this.model.data; return `https://${azure_resource_name}.openai.azure.com/openai/deployments?api-version=${azure_api_version}`; } /** * Azure returns a list of deployments in the shape: * { * "object": "list", * "data": [ * { * "id": "mydeployment", * "model": "gpt-35-turbo", * "status": "succeeded", * "createdAt": ... * "updatedAt": ... * ... * }, * ... * ] * } * We'll parse them into a dictionary keyed by deployment ID. */ parse_model_data(model_data) { if (model_data.object !== "list" || !Array.isArray(model_data.data)) { return { "_": { id: "No deployments found." } }; } const parsed = {}; for (const d of model_data.data) { parsed[d.id] = { model_name: d.id, id: d.id, raw: d, // You can add more details if you want: description: `Model: ${d.model}, Status: ${d.status}`, // Hard to guess tokens; omit or guess: max_input_tokens: 4e3 }; } return parsed; } }; // node_modules/obsidian-smart-env/node_modules/smart-chat-model/adapters/google.js var SmartChatModelGoogleAdapter = class extends SmartChatModelApiAdapter { static key = "google"; static defaults = { description: "Google (Gemini)", type: "API", api_key_header: "none", endpoint: "https://generativelanguage.googleapis.com/v1beta/models/MODEL_NAME:generateContent", endpoint_streaming: "https://generativelanguage.googleapis.com/v1beta/models/MODEL_NAME:streamGenerateContent", streaming: true, adapter: "Gemini", models_endpoint: "https://generativelanguage.googleapis.com/v1beta/models", default_model: "gemini-1.5-pro", signup_url: "https://ai.google.dev/" }; streaming_chunk_splitting_regex = /(\r\n|\n|\r){2}/g; // handle Google's BS (split on double newlines only) /** * Get request adapter class */ req_adapter = SmartChatModelGeminiRequestAdapter; /** * Get response adapter class */ res_adapter = SmartChatModelGeminiResponseAdapter; /** * Uses Gemini's dedicated token counting endpoint */ async count_tokens(input) { const req = { url: `https://generativelanguage.googleapis.com/v1beta/models/${this.model_key}:countTokens?key=${this.api_key}`, method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(this.prepare_token_count_body(input)) }; const resp = await this.http_adapter.request(req); return resp.json.totalTokens; } /** * Formats input for token counting based on type * @private */ prepare_token_count_body(input) { if (typeof input === "string") { return { contents: [{ parts: [{ text: input }] }] }; } else if (Array.isArray(input)) { return { contents: input.map((msg) => this.transform_message_for_token_count(msg)) }; } else if (typeof input === "object") { return { contents: [this.transform_message_for_token_count(input)] }; } throw new Error("Invalid input for count_tokens"); } /** * Transforms message for token counting, handling text and images * @private */ transform_message_for_token_count(message) { return { role: message.role === "assistant" ? "model" : message.role, parts: Array.isArray(message.content) ? message.content.map((part) => { if (part.type === "text") return { text: part.text }; if (part.type === "image_url") return { inline_data: { mime_type: part.image_url.url.split(";")[0].split(":")[1], data: part.image_url.url.split(",")[1] } }; return part; }) : [{ text: message.content }] }; } /** * Builds endpoint URLs with model and API key */ get endpoint() { return `https://generativelanguage.googleapis.com/v1beta/models/${this.model_key}:generateContent?key=${this.api_key}`; } get endpoint_streaming() { return `https://generativelanguage.googleapis.com/v1beta/models/${this.model_key}:streamGenerateContent?key=${this.api_key}`; } // /** // * Extracts text from Gemini's streaming format // */ // get_text_chunk_from_stream(event) { // const data = JSON.parse(event.data); // return data.candidates[0]?.content?.parts[0]?.text || ''; // } /** * Get models endpoint URL with API key * @returns {string} Complete models endpoint URL */ get models_endpoint() { return `${this.constructor.defaults.models_endpoint}?key=${this.api_key}`; } /** * Get HTTP method for models endpoint * @returns {string} HTTP method ("GET") */ get models_endpoint_method() { return "GET"; } get models_request_params() { return { url: this.models_endpoint, method: this.models_endpoint_method }; } /** * Parse model data from Gemini API response * @param {Object} model_data - Raw model data from API * @returns {Object} Map of model objects with capabilities and limits */ parse_model_data(model_data) { return model_data.models.filter((model) => model.name.startsWith("models/gemini")).reduce((acc, model) => { const out = { model_name: model.name.split("/").pop(), id: model.name.split("/").pop(), max_input_tokens: model.inputTokenLimit, max_output_tokens: model.maxOutputTokens, description: model.description, multimodal: model.name.includes("vision") || model.description.includes("multimodal"), raw: model }; acc[model.name.split("/").pop()] = out; return acc; }, {}); } is_end_of_stream(event) { return event.data.includes('"finishReason"'); return false; } }; var SmartChatModelGeminiRequestAdapter = class extends SmartChatModelRequestAdapter { to_platform(streaming = false) { return this.to_gemini(streaming); } to_gemini(streaming = false) { const gemini_body = { contents: this._transform_messages_to_gemini(), generationConfig: { temperature: this.temperature, maxOutputTokens: this.max_tokens, topK: this._req.topK || 1, topP: this._req.topP || 1, stopSequences: this._req.stop || [] }, safetySettings: [ { category: "HARM_CATEGORY_HARASSMENT", threshold: "BLOCK_NONE" }, { category: "HARM_CATEGORY_HATE_SPEECH", threshold: "BLOCK_NONE" }, { category: "HARM_CATEGORY_SEXUALLY_EXPLICIT", threshold: "BLOCK_NONE" }, { category: "HARM_CATEGORY_DANGEROUS_CONTENT", threshold: "BLOCK_NONE" } ] }; if (this.tools) gemini_body.tools = this._transform_tools_to_gemini(); if (gemini_body.tools && this.tool_choice !== "none") gemini_body.tool_config = this._transform_tool_choice_to_gemini(); return { url: streaming ? this.adapter.endpoint_streaming : this.adapter.endpoint, method: "POST", headers: this.get_headers(), body: JSON.stringify(gemini_body) }; } _transform_messages_to_gemini() { let gemini_messages = []; let system_message = ""; for (const message of this.messages) { if (message.role === "system") { system_message += message.content + "\n"; } else { gemini_messages.push({ role: this._get_gemini_role(message.role), parts: this._transform_content_to_gemini(message.content) }); } } if (system_message) { gemini_messages.unshift({ role: "user", parts: [{ text: system_message.trim() }] }); } return gemini_messages; } _get_gemini_role(role) { const role_map = { user: "user", assistant: "model", function: "model" // Gemini doesn't have a function role, so we'll treat it as model }; return role_map[role] || role; } _transform_content_to_gemini(content) { if (Array.isArray(content)) { return content.map((part) => { if (part.type === "text") return { text: part.text }; if (part.type === "image_url") { let mime_type = part.image_url.url.split(";")[0].split(":")[1]; if (mime_type === "image/jpg") mime_type = "image/jpeg"; return { inline_data: { mime_type, data: part.image_url.url.split(",")[1] } }; } if (part.type === "file" && part.file?.filename?.toLowerCase().endsWith(".pdf")) { if (part.file?.file_data) { return { inline_data: { mime_type: "application/pdf", data: part.file.file_data.split(",")[1] } }; } } return part; }); } return [{ text: content }]; } _transform_tools_to_gemini() { return [{ function_declarations: this.tools.map((tool) => ({ name: tool.function.name, description: tool.function.description, parameters: tool.function.parameters })) }]; } _transform_tool_choice_to_gemini() { return { function_calling_config: { mode: "ANY", allowed_function_names: this.tools.map((tool) => tool.function.name) } }; } }; var SmartChatModelGeminiResponseAdapter = class extends SmartChatModelResponseAdapter { static get platform_res() { return { candidates: [{ content: { parts: [ { text: "" } ], role: "" }, finishReason: "" }], promptFeedback: {}, usageMetadata: {} }; } to_openai() { if (this.error) return { error: normalize_error(this.error, this.status) }; const first_candidate = this._res.candidates[0]; if (!this._res.id) this._res.id = "gemini-" + Date.now().toString(); return { id: this._res.id, object: "chat.completion", created: Date.now(), model: this.adapter.model_key, choices: [{ index: 0, message: first_candidate?.content ? this._transform_message_to_openai(first_candidate.content) : "", finish_reason: this._get_openai_finish_reason(first_candidate.finishReason) }], usage: this._transform_usage_to_openai() }; } _transform_message_to_openai(content) { const message = { role: "assistant", content: content.parts.filter((part) => part.text).map((part) => part.text).join("") }; const function_call = content.parts.find((part) => part.functionCall); if (function_call) { message.tool_calls = [{ type: "function", function: { name: function_call.functionCall.name, arguments: JSON.stringify(function_call.functionCall.args) } }]; } return message; } _get_openai_finish_reason(finish_reason) { const reason_map = { "STOP": "stop", "MAX_TOKENS": "length", "SAFETY": "content_filter", "RECITATION": "content_filter", "OTHER": "null" }; return reason_map[finish_reason] || finish_reason.toLowerCase(); } _transform_usage_to_openai() { if (!this._res.usageMetadata) { return { prompt_tokens: null, completion_tokens: null, total_tokens: null }; } return { prompt_tokens: this._res.usageMetadata.promptTokenCount || null, completion_tokens: this._res.usageMetadata.candidatesTokenCount || null, total_tokens: this._res.usageMetadata.totalTokenCount || null }; } handle_chunk(chunk) { let chunk_trimmed = chunk.trim(); if (["[", ","].includes(chunk_trimmed[0])) chunk_trimmed = chunk_trimmed.slice(1); if (["]", ","].includes(chunk_trimmed[chunk_trimmed.length - 1])) chunk_trimmed = chunk_trimmed.slice(0, -1); const data = JSON.parse(chunk_trimmed); let raw; if (data.candidates?.[0]?.content?.parts?.[0]?.text?.length) { const content = data.candidates[0].content.parts[0].text; raw = content; this._res.candidates[0].content.parts[0].text += content; } if (data.candidates?.[0]?.content?.role?.length) { this._res.candidates[0].content.role = data.candidates[0].content.role; } if (data.candidates?.[0]?.finishReason?.length) { this._res.candidates[0].finishReason += data.candidates[0].finishReason; } if (data.promptFeedback) { this._res.promptFeedback = { ...this._res.promptFeedback || {}, ...data.promptFeedback }; } if (data.usageMetadata) { this._res.usageMetadata = { ...this._res.usageMetadata || {}, ...data.usageMetadata }; } if (data.candidates?.[0]?.content?.parts?.[0]?.functionCall) { if (!this._res.candidates[0].content.parts[0].functionCall) { this._res.candidates[0].content.parts[0].functionCall = { name: "", args: {} }; } this._res.candidates[0].content.parts[0].functionCall.name += data.candidates[0].content.parts[0].functionCall.name; if (data.candidates[0].content.parts[0].functionCall.args) { Object.entries(data.candidates[0].content.parts[0].functionCall.args).forEach(([key, value]) => { if (!this._res.candidates[0].content.parts[0].functionCall.args[key]) { this._res.candidates[0].content.parts[0].functionCall.args[key] = ""; } this._res.candidates[0].content.parts[0].functionCall.args[key] += value; }); } } return raw; } }; var SmartChatModelGeminiAdapter = class extends SmartChatModelGoogleAdapter { static key = "gemini"; static defaults = { description: "Gemini (SWITCH TO **GOOGLE** ADAPTER)", type: "API", api_key_header: "none", endpoint: "https://generativelanguage.googleapis.com/v1beta/models/MODEL_NAME:generateContent", endpoint_streaming: "https://generativelanguage.googleapis.com/v1beta/models/MODEL_NAME:streamGenerateContent", streaming: true, adapter: "Gemini", models_endpoint: "https://generativelanguage.googleapis.com/v1beta/models", default_model: "gemini-1.5-pro", signup_url: "https://ai.google.dev/" }; }; // node_modules/obsidian-smart-env/node_modules/smart-chat-model/adapters/open_router.js var SmartChatModelOpenRouterAdapter = class extends SmartChatModelApiAdapter { static key = "open_router"; static models_dev_key = "openrouter"; static defaults = { description: "Open Router", type: "API", endpoint: "https://openrouter.ai/api/v1/chat/completions", streaming: true, adapter: "OpenRouter", models_endpoint: "https://openrouter.ai/api/v1/models", default_model: "mistralai/mistral-7b-instruct:free", signup_url: "https://accounts.openrouter.ai/sign-up?redirect_url=https%3A%2F%2Fopenrouter.ai%2Fkeys" }; /** * Get request adapter class * @returns {typeof SmartChatModelOpenRouterRequestAdapter} Request adapter class */ get req_adapter() { return SmartChatModelOpenRouterRequestAdapter; } /** * Get response adapter class * @returns {typeof SmartChatModelOpenRouterResponseAdapter} Response adapter class */ get res_adapter() { return SmartChatModelOpenRouterResponseAdapter; } /** * Count tokens in input text (rough estimate) * @param {string|Object} input - Text to count tokens for * @returns {Promise} Estimated token count */ async count_tokens(input) { const text = typeof input === "string" ? input : JSON.stringify(input); return Math.ceil(text.length / 4); } get models_request_params() { return { url: this.models_endpoint, method: "GET" }; } /** * Parse model data from OpenRouter API response * @param {Object} model_data - Raw model data * @returns {Object} Map of model objects with capabilities and limits */ parse_model_data(model_data) { if (model_data.data) { model_data = model_data.data; } if (model_data.error) throw new Error(model_data.error); return model_data.reduce((acc, model) => { acc[model.id] = { model_name: model.id, id: model.id, max_input_tokens: model.context_length, name: model.name, description: model.name, long_desc: model.description, multimodal: model.architecture.modality === "multimodal", raw: model }; return acc; }, {}); } }; var SmartChatModelOpenRouterRequestAdapter = class extends SmartChatModelRequestAdapter { to_platform(stream = false) { const req = this.to_openai(stream); return req; } _get_openai_content(message) { if (message.role === "user") { if (Array.isArray(message.content) && message.content.every((part) => part.type === "text")) { return message.content.map((part) => part.text).join("\n"); } } return message.content; } }; var SmartChatModelOpenRouterResponseAdapter = class extends SmartChatModelResponseAdapter { static get platform_res() { return { id: "", object: "chat.completion", created: 0, model: "", choices: [], usage: {} }; } to_platform() { return this.to_openai(); } get object() { return "chat.completion"; } get error() { if (!this._res.error) return null; const error = this._res.error; if (!error.message) error.message = ""; if (this._res.error.metadata?.raw) { if (typeof this._res.error.metadata.raw === "string") { error.message += ` ${this._res.error.metadata.raw}`; } else { error.message += ` ${JSON.stringify(this._res.error.metadata.raw, null, 2)}`; } } if (error.message.startsWith("No cookie auth")) { error.suggested_action = "Ensure your Open Router API key is set correctly."; } return error; } }; // node_modules/obsidian-smart-env/node_modules/smart-chat-model/adapters/lm_studio.js var SmartChatModelLmStudioAdapter = class extends SmartChatModelApiAdapter { static key = "lm_studio"; /** @type {import('./_adapter.js').SmartChatModelAdapter['constructor']['defaults']} */ static defaults = { description: "LM Studio (OpenAI\u2011compatible)", type: "API", endpoint: "http://localhost:1234/v1/chat/completions", streaming: true, adapter: "LM_Studio_OpenAI_Compat", models_endpoint: "http://localhost:1234/v1/models", default_model: "", signup_url: "https://lmstudio.ai/docs/api/openai-api", api_key: "no api key required" }; /* ------------------------------------------------------------------ * * Request / Response classes * ------------------------------------------------------------------ */ get req_adapter() { return SmartChatModelLmStudioRequestAdapter; } get res_adapter() { return SmartChatModelLmStudioResponseAdapter; } /* ------------------------------------------------------------------ * * Settings * ------------------------------------------------------------------ */ /** * Extend the base settings with a read‑only HTML block that reminds the * user to enable CORS inside LM Studio. The Smart View renderer treats * `type: "html"` as a static fragment, so no extra runtime logic is needed. */ get settings_config() { const config = super.settings_config; delete config["[CHAT_ADAPTER].api_key"]; return { ...config, "[CHAT_ADAPTER].cors_instructions": { /* visible only when this adapter is selected */ name: "CORS required", type: "html", value: `

Before you can use LM Studio you must Enable CORS inside LM Studio \u2192 Developer \u2192 Settings

` } }; } /* ------------------------------------------------------------------ * * Model list helpers * ------------------------------------------------------------------ */ /** * LM Studio returns an OpenAI‑style list; normalise to the project shape. */ parse_model_data(model_data) { if (model_data.object !== "list" || !Array.isArray(model_data.data)) { return { _: { id: "No models found." } }; } const out = {}; for (const m of model_data.data) { out[m.id] = { id: m.id, model_name: m.id, description: `LM Studio model: ${m.id}`, multimodal: false }; } return out; } get models_endpoint_method() { return "get"; } /** * Count tokens in input text (no dedicated endpoint) * Rough estimate: 1 token ~ 4 chars * @param {string|Object} input * @returns {Promise} */ async count_tokens(input) { const text = typeof input === "string" ? input : JSON.stringify(input); return Math.ceil(text.length / 4); } /** * Test API key - LM Studio doesn't require API key. Always true. * @returns {Promise} */ async test_api_key() { return true; } get api_key() { return "no api key required"; } }; var SmartChatModelLmStudioRequestAdapter = class extends SmartChatModelRequestAdapter { to_platform(streaming = false) { const req = this.to_openai(streaming); const body = JSON.parse(req.body); if (this.tool_choice?.function?.name) { const last_msg = body.messages[body.messages.length - 1]; if (typeof last_msg.content === "string") { last_msg.content = [ { type: "text", text: last_msg.content } ]; } last_msg.content.push({ type: "text", text: `Use the "${this.tool_choice.function.name}" tool.` }); body.tool_choice = "required"; } else if (body.tool_choice && typeof body.tool_choice === "object") { body.tool_choice = "auto"; } req.body = JSON.stringify(body); return req; } }; var SmartChatModelLmStudioResponseAdapter = class extends SmartChatModelResponseAdapter { }; // node_modules/obsidian-smart-env/node_modules/smart-chat-model/adapters/ollama.js var SmartChatModelOllamaAdapter = class extends SmartChatModelApiAdapter { static key = "ollama"; static defaults = { description: "Ollama (Local)", type: "API", // models_endpoint: "http://localhost:11434/api/tags", // endpoint: "http://localhost:11434/api/chat", api_key: "na", host: "http://localhost:11434", endpoint: "/api/chat", models_endpoint: "/api/tags", // streaming: false, // TODO: Implement streaming streaming: true }; req_adapter = SmartChatModelOllamaRequestAdapter; res_adapter = SmartChatModelOllamaResponseAdapter; get host() { return this.model.data.host || this.constructor.defaults.host; } get endpoint() { return `${this.host}${this.constructor.defaults.endpoint}`; } get models_endpoint() { return `${this.host}${this.constructor.defaults.models_endpoint}`; } get model_show_endpoint() { return `${this.host}/api/show`; } get models_endpoint_method() { return "GET"; } /** * Get available models from local Ollama instance * @param {boolean} [refresh=false] - Whether to refresh cached models * @returns {Promise} Map of model objects */ async get_models(refresh = false) { if (!refresh && typeof this.model_data === "object" && Object.keys(this.model_data || {}).length > 0 && this.model_data_loaded_at && time_now - this.model_data_loaded_at < 1 * 60 * 60 * 1e3) return this.model_data; try { const list_resp = await this.http_adapter.request(this.models_request_params); const list_data = await list_resp.json(); const models_raw_data = []; for (const model of list_data.models) { const model_details_resp = await this.http_adapter.request({ url: this.model_show_endpoint, method: "POST", body: JSON.stringify({ model: model.name }) }); const model_details_data = await model_details_resp.json(); models_raw_data.push({ ...model_details_data, name: model.name }); } this.model_data = this.parse_model_data(models_raw_data); await this.get_enriched_model_data(); this.model.data.provider_models = this.model_data; if (typeof this.model.re_render_settings === "function") { this.model.re_render_settings(); } this.model_data_loaded_at = Date.now(); return this.model_data; } catch (error) { console.error("Failed to fetch model data:", error); return { "_": { id: `Failed to fetch models from ${this.model.adapter_name}` } }; } } /** * Parse model data from Ollama API response * @param {Object[]} model_data - Raw model data from Ollama * @returns {Object} Map of model objects with capabilities and limits */ parse_model_data(model_data) { if (!Array.isArray(model_data)) { this.model_data = {}; console.error("Invalid model data format from Ollama:", model_data); return {}; } if (model_data.length === 0) { this.model_data = { "no_models_available": { id: "no_models_available", name: "No models currently available" } }; return this.model_data; } return model_data.reduce((acc, model) => { if (model.name.includes("embed")) return acc; const out = { model_name: model.name, id: model.name, multimodal: false, max_input_tokens: Object.entries(model.model_info).find((m) => m[0].includes(".context_length"))[1] }; acc[model.name] = out; return acc; }, {}); } /** * Override settings config to remove API key setting since not needed for local instance * @returns {Object} Settings configuration object */ get settings_config() { const config = super.settings_config; delete config["[CHAT_ADAPTER].api_key"]; config["[CHAT_ADAPTER].host"] = { name: "Ollama host", type: "text", description: "Enter the host for your Ollama instance", default: this.constructor.defaults.host }; return config; } is_end_of_stream(event) { return event.data.includes('"done_reason"'); } }; var SmartChatModelOllamaRequestAdapter = class extends SmartChatModelRequestAdapter { /** * Convert request to Ollama format * @returns {Object} Request parameters in Ollama format */ to_platform(streaming = false) { const ollama_body = { model: this.model_id, messages: this._transform_messages_to_ollama(), options: this._transform_parameters_to_ollama(), stream: streaming || this.stream // format: 'json', // only used for tool calls since returns JSON in content body }; if (this.tools) { ollama_body.tools = this._transform_functions_to_tools(); if (this.tool_choice?.function?.name) { ollama_body.messages[ollama_body.messages.length - 1].content += ` Use the "${this.tool_choice.function.name}" tool.`; ollama_body.format = "json"; } } return { url: this.adapter.endpoint, method: "POST", body: JSON.stringify(ollama_body) }; } /** * Transform messages to Ollama format * @returns {Array} Messages in Ollama format * @private */ _transform_messages_to_ollama() { return this.messages.map((message) => { const ollama_message = { role: message.role, content: this._transform_content_to_ollama(message.content) }; const images = this._extract_images_from_content(message.content); if (images.length > 0) { ollama_message.images = images.map((img) => img.replace(/^data:image\/[^;]+;base64,/, "")); } return ollama_message; }); } /** * Transform content to Ollama format * @param {string|Array} content - Message content * @returns {string} Content in Ollama format * @private */ _transform_content_to_ollama(content) { if (Array.isArray(content)) { return content.filter((item) => item.type === "text").map((item) => item.text).join("\n"); } return content; } /** * Extract images from content * @param {string|Array} content - Message content * @returns {Array} Array of image URLs * @private */ _extract_images_from_content(content) { if (!Array.isArray(content)) return []; return content.filter((item) => item.type === "image_url").map((item) => item.image_url.url); } /** * Transform functions to tools format * @returns {Array} Tools array in Ollama format * @private */ _transform_functions_to_tools() { return this.tools; } /** * Transform parameters to Ollama options format * @returns {Object} Options in Ollama format * @private */ _transform_parameters_to_ollama() { const options = {}; if (this.max_tokens) options.num_predict = this.max_tokens; if (this.temperature) options.temperature = this.temperature; if (this.top_p) options.top_p = this.top_p; if (this.frequency_penalty) options.frequency_penalty = this.frequency_penalty; if (this.presence_penalty) options.presence_penalty = this.presence_penalty; return options; } }; var SmartChatModelOllamaResponseAdapter = class extends SmartChatModelResponseAdapter { static get platform_res() { return { model: "", created_at: null, message: { role: "", content: "" }, total_duration: 0, load_duration: 0, prompt_eval_count: 0, prompt_eval_duration: 0, eval_count: 0, eval_duration: 0 }; } /** * Convert response to OpenAI format * @returns {Object} Response in OpenAI format */ to_openai() { if (this.error) return { error: normalize_error(this.error, this.status) }; return { id: this._res.created_at, object: "chat.completion", created: Date.now(), model: this._res.model, choices: [ { index: 0, message: this._transform_message_to_openai(), finish_reason: this._res.done_reason } ], usage: this._transform_usage_to_openai() }; } /** * Transform message to OpenAI format * @returns {Object} Message in OpenAI format * @private */ _transform_message_to_openai() { return { role: this._res.message.role, content: this._res.message.content, tool_calls: this._res.message.tool_calls }; } /** * Transform usage statistics to OpenAI format * @returns {Object} Usage statistics in OpenAI format * @private */ _transform_usage_to_openai() { return { prompt_tokens: this._res.prompt_eval_count || 0, completion_tokens: this._res.eval_count || 0, total_tokens: (this._res.prompt_eval_count || 0) + (this._res.eval_count || 0) }; } /** * Parse chunk adds delta to content as expected output format */ handle_chunk(chunk) { chunk = JSON.parse(chunk || "{}"); if (chunk.created_at && !this._res.created_at) { this._res.created_at = chunk.created_at; } let raw; if (chunk.message?.content) { const content = chunk.message.content; raw = content; this._res.message.content += content; } if (chunk.message?.role) { this._res.message.role = chunk.message.role; } if (chunk.model) { this._res.model = chunk.model; } if (chunk.message?.tool_calls) { if (!this._res.message.tool_calls) { this._res.message.tool_calls = [{ id: "", type: "function", function: { name: "", arguments: "" } }]; } if (chunk.message.tool_calls[0].id) { this._res.message.tool_calls[0].id += chunk.message.tool_calls[0].id; } if (chunk.message.tool_calls[0].function.name) { this._res.message.tool_calls[0].function.name += chunk.message.tool_calls[0].function.name; } if (chunk.message.tool_calls[0].function.arguments) { if (typeof chunk.message.tool_calls[0].function.arguments === "string") { this._res.message.tool_calls[0].function.arguments += chunk.message.tool_calls[0].function.arguments; } else { this._res.message.tool_calls[0].function.arguments = chunk.message.tool_calls[0].function.arguments; } } } return raw; } }; // node_modules/obsidian-smart-env/node_modules/smart-chat-model/adapters/_custom.js var adapters_map = { "openai": { req: SmartChatModelRequestAdapter, res: SmartChatModelResponseAdapter }, "anthropic": { req: SmartChatModelAnthropicRequestAdapter, res: SmartChatModelAnthropicResponseAdapter }, "gemini": { req: SmartChatModelGeminiRequestAdapter, res: SmartChatModelGeminiResponseAdapter }, "lm_studio": { req: SmartChatModelLmStudioRequestAdapter, res: SmartChatModelLmStudioResponseAdapter }, "ollama": { req: SmartChatModelOllamaRequestAdapter, res: SmartChatModelOllamaResponseAdapter } }; var SmartChatModelCustomAdapter = class extends SmartChatModelApiAdapter { static key = "custom"; static defaults = { description: "Custom API (Local or Remote, OpenAI format)", type: "API", /** * new default property: 'api_adapter' indicates which * request/response adapter set to use internally */ api_adapter: "openai" }; /** * Provide dynamic request/response classes */ /** * @override * @returns {typeof SmartChatModelRequestAdapter} */ get req_adapter() { const adapter_name = this.model.data.api_adapter || "openai"; const map_entry = adapters_map[adapter_name]; return map_entry && map_entry.req ? map_entry.req : SmartChatModelRequestAdapter; } /** * @override * @returns {typeof SmartChatModelResponseAdapter} */ get res_adapter() { const adapter_name = this.model.data.api_adapter || "openai"; const map_entry = adapters_map[adapter_name]; return map_entry && map_entry.res ? map_entry.res : SmartChatModelResponseAdapter; } /** * Synthesize a custom endpoint from the config fields. * All fields are optional; fallback to a minimal default. * @returns {string} */ get endpoint() { const protocol = this.model.data.protocol || "http"; const hostname = this.model.data.hostname || "localhost"; const port = this.model.data.port ? `:${this.model.data.port}` : ""; let path = this.model.data.path || ""; if (path && !path.startsWith("/")) path = `/${path}`; return `${protocol}://${hostname}${port}${path}`; } get_adapters_as_options() { return Object.keys(adapters_map).map((adapter_name) => ({ value: adapter_name, name: adapter_name })); } /** * Provide custom settings for configuring * the user-defined fields plus the new 'api_adapter'. * @override * @returns {Object} settings configuration */ get settings_config() { return { /** * Select which specialized request/response adapter * you'd like to use for your custom endpoint. */ "[CHAT_ADAPTER].api_adapter": { name: "API Adapter", type: "dropdown", description: "Pick a built-in or external adapter to parse request/response data.", // Provide a short selection set, or dynamically gather from keys of adapters_map // options_callback: 'adapter.get_adapters_as_options', options_callback: () => { this.get_adapters_as_options(); }, // UNTESTED default: "openai" }, "[CHAT_ADAPTER].id": { name: "Model Name", type: "text", description: "Enter the model name for your endpoint if needed." }, "[CHAT_ADAPTER].protocol": { name: "Protocol", type: "text", description: "e.g. http or https" }, "[CHAT_ADAPTER].hostname": { name: "Hostname", type: "text", description: "e.g. localhost or some.remote.host" }, "[CHAT_ADAPTER].port": { name: "Port", type: "number", description: "Port number or leave blank" }, "[CHAT_ADAPTER].path": { name: "Path", type: "text", description: "Path portion of the URL (leading slash optional)" }, "[CHAT_ADAPTER].streaming": { name: "Streaming", type: "toggle", description: "Enable streaming if your API supports it." }, "[CHAT_ADAPTER].max_input_tokens": { name: "Max Input Tokens", type: "number", description: "Max number of tokens your model can handle in the prompt." }, "[CHAT_ADAPTER].api_key": { name: "API Key", type: "password", description: "If your service requires an API key, add it here." } }; } }; // node_modules/obsidian-smart-env/node_modules/smart-chat-model/adapters/groq.js var SmartChatModelGroqAdapter = class extends SmartChatModelApiAdapter { static key = "groq"; static defaults = { description: "Groq", type: "API", endpoint: "https://api.groq.com/openai/v1/chat/completions", streaming: true, adapter: "Groq", models_endpoint: "https://api.groq.com/openai/v1/models", default_model: "llama3-8b-8192", signup_url: "https://groq.com" }; /** * Request adapter class * @returns {typeof SmartChatModelGroqRequestAdapter} */ get req_adapter() { return SmartChatModelGroqRequestAdapter; } /** * Response adapter class * @returns {typeof SmartChatModelGroqResponseAdapter} */ get res_adapter() { return SmartChatModelGroqResponseAdapter; } get models_endpoint_method() { return "GET"; } /** * Parse model data from Groq API format to a dictionary keyed by model ID. * The API returns a list of model objects like: * { * "object": "list", * "data": [ { "id": "...", "object": "model", ... }, ... ] * } * * We'll convert each model to: * { * model_name: model.id, * id: model.id, * max_input_tokens: model.context_window, * description: `Owned by: ${model.owned_by}, context: ${model.context_window}`, * multimodal: Check if model name or description suggests multimodality * } */ parse_model_data(model_data) { if (model_data.object !== "list" || !Array.isArray(model_data.data)) { return { "_": { id: "No models found." } }; } const parsed = {}; for (const m of model_data.data) { parsed[m.id] = { model_name: m.id, id: m.id, max_input_tokens: m.context_window || 8192, description: `Owned by: ${m.owned_by}, context: ${m.context_window}`, // A basic heuristic for multimodal: if 'vision' or 'tool' is in model id // Adjust as needed based on known capabilities multimodal: m.id.includes("vision") }; } return parsed; } }; var SmartChatModelGroqRequestAdapter = class extends SmartChatModelRequestAdapter { _get_openai_content(message) { if (["assistant", "tool"].includes(message.role)) { if (Array.isArray(message.content)) { return message.content.map((part) => { if (typeof part === "string") return part; if (part?.text) return part.text; return ""; }).join("\n"); } } return message.content; } }; var SmartChatModelGroqResponseAdapter = class extends SmartChatModelResponseAdapter { }; // node_modules/obsidian-smart-env/node_modules/smart-chat-model/adapters/xai.js var SmartChatModelXaiAdapter = class extends SmartChatModelApiAdapter { /** Human-readable platform key used by SmartChatModel */ static key = "xai"; /** @type {import('./_adapter.js').SmartChatModelAdapter['constructor']['defaults']} */ static defaults = { description: "xAI Grok", type: "API", adapter: "xAI_Grok", endpoint: "https://api.x.ai/v1/chat/completions", streaming: true, models_endpoint: "https://api.x.ai/v1/models", default_model: "grok-3-mini-beta", signup_url: "https://ide.x.ai" }; /** Grok is OpenAI-compatible β†’ reuse the stock adapters */ get req_adapter() { return SmartChatModelRequestAdapter; } get res_adapter() { return SmartChatModelResponseAdapter; } /* ------------------------------------------------------------------ * * Model-list helpers * ------------------------------------------------------------------ */ /** * The Grok `/v1/models` route is **GET**, not POST. * Override the HTTP verb so `get_models()` works. * @returns {string} 'GET' */ get models_endpoint_method() { return "GET"; } /** * Parse `/v1/models` payload to the canonical shape used by SmartChat. * * Grok returns: * ```json * { "object":"list", * "data":[{ "id":"grok-3-beta", "context_length":128000, …}] } * ``` */ parse_model_data(model_data = {}) { const list = model_data.data || model_data.models || []; return list.reduce((acc, m) => { const id = m.id || m.name; acc[id] = { id, model_name: id, description: m.description || `context: ${m.context_length || "n/a"}`, max_input_tokens: m.context_length || 128e3, multimodal: !!m.modality && m.modality.includes("vision"), raw: m }; return acc; }, {}); } }; // node_modules/obsidian-smart-env/node_modules/smart-chat-model/adapters/deepseek.js var SmartChatModelDeepseekAdapter = class extends SmartChatModelApiAdapter { static key = "deepseek"; static defaults = { description: "DeepSeek", type: "API", endpoint: "https://api.deepseek.com/chat/completions", streaming: true, adapter: "DeepSeek", models_endpoint: "https://api.deepseek.com/models", default_model: "deepseek-base", signup_url: "https://deepseek.com/signup" }; /** * Get the request adapter class * @returns {typeof SmartChatModelDeepseekRequestAdapter} Request adapter class */ get req_adapter() { return SmartChatModelDeepseekRequestAdapter; } /** * Get the response adapter class * @returns {typeof SmartChatModelDeepseekResponseAdapter} Response adapter class */ get res_adapter() { return SmartChatModelDeepseekResponseAdapter; } get models_endpoint_method() { return "GET"; } /** * Parse the raw model data from DeepSeek's /v1/models endpoint * into a structured map of model objects keyed by model ID. * @param {Object} model_data - Raw JSON from DeepSeek * @returns {Object} Map of model objects */ parse_model_data(model_data) { if (!model_data?.data || !Array.isArray(model_data.data)) { return { "_": { id: "No models found." } }; } const parsed = {}; for (const m of model_data.data) { parsed[m.id] = { model_name: m.id, id: m.id, max_input_tokens: m.context_size || 8192, description: m.description || m.name || m.id, raw: m }; } return parsed; } /** * Estimate tokens in user input. * @param {string|Object} input - Input text or structured message * @returns {Promise} Token count estimate */ async count_tokens(input) { const text = typeof input === "string" ? input : JSON.stringify(input); return Math.ceil(text.length / 4); } /** * Check if an incoming streaming chunk signals end of stream. * @param {CustomEvent} event - SSE event with data * @returns {boolean} True if end of stream */ is_end_of_stream(event) { if (!event?.data) return false; return event.data.includes('"done":true') || event.data.includes("[DONE]"); } }; var SmartChatModelDeepseekRequestAdapter = class extends SmartChatModelRequestAdapter { /** * Convert incoming request to DeepSeek's expected format * Often just reuse the base "to_openai()" if that matches DeepSeek's design * @param {boolean} streaming - True if streaming * @returns {Object} Request parameters */ to_platform(streaming = false) { return this.to_openai(streaming); } }; var SmartChatModelDeepseekResponseAdapter = class extends SmartChatModelResponseAdapter { }; // node_modules/obsidian-smart-env/default.config.js var import_obsidian35 = require("obsidian"); // node_modules/obsidian-smart-env/node_modules/smart-blocks/content_parsers/parse_blocks.js function parse_blocks(source, content) { let { blocks: blocks_obj, task_lines, tasks, codeblock_ranges } = parse_markdown_blocks(content); const last_read_at = source.data.last_read?.at || Date.now(); for (const [sub_key, line_range] of Object.entries(blocks_obj)) { const block_key = source.key + sub_key; const existing_block = source.block_collection.get(block_key); const block_content = get_line_range2(content, line_range[0], line_range[1]); if (existing_block && existing_block.lines[0] === line_range[0] && existing_block.lines[1] === line_range[1] && existing_block.size === block_content.length && existing_block.vec) { continue; } const block_outlinks = get_markdown_links(block_content); const block_data = { key: block_key, lines: line_range, size: block_content.length, outlinks: block_outlinks, last_read: { at: last_read_at, hash: murmur_hash_32_alphanumeric(block_content) } }; if (!existing_block || existing_block?.data.last_read?.hash !== block_data.last_read.hash) { const new_item = new source.block_collection.item_type(source.env, block_data); source.block_collection.set(new_item); } else { existing_block.data = { ...existing_block.data, ...block_data // overwrites lines, last_read }; } } clean_and_update_source_blocks(source, blocks_obj, task_lines, tasks, codeblock_ranges); for (const block of source.blocks) { if (!block.vec) { block.queue_embed(); } } } function clean_and_update_source_blocks(source, blocks_obj, task_lines = [], tasks = {}, codeblock_ranges = {}) { const current_block_keys = new Set(Object.keys(blocks_obj).map((sk) => source.key + sk)); const blocks = source.blocks; for (let i = 0; i < blocks.length; i++) { if (!current_block_keys.has(blocks[i].key)) { blocks[i].deleted = true; blocks[i].queue_save(); } } source.data.blocks = blocks_obj; source.data.task_lines = task_lines; source.data.tasks = tasks; source.data.codeblock_ranges = codeblock_ranges; source.queue_save(); } // node_modules/obsidian-smart-env/node_modules/smart-collections/utils/ajson_merge.js function ajson_merge(existing, new_obj) { if (new_obj === null) return null; if (new_obj === void 0) return existing; if (typeof new_obj !== "object") return new_obj; if (typeof existing !== "object" || existing === null) existing = {}; const keys = Object.keys(new_obj); const length = keys.length; for (let i = 0; i < length; i++) { const key = keys[i]; const new_val = new_obj[key]; const existing_val = existing[key]; if (Array.isArray(new_val)) { existing[key] = new_val.slice(); } else if (is_object(new_val)) { existing[key] = ajson_merge(is_object(existing_val) ? existing_val : {}, new_val); } else if (new_val !== void 0) { existing[key] = new_val; } } return existing; } function is_object(obj) { return obj !== null && typeof obj === "object" && !Array.isArray(obj); } // node_modules/obsidian-smart-env/node_modules/smart-collections/adapters/ajson_single_file.js var class_to_collection_key2 = { "SmartSource": "smart_sources", "SmartNote": "smart_sources", // DEPRECATED "SmartBlock": "smart_blocks", "SmartDirectory": "smart_directories" }; function _parse_ajson_key(ajson_key) { let changed = false; let [collection_key, ...item_key] = ajson_key.split(":"); if (class_to_collection_key2[collection_key]) { collection_key = class_to_collection_key2[collection_key]; changed = true; } return { collection_key, item_key: item_key.join(":"), changed }; } var AjsonSingleFileCollectionDataAdapter = class extends AjsonMultiFileCollectionDataAdapter { /** * Returns the single shared `.ajson` file path for this collection. * @param {string} [key] - (unused) Item key, ignored in single-file mode. * @returns {string} The single .ajson file path for the entire collection. */ get_item_data_path(key) { const file_name = (this.collection?.collection_key || "collection") + ".ajson"; const sep = this.fs?.sep || "/"; const dir = this.collection.data_dir || "data"; return [dir, file_name].join(sep); } /** * Override process_load_queue to parse the entire single-file .ajson once, * distributing final states to items. * * @async * @returns {Promise} */ async process_load_queue() { this.collection.emit_event("collection:load_started"); this.collection.show_process_notice("loading_collection"); if (!await this.fs.exists(this.collection.data_dir)) { await this.fs.mkdir(this.collection.data_dir); } const path = this.get_item_data_path(); if (!await this.fs.exists(path)) { for (const item of Object.values(this.collection.items)) { if (item._queue_load) { item.queue_import?.(); } } this.collection.clear_process_notice("loading_collection"); this.collection.emit_event("collection:load_halted"); return; } const raw_data = await this.fs.read(path, "utf-8", { no_cache: true }); if (!raw_data) { for (const item of Object.values(this.collection.items)) { if (item._queue_load) { item.queue_import?.(); } } this.collection.clear_process_notice("loading_collection"); this.collection.emit_event("collection:load_halted"); return; } const { rewrite, file_data } = this.parse_single_file_ajson(raw_data); if (rewrite) { if (file_data.length) { await this.fs.write(path, file_data); } else { await this.fs.remove(path); } } for (const item of Object.values(this.collection.items)) { item._queue_load = false; item.loaded_at = Date.now(); } this.collection.clear_process_notice("loading_collection"); this.collection.emit_event("collection:load_completed"); } /** * Helper to parse single-file .ajson content, distributing states to items. * * @param {string} raw * @returns {{ rewrite: boolean, file_data: string }} */ parse_single_file_ajson(raw) { let rewrite = false; const lines = raw.trim().split("\n").filter(Boolean); let data_map = {}; let line_count = 0; for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); if (!line.endsWith(",")) { rewrite = true; } const trimmed = line.replace(/,$/, ""); const combined = "{" + trimmed + "}"; try { const obj = JSON.parse(combined); const [fullKey, value] = Object.entries(obj)[0]; let { collection_key, item_key, changed } = _parse_ajson_key(fullKey); const newKey = `${collection_key}:${item_key}`; if (!value) { delete data_map[newKey]; if (changed || newKey !== fullKey) { delete data_map[fullKey]; } rewrite = true; } else { data_map[newKey] = value; if (changed || newKey !== fullKey) { delete data_map[fullKey]; rewrite = true; } } } catch (err) { console.warn("parse error for line: ", line, err); rewrite = true; } line_count++; } for (const [ajson_key, val] of Object.entries(data_map)) { const [collection_key, ...rest] = ajson_key.split(":"); const item_key = rest.join(":"); const collection = this.collection.env[collection_key]; if (!collection) continue; let item = collection.get(item_key); if (!item) { const ItemClass = collection.item_type; item = new ItemClass(this.env, val); collection.set(item); } else { item.data = ajson_merge(item.data, val); } item.loaded_at = Date.now(); item._queue_load = false; if (!val.key) val.key = item_key; } if (line_count > Object.keys(data_map).length) { rewrite = true; } let minimal_lines = []; for (const [ajson_key, val] of Object.entries(data_map)) { minimal_lines.push(`${JSON.stringify(ajson_key)}: ${JSON.stringify(val)},`); } return { rewrite, file_data: minimal_lines.join("\n") }; } /** * Override process_save_queue for single-file approach. * We'll simply call save_item for each queued item, which appends a line to the same `.ajson`. * * @async * @returns {Promise} */ async process_save_queue() { this.collection.emit_event("collection:save_started"); this.collection.show_process_notice("saving_collection"); const save_queue = Object.values(this.collection.items).filter((item) => item._queue_save); const time_start = Date.now(); const batch_size = 50; for (let i = 0; i < save_queue.length; i += batch_size) { const batch = save_queue.slice(i, i + batch_size); await Promise.all(batch.map((item) => { const adapter = this.create_item_adapter(item); return adapter.save().catch((err) => { console.warn(`Error saving item ${item.key}`, err); item.queue_save(); }); })); } const deleted_items = Object.values(this.collection.items).filter((item) => item.deleted); if (deleted_items.length) { deleted_items.forEach((item) => { delete this.collection.items[item.key]; }); } console.log(`Saved (single-file) ${this.collection.collection_key} in ${Date.now() - time_start}ms`); this.collection.clear_process_notice("saving_collection"); this.collection.emit_event("collection:save_completed"); } }; var AjsonSingleFileItemDataAdapter = class extends AjsonMultiFileItemDataAdapter { /** * Overridden to always return the single file path from the parent collection adapter. * @returns {string} */ get data_path() { return this.collection_adapter.get_item_data_path(this.item.key); } /** * Load logic: * In single-file mode, we typically rely on the collection's `process_load_queue()` * to parse the entire file. This direct `load()` will do a naive re-parse as well * if used individually. */ async load() { const path = this.data_path; if (!await this.fs.exists(path)) { this.item.queue_import?.(); return; } try { const raw_data = await this.fs.read(path, "utf-8", { no_cache: true }); if (!raw_data) { this.item.queue_import?.(); return; } const { rewrite } = this.collection_adapter.parse_single_file_ajson(raw_data); } catch (err) { console.warn(`Error loading single-file item ${this.item.key}`, err); this.item.queue_import?.(); } } }; var ajson_single_file_default = { collection: AjsonSingleFileCollectionDataAdapter, item: AjsonSingleFileItemDataAdapter }; // node_modules/obsidian-smart-env/node_modules/smart-components/smart_component.js var SmartComponent = class extends CollectionItem { static key = "smart_component"; static collection_key = "smart_components"; collection_key = "smart_components"; get_key() { if (this.data?.key) return this.data.key; const scope_key = this.scope_key; const component_key = this.component_key; const version = Number.isFinite(this.data?.version) ? this.data.version : 0; const hash = this.data?.hash || "nohash"; const key_pcs = []; if (!component_key.includes(scope_key) && scope_key !== "global") key_pcs.push(scope_key); key_pcs.push(component_key); return `${key_pcs.join("_").replace(/\./g, "_")}#${[version, hash].join("#")}`; } get scope_key() { return this.data?.scope_key; } get component_key() { return this.data?.component_key; } get component_adapter() { return this._component_adapter; } /** * Delegates render logic to the adapter. * @param {object} component_scope * @param {object} [opts={}] * @returns {Promise<*>} */ async render(component_scope, opts = {}) { if (!this.component_adapter) { throw new Error(`SmartComponent: adapter missing for ${this.component_key}`); } return await this.component_adapter.render(component_scope, opts); } }; // node_modules/obsidian-smart-env/node_modules/smart-components/adapters/_adapter.js function parse_component_properties(component_properties = []) { const parts = component_properties.filter(Boolean).map((part) => part.toString()); const component_key = parts.pop(); const scope_key = parts.length ? parts.join(".") : "global"; return { scope_key, component_key }; } async function build_component_data(component_properties, component_module) { const { scope_key, component_key } = parse_component_properties(component_properties); if (!component_key) return null; const render_fn = typeof component_module === "function" ? component_module : component_module?.render; const version = typeof render_fn?.version === "number" ? render_fn.version : 0; const hash = await murmur_hash_32_alphanumeric(render_fn.toString()); return { scope_key, component_key, version, hash }; } var SmartComponentAdapter = class { constructor(item, component_module) { this.item = item; this.module = component_module; this.item.env.create_env_getter(this); } static should_use_adapter(component_module) { return true; } static async register_component(collection, component_properties, component_module) { if (!this.should_use_adapter(component_module)) return null; const data = await build_component_data(component_properties, component_module); if (!data) return null; const item = await collection.create_or_update({ ...data }); if (!item) return null; item._component_module = component_module; item._component_adapter = new this(item, component_module); return item; } /** * Render the component for the provided scope. * @abstract * @param {Object} scope - Render scope from the hosting environment. * @param {Object} [opts] - Optional render options. * @returns {Promise<*>} Rendered output for the component. */ async render(scope, opts) { throw new Error("render() not implemented"); } }; // node_modules/obsidian-smart-env/node_modules/smart-components/adapters/smart_view_component_adapter.js var SmartViewComponentAdapter = class extends SmartComponentAdapter { static should_use_adapter(component_module) { return typeof component_module === "function" || typeof component_module?.render === "function"; } get smart_view() { if (!this._smart_view) { this._smart_view = this.env.init_module("smart_view"); } return this._smart_view; } async render(scope, opts = {}) { const render_fn = typeof this.module === "function" ? this.module : this.module?.render; if (typeof render_fn !== "function") { throw new Error("SmartViewComponentAdapter: render() missing on module"); } return await render_fn.call(this.smart_view, scope, opts); } }; // node_modules/obsidian-smart-env/node_modules/smart-components/smart_components.js function flatten_components_config(config, path = [], acc = []) { if (!config || typeof config !== "object") return acc; Object.entries(config).forEach(([key, value]) => { const next_path = [...path, key]; if (!value) return; if (typeof value === "function" || typeof value?.render === "function") { acc.push({ properties: next_path, module: value }); return; } if (typeof value === "object") { flatten_components_config(value, next_path, acc); } }); return acc; } var SmartComponents = class extends Collection { static key = "smart_components"; static collection_key = "smart_components"; collection_key = "smart_components"; async init() { await this.load_components_from_config(); } get component_adapters() { if (Array.isArray(this.opts?.component_adapters)) { return this.opts.component_adapters; } if (this.opts?.component_adapters && typeof this.opts.component_adapters === "object") { return Object.values(this.opts.component_adapters); } return this.constructor.default_component_adapters || []; } async load_components_from_config() { const records = flatten_components_config(this.env.config?.components || {}); for (const record of records) { await this.register_component(record.properties, record.module); } } async register_component(component_properties, component_module) { for (const AdapterClass of this.component_adapters) { const item = await AdapterClass.register_component(this, component_properties, component_module); if (item) return item; } return null; } async render_component(component_key, scope, opts = {}) { const components = this.filter((item) => { if (item.key.startsWith(component_key + "#")) return true; return item.component_key === component_key; }).sort((a, b) => { const a_scope_match = a.scope_key === scope.key ? 1 : 0; const b_scope_match = b.scope_key === scope.key ? 1 : 0; return b_scope_match - a_scope_match; }); if (components.length === 0) { throw new Error(`SmartComponents: no component found for key ${component_key}`); } const selected_component = components[0]; return await selected_component.render(scope, opts); } }; var smart_components_default = { class: SmartComponents, item_type: SmartComponent, data_adapter: ajson_single_file_default, component_adapters: { SmartViewComponentAdapter } }; // node_modules/obsidian-smart-env/node_modules/smart-components/index.js var smart_components_default2 = smart_components_default; // node_modules/obsidian-smart-env/node_modules/smart-contexts/utils/filter_redundant_context_items.js function filter_redundant_context_items(items = []) { const parents = /* @__PURE__ */ new Set(); for (const { key } of items) { if (!key.includes("#")) parents.add(key); } return items.filter(({ key }) => { if (!key.includes("#")) return true; const base = key.split("#")[0]; return !parents.has(base); }); } // node_modules/obsidian-smart-env/node_modules/smart-contexts/smart_context.js var SmartContext = class extends CollectionItem { static version = 1; static get defaults() { return { data: { key: "", context_items: {}, context_opts: {} } }; } // queue_save to debounce process save queue queue_save() { super.queue_save(); this.collection.queue_save(); } /** * add_item * @param {string|object} item */ add_item(item, params = {}) { const { emit_updated = true } = params; let key; if (typeof item === "object") { key = item.key || item.path; } else { key = item; } const existing = this.data.context_items[key]; const context_item = { d: 0, at: Date.now(), ...existing || {}, ...typeof item === "object" ? item : {} }; if (!key) return console.error("SmartContext: add_item called with invalid item", item); this.data.context_items[key] = context_item; this.queue_save(); if (emit_updated) this.emit_event("context:updated", { add_item: key }); } /** * add_items * @param {string[]|object[]} items */ add_items(items) { if (!Array.isArray(items)) items = [items]; items.forEach((item) => this.add_item(item, { emit_updated: false })); this.emit_event("context:updated", { added_items: items.map((item) => typeof item === "object" ? item.key || item.path : item) }); } /** * remove_item * Removes a path/ref from context and emits context:updated * @param {string} key */ remove_item(key) { if (!key || !this.data?.context_items?.[key]) return; if (this.data.context_items[key].folder) { this.data.context_items[key].exclude = true; } else { delete this.data.context_items[key]; } this.queue_save(); this.emit_event("context:updated", { removed_key: key }); } clear_all() { this.data.context_items = {}; this.queue_save(); this.emit_event("context:updated", { cleared: true }); } get context_item_keys() { return Object.entries(this.data?.context_items || {}).filter(([key, item_data]) => !item_data.exclude).map(([key, item_data]) => key); } get key() { if (!this.data.key) { this.data.key = Date.now().toString(); } return this.data.key; } get has_context_items() { return Object.keys(this.data.context_items || {}).length > 0; } get name() { return this.data.name; } set name(name) { if (typeof name !== "string") throw new TypeError("Name must be a string"); const was_nameless = !this.data.name || String(this.data.name).trim().length === 0; this.data.name = name; if (was_nameless) this.emit_event("context:named"); else this.emit_event("context:renamed", { name }); this.queue_save(); } get size() { let size = 0; const context_items = this.get_context_items(); context_items.forEach((item) => { if (item.size) size += item.size; }); return size; } get item_count() { return Object.entries(this.data?.context_items || {}).filter(([key, item_data]) => !item_data.exclude).length; } // v3 async get_text(params = {}) { const segments = []; const context_items = this.context_items.filter(params.filter).sort((a, b) => a.data.d - b.data.d); console.log("get_text context_items", context_items); for (const item of context_items) { if (item.is_media) continue; const item_text = await item.get_text(); if (typeof item_text === "string") segments.push(item_text); else this.emit_get_text_error(item, item_text); } const context_items_text = segments.join("\n"); if (typeof this.actions.context_merge_template === "function") { return await this.actions.context_merge_template(context_items_text, context_items); } return context_items_text; } async get_media(params = {}) { const context_items = this.context_items.filter(params.filter); const out = []; for (const item of context_items) { if (!item.is_media) continue; const item_base64 = await item.get_base64(); if (item_base64.error) this.emit_get_media_error(item, item_base64); else out.push(item_base64); } return out; } get context_items() { if (!this._context_items) { const config = this.env.config.collections.context_items; const Class = config.class; this._context_items = new Class(this.env, { ...config, class: null }); this._context_items.load_from_data(this.data.context_items || {}); this.on_event("context:updated", () => { this._context_items = null; }); } return this._context_items; } emit_get_text_error(item, item_text) { this.emit_event("notification:error", { message: `Context item did not return text: ${item.key}`, ...item_text && typeof item_text === "object" ? item_text : {} }); } emit_get_media_error(item, item_base64) { this.emit_event("notification:error", { message: `Context item did not return media: ${item.key}`, ...item_base64 && typeof item_base64 === "object" ? item_base64 : {} }); } /** * DEPRECATED */ /** * Return *ContextItem* instances (any depth) for a given key array. * @deprecated use context_items property instead * @param {string[]} keys */ get_context_items(keys = this.context_item_keys) { return filter_redundant_context_items( keys.map((k) => this.get_context_item(k)).filter(Boolean) ); } /** * @deprecated use context_items property instead */ get_context_item(key) { const existing = this.env.context_items.get(key); if (existing) return existing; return this.env.context_items.new_item({ key, ...this.data.context_items[key] || {} }); } /** * @method get_ref * @deprecated moving to using ContextItem instances */ get_ref(key) { return this.collection.get_ref(key); } /** * @deprecated */ get_item_keys_by_depth(depth) { return Object.keys(this.data.context_items).filter((k) => { const item_depth = this.data.context_items[k].d; if (item_depth === depth) return true; if (typeof item_depth === "undefined" && depth === 0) return true; return false; }); } }; // node_modules/obsidian-smart-env/node_modules/smart-contexts/smart_contexts.js var SmartContexts = class extends Collection { static version = 0.1; /** * new_context * @param {object} data * @param {object} opts * @param {string[]} opts.add_items * @returns {SmartContext} */ new_context(data = {}, opts = {}) { const item = new this.item_type(this.env, data); if (Array.isArray(opts.add_items)) item.add_items(opts.add_items); this.set(item); item.queue_save(); item.emit_event("context:created"); return item; } /** * Default settings for all SmartContext items in this collection. * @readonly */ static get default_settings() { return { template_before: "\n{{FILE_TREE}}", template_after: "" }; } get settings_config() { return { ...this.env.config.actions.context_merge_template?.settings_config || {} }; } get_ref(key) { const collection = key.includes("#") ? this.env.smart_blocks : this.env.smart_sources; return collection.get(key); } }; // node_modules/obsidian-smart-env/node_modules/smart-contexts/index.js var smart_contexts_default_config = { class: SmartContexts, data_adapter: AjsonSingleFileCollectionDataAdapter, item_type: SmartContext }; var smart_contexts_default = smart_contexts_default_config; // node_modules/obsidian-smart-env/node_modules/smart-contexts/context_item.js var ContextItem = class extends CollectionItem { // special handling because current name_to_collection_key removes "Items" suffix get collection_key() { return "context_items"; } get context_type_adapter() { if (!this._context_type_adapter) { const Class = this.collection.context_item_adapters.find((adapter_class) => adapter_class.detect(this.key, this.data)); if (!Class) throw new Error(`No context item adapter found for key: ${this.key}`); this._context_type_adapter = new Class(this); } return this._context_type_adapter; } get exists() { return this.context_type_adapter.exists; } // v3 async get_text() { const item_text = await this.context_type_adapter.get_text(); if (typeof item_text !== "string") return item_text; if (typeof this.actions.context_item_merge_template === "function") { return await this.actions.context_item_merge_template(item_text); } return item_text; } async get_base64() { if (this.is_media) { return await this.context_type_adapter.get_base64(); } return { error: `Context item is not media type: ${this.key}` }; } async open(event = null) { return await this.context_type_adapter.open(event); } get is_media() { return this.context_type_adapter.is_media || false; } get item_ref() { return this.context_type_adapter.ref || null; } get size() { return this.data.size || this.context_type_adapter.size || 0; } get mtime() { return this.data.mtime || this.context_type_adapter.mtime || null; } }; // node_modules/obsidian-smart-env/node_modules/smart-contexts/adapters/context-items/_adapter.js var ContextItemAdapter = class { constructor(item) { this.item = item; } static detect(key, data = {}) { return false; } get env() { return this.item.env; } get exists() { return true; } // v3 API /** * for calculating context size */ get size() { return 0; } async get_text() { } async open() { } }; // node_modules/obsidian-smart-env/node_modules/smart-contexts/adapters/context-items/block.js var BlockContextItemAdapter = class extends ContextItemAdapter { static order = 6; static detect(key) { return key.includes("#"); } get ref() { return this.env.smart_blocks.get(this.item.key); } get inlinks() { return this.ref.inlinks || []; } get outlinks() { return this.ref.outlinks || []; } get exists() { return !!(this.ref && !this.ref.is_gone); } get mtime() { return this.ref?.mtime || null; } get size() { return this.ref?.size || 0; } async get_text() { const block = this.ref; if (!block) return { error: "Block not found" }; return await block.read(); } async open(event = null) { this.ref.actions.source_open(event); } }; // node_modules/obsidian-smart-env/node_modules/smart-contexts/adapters/context-items/source.js var SourceContextItemAdapter = class extends ContextItemAdapter { static order = 7; // default lowest priority static detect(key) { return true; } get ref() { return this.env.smart_sources.get(this.item.key); } get inlinks() { return this.ref.inlinks || []; } get outlinks() { return this.ref.outlinks || []; } get exists() { return !!(this.ref && !this.ref.is_gone); } get size() { return this.ref?.size || 0; } get mtime() { return this.ref?.mtime || null; } async get_text() { return await this.ref?.read() || "MISSING SOURCE"; } async open(event = null) { this.ref.actions.source_open(event); } }; // node_modules/obsidian-smart-env/node_modules/smart-contexts/utils/image_extension_regex.js var image_extension_regex = /\.(png|jpe?g|gif|bmp|webp|svg|ico|mp4)$/i; // node_modules/obsidian-smart-env/node_modules/smart-contexts/adapters/context-items/image.js var ImageContextItemAdapter = class extends ContextItemAdapter { static detect(key) { if (image_extension_regex.test(key)) return "image"; return false; } get exists() { return this.item.env.smart_sources.fs.exists_sync(this.item.key); } get is_media() { return true; } async get_base64() { const ext = this.item.key.split(".").pop().toLowerCase(); try { const base64_data = await this.item.env.fs.read(this.item.key, "base64"); const base64_url = `data:image/${ext};base64,${base64_data}`; return { type: "image_url", key: this.item.key, name: this.item.key.split(/[\\/]/).pop(), url: base64_url }; } catch (err) { console.warn(`Failed to convert image ${this.item.key} to base64`, err); return { error: `Failed to convert image to base64: ${err.message}` }; } } }; // node_modules/obsidian-smart-env/node_modules/smart-contexts/adapters/context-items/pdf.js var PdfContextItemAdapter = class extends ContextItemAdapter { static detect(key) { if (key.endsWith(".pdf")) return "pdf"; return false; } async add_to_snapshot(snapshot) { if (!snapshot.pdfs) snapshot.pdfs = []; snapshot.pdfs.push(this.item.key); } get is_media() { return true; } async get_base64() { try { const base64_data = await this.item.env.fs.read(this.item.key, "base64"); const base64_url = `data:application/pdf;base64,${base64_data}`; return { type: "pdf_url", key: this.item.key, name: this.item.key.split(/[\\/]/).pop(), url: base64_url }; } catch (err) { console.warn(`Failed to convert PDF ${this.item.key} to base64`, err); return { error: `Failed to convert PDF to base64: ${err.message}` }; } } get exists() { return this.item.env.smart_sources.fs.exists_sync(this.item.key); } }; // node_modules/obsidian-smart-env/node_modules/smart-contexts/context_items.js var ContextItems = class extends Collection { async load() { console.log("ContextItems: load called"); } static version = 1; get context_item_adapters() { if (!this._context_item_adapters) { this._context_item_adapters = Object.values(this.opts.context_item_adapters).sort((a, b) => { const order_a = a.order || 0; const order_b = b.order || 0; return order_a - order_b; }); } return this._context_item_adapters; } new_item(data) { const item = new this.item_type(this.env, data); this.set(item); return item; } process_load_queue() { } get settings_config() { return { ...this.env.config.actions.context_item_merge_template?.settings_config || {} }; } get_adapter_class(key, item_data) { return this.context_item_adapters.find((adapter_class) => adapter_class.detect(key, item_data)); } static get default_settings() { return { template_before: '', template_after: "" }; } load_from_data(context_items_data) { delete this.items; this.items = {}; const entries = Object.entries(context_items_data || {}); for (let i = 0; i < entries.length; i++) { const [key, item_data] = entries[i]; if (item_data.exclude) continue; this.new_item({ key, ...item_data }); } } }; var context_items_default = { version: 1, class: ContextItems, collection_key: "context_items", item_type: ContextItem, context_item_adapters: { BlockContextItemAdapter, SourceContextItemAdapter, ImageContextItemAdapter, PdfContextItemAdapter } }; // node_modules/obsidian-smart-env/node_modules/smart-events/event_log.js function next_log_stats(prev = {}, at_ms) { const ct = (prev.ct || 0) + 1; const first_at = prev.first_at ?? at_ms; const last_at = at_ms; return { ct, first_at, last_at }; } var EventLog = class extends CollectionItem { static version = 2e-3; /** @returns {{data: EventLogData}} */ static get defaults() { return { data: { key: null, ct: 0, first_at: null, last_at: null } }; } /** * Counters are updated via EventLogs listener. * @param {Partial} [_input_data] */ init(_input_data) { } }; // node_modules/obsidian-smart-env/node_modules/smart-events/event_logs.js var EXCLUDED_EVENT_KEYS = { "collection:save_started": true, "collection:save_completed": true }; var EventLogs = class extends Collection { static version = 3e-3; constructor(env, opts = {}) { super(env, opts); this.session_events = []; this.notification_status = null; } /** * Factory that attaches the collection to env and registers the wildcard listener. * @param {Object} env * @param {Object} [opts={}] * @returns {EventLogs} */ static create(env, opts = {}) { const instance = new this(env, opts); instance.init(); return instance; } /** Prefer an explicit item class to keep wiring thin. */ get item_type() { return EventLog; } /** * Instance init * - Ensure env.events exists * - Register wildcard listener * - Idempotent across repeated calls */ init() { if (!this.env?.events) SmartEvents.create(this.env); if (this._unsub_wildcard) this._unsub_wildcard(); this._unsub_wildcard = this.env.events.on(WILDCARD_KEY, (event, event_key) => { this.on_any_event(event_key, event); }); } /** * Handle any emitted event. * Persists counters and timestamps in epoch ms. * @param {string} event_key * @param {Record} event */ on_any_event(event_key, event) { if (EXCLUDED_EVENT_KEYS[event_key]) return; this.session_events.push({ event_key, event }); if (event_key === "notification:error") this.notification_status = "error"; else if (event_key === "notification:warning" && this.notification_status !== "error") this.notification_status = "warning"; else if (event_key === "notification:attention" && !this.notification_status) this.notification_status = "attention"; try { if (typeof event_key !== "string") return; const at_ms = Date.now(); let event_log = this.get(event_key); if (!event_log) { event_log = new EventLog(this.env, { key: event_key }); this.set(event_log); this.emit_event("event_log:first", { first_of_event_key: event_key }); } const next = next_log_stats( { ct: event_log.data.ct, first_at: event_log.data.first_at, last_at: event_log.data.last_at }, at_ms ); event_log.data = { ...event_log.data, ...next }; if (event.event_source) { if (!event_log.data.event_sources) event_log.data.event_sources = {}; if (!event_log.data.event_sources[event.event_source]) { event_log.data.event_sources[event.event_source] = 0; } event_log.data.event_sources[event.event_source]++; } event_log.queue_save(); this.queue_save(); } catch (err) { console.error("[EventLogs] record failure", event_key, err); } } /** * Cleanly detach listeners and cancel pending save. */ unload() { if (this._save_timer) { clearTimeout(this._save_timer); this._save_timer = null; } if (typeof this._unsub_wildcard === "function") { this._unsub_wildcard(); this._unsub_wildcard = null; } return super.unload(); } }; var event_logs_default = { class: EventLogs, collection_key: "event_logs", data_adapter: AjsonSingleFileCollectionDataAdapter, item_type: EventLog }; // node_modules/obsidian-smart-env/src/modals/smart_fuzzy_suggest_modal.js var import_obsidian4 = require("obsidian"); var SmartFuzzySuggestModal = class extends import_obsidian4.FuzzySuggestModal { constructor(item_or_collection) { const env = item_or_collection.env; const plugin = env.plugin; const app = plugin.app; super(app); this.app = app; env.create_env_getter(this); this.plugin = plugin; this.item_or_collection = item_or_collection; this.emptyStateText = "No suggestions available"; } /** Unique type key for this modal class. Subclasses override. */ static get modal_type() { return "smart-fuzzy-suggest"; } /** Human label used in commands. Subclasses override as needed. */ static get display_text() { return "Smart Fuzzy Suggest"; } /** Event name listened to on env.events to open this modal. */ static get event_domain() { return `${this.modal_type}`; } /** Command id used with addCommand. */ static get command_id() { return this.modal_type; } static open(item_or_collection, params) { const Modal10 = ( /** @type {typeof SmartFuzzySuggestModal} */ this ); const modal = new Modal10(item_or_collection, params); modal.open(params); return modal; } static register_modal(plugin) { const Modal10 = ( /** @type {typeof SmartFuzzySuggestModal} */ this ); const env = plugin?.env; const modal_config = { ...env.config.modals?.[this.modal_key] || {}, class: null }; console.log(`Registering modal: ${this.display_text}`, { modal_config, Modal: Modal10 }); const open_handler = (payload = {}) => { const item = Modal10.resolve_item_from_payload(env, payload); const modal = Modal10.open(item, { ...modal_config, ...payload // spread since event payload is locked }); return modal; }; const disposers = [ env?.events?.on?.(`${Modal10.event_domain}:open`, open_handler) // env.events?.on?.(`${Modal.event_domain}:suggest`, suggest_handler), ]; const dispose_all = () => { disposers.forEach((dispose) => typeof dispose === "function" && dispose()); }; if (typeof plugin.register === "function") { plugin.register(() => dispose_all()); } return { event_domain: Modal10.event_domain }; } static resolve_item_from_payload(env, payload) { const item = env?.[payload.collection_key]?.items?.[payload.item_key]; return item; } open(params = {}) { super.open(); this.modalEl.addEventListener("keydown", (e) => { if (e.key === "Enter") this.selectActiveSuggestion(e); const is_cursor_end_of_input = this.inputEl.selectionStart === this.inputEl.value.length; if (e.target !== this.inputEl || !this.inputEl.value || is_cursor_end_of_input || import_obsidian4.Keymap.isModEvent(e)) { if (e.key === "ArrowLeft") { this.use_arrow_left = true; this.selectActiveSuggestion(e); return; } if (e.key === "ArrowRight" && (is_cursor_end_of_input || e.target !== this.inputEl || !this.inputEl.value)) { e.preventDefault(); this.use_mod_select = true; this.use_arrow_right = true; this.selectActiveSuggestion(e); return; } } }); } getItems() { return this.get_suggestions(); } getItemText(suggestion_item) { return suggestion_item.display; } filter_suggestions(suggestions) { return suggestions; } get_suggestions() { if (this.suggestions?.length) { this.suggestions = this.filter_suggestions(this.suggestions); if (this.suggestions.length > 0) { return this.suggestions; } } if (this.default_suggest_action_keys?.length) { if (this.default_suggest_action_keys.length === 1) { this.update_suggestions(this.default_suggest_action_keys[0]); return []; } return this.get_suggest_scopes(); } return []; } get_suggest_scopes() { return this.default_suggest_action_keys.map((action_key) => { const action_config = this.env.config.actions[action_key]; if (!action_config) return null; const display_name5 = action_config.display_name || action_key; return { select_action: () => { this.update_suggestions(action_key); }, key: action_key, display: display_name5 }; }).filter(Boolean); } async update_suggestions(suggest_ref) { if (typeof suggest_ref === "string") { suggest_ref = this.item_or_collection.actions[suggest_ref]; } if (typeof suggest_ref === "function") { const result = await suggest_ref({ modal: this }); console.log("Suggestion action result", result); if (Array.isArray(result) && result.length) { this.suggestions = result; } } else if (Array.isArray(suggest_ref)) { this.suggestions = suggest_ref; } if (Array.isArray(this.suggestions) && this.suggestions.length) { this.updateSuggestions(); } else { this.env.events.emit("notification:error", { message: "Invalid suggestion action" }); console.warn("Invalid suggestion action", suggest_ref); } } get default_suggest_action_keys() { if (Array.isArray(this.params?.default_suggest_action_keys)) { return this.params.default_suggest_action_keys; } return this.env.config.modals[this.modal_key]?.default_suggest_action_keys || []; } renderSuggestion(sug, el) { super.renderSuggestion(sug, el); if (sug.item.icon) { el.addClass("sc-modal-suggestion-has-icon"); const icon_el = el.createEl("span"); (0, import_obsidian4.setIcon)(icon_el, sug.item.icon); } return el; } onChooseSuggestion(selected, evt, ...other) { this.prevent_close = true; const suggestion = selected.item; const is_arrow_left = this.use_arrow_left; const is_arrow_right = this.use_arrow_right; const is_mod_select = import_obsidian4.Keymap.isModifier(evt, "Mod") || this.use_mod_select; this.use_arrow_right = false; this.use_mod_select = false; this.use_arrow_left = false; if (is_arrow_left) { if (typeof suggestion.arrow_left_action === "function") { this.handle_choose_action(suggestion, "arrow_left_action"); } else { if (this.last_input_value) { this.inputEl.value = this.last_input_value; setTimeout(() => { const len = this.inputEl.value.length; this.inputEl.setSelectionRange(len, len); }, 0); this.last_input_value = null; } this.suggestions = null; this.params.default_suggest_action_keys = null; this.updateSuggestions(); return; } } else if (is_arrow_right && typeof suggestion.arrow_right_action === "function") { this.handle_choose_action(suggestion, "arrow_right_action"); } else if (is_mod_select && typeof suggestion.mod_select_action === "function") { this.handle_choose_action(suggestion, "mod_select_action"); } else if (typeof suggestion.select_action === "function") { this.handle_choose_action(suggestion, "select_action"); } else { this.env.events.emit("notification:warning", { selection_display: suggestion.display, message: "No action defined for this suggestion" }); } } async handle_choose_action(suggestion, action_key) { let chosen_action = suggestion[action_key]; const result = await chosen_action({ modal: this }); if (Array.isArray(result) && result.length) { this.suggestions = result; } else if (Array.isArray(result)) { this.env.events.emit("notification:info", { message: "No suggestions returned from action" }); } const idx = this.chooser.values.findIndex((i) => i.item?.display === suggestion.display); setTimeout(() => { this.updateSuggestions(); if (idx !== -1) { this.chooser.setSelectedItem(idx); } }, 100); } close() { setTimeout(() => { if (!this.prevent_close) super.close(); this.prevent_close = false; }, 10); } onClose() { this.item_or_collection.emit_event(`${this.constructor.event_domain}:closed`); } }; // node_modules/obsidian-smart-env/src/modals/context_selector.js var import_obsidian5 = require("obsidian"); var ContextModal = class extends SmartFuzzySuggestModal { /** Modal identity */ static get modal_type() { return "context_selector"; } static get display_text() { return "Context Selector"; } static get event_domain() { return "context_selector"; } static get command_id() { return this.modal_type; } static get modal_key() { return "context_selector"; } get modal_key() { return "context_selector"; } constructor(smart_context, params = {}) { super(smart_context); this.params = { ...params }; this.smart_context = smart_context; this.setInstructions([ { command: "Enter", purpose: "Add to context" }, { command: `\u2192 / \u2190`, purpose: "Toggle block view" }, { command: "Esc", purpose: "Close" } ]); } open(params = {}) { this.params = { ...this.params, ...params }; super.open(); this.render(this.params); } async render(params = this.params) { this.modalEl.style.display = "flex"; this.modalEl.style.flexDirection = "column"; this.modalEl.style.height = "100%"; this.modalEl.prepend( await this.env.smart_components.render_component( "smart_context_item", this.smart_context, params ) ); } filter_suggestions(suggestions) { return suggestions.filter((s) => { if (s.key && this.smart_context?.data?.context_items[s.key]) return false; return true; }); } }; // node_modules/obsidian-smart-env/src/modals/notifications_feed_modal.js var import_obsidian6 = require("obsidian"); var NotificationsFeedModal = class extends import_obsidian6.Modal { constructor(app, env) { super(app); this.env = env; } async onOpen() { this.titleEl.setText("Smart Env notifications"); this.contentEl.empty(); const event_log = await this.env.smart_components.render_component("notifications_feed", this.env); this.contentEl.appendChild(event_log); } onClose() { this.contentEl.empty(); } }; // node_modules/obsidian-smart-env/src/modals/milestones_modal.js var import_obsidian7 = require("obsidian"); var MilestonesModal = class extends import_obsidian7.Modal { constructor(app, env) { super(app); this.env = env; } async onOpen() { this.titleEl.setText("Smart Milestones"); this.contentEl.empty(); const milestones = await this.env.smart_components.render_component("milestones", this.env, {}); this.contentEl.appendChild(milestones); } onClose() { this.contentEl.empty(); } }; // node_modules/obsidian-smart-env/default.settings.js var default_settings = { is_obsidian_vault: true, smart_blocks: { embed_blocks: true, min_chars: 200 }, smart_sources: { min_chars: 200, embed_model: { adapter: "transformers", transformers: { model_key: "TaylorAI/bge-micro-v2" } }, excluded_headings: "", file_exclusions: "Untitled", folder_exclusions: "" }, language: "en", re_import_wait_time: 13, smart_chat_threads: { chat_model: { adapter: "ollama", ollama: {} } }, smart_notices: {}, smart_view_filter: { expanded_view: false, render_markdown: true, show_full_path: false }, version: "", new_user: true, // DEPRECATED: 2025-06-05 (use localStorage instead???) // 2025-11-26 models: { embedding_platform: "transformers", chat_completion_platform: "open_router" } }; // node_modules/obsidian-smart-env/node_modules/smart-models/items/model.js var Model = class extends CollectionItem { /** * Default properties for an instance of CollectionItem. * @returns {Object} */ static get defaults() { return { data: { api_key: "", provider_key: "", model_key: "" } }; } get_key() { if (!this.data.key) { this.data.created_at = Date.now(); this.data.key = `${this.data.provider_key}#${this.data.created_at}`; } return this.data.key; } get provider_key() { return this.data.provider_key; } get env_config() { return this.collection.env_config; } get provider_config() { return this.env_config.providers?.[this.provider_key] || {}; } get ProviderAdapterClass() { return this.provider_config.class; } get instance() { if (!this._instance) { if (!this.ProviderAdapterClass) { const new_default_model = this.collection.new_model({ provider_key: this.collection.default_provider_key }); return new_default_model.instance; } const Class = this.ProviderAdapterClass; this._instance = new Class(this); this._instance.load(); this.once_event("model:changed", () => { this._instance.unload?.(); this._instance = null; }); } return this._instance; } async count_tokens(text) { return this.instance.count_tokens(text); } get api_key() { return this.data.api_key; } /** * Create (or reuse) a proxy around a target settings object so that * any mutations trigger queue_save on the model. * Proxies are cached per-target via WeakMap to support deep nested objects. * * @param {Object} target - The settings object or nested object to wrap. * @returns {Object} Proxied object or original value if not an object. * @private */ create_settings_proxy(target) { if (!target || typeof target !== "object") return target; if (!this._settings_proxy_map) { this._settings_proxy_map = /* @__PURE__ */ new WeakMap(); } const existing = this._settings_proxy_map.get(target); if (existing) return existing; const self = this; const handler = { get(target_obj, prop, receiver) { const value = Reflect.get(target_obj, prop, receiver); if (value && typeof value === "object") { return self.create_settings_proxy(value); } return value; }, set(target_obj, prop, value, receiver) { const previous = target_obj[prop]; const result = Reflect.set(target_obj, prop, value, receiver); if (previous !== value) { self.debounce_save(); } return result; }, deleteProperty(target_obj, prop) { const had = Object.prototype.hasOwnProperty.call(target_obj, prop); const result = Reflect.deleteProperty(target_obj, prop); if (had) { self.debounce_save(); } return result; } }; const proxy = new Proxy(target, handler); this._settings_proxy_map.set(target, proxy); return proxy; } debounce_save(ms = 100) { this.emit_event("model:changed"); if (this._debounce_save_timeout) { clearTimeout(this._debounce_save_timeout); } this._debounce_save_timeout = setTimeout(() => { this.queue_save(); this.collection.process_save_queue(); this._debounce_save_timeout = null; }, ms); } async get_model_key_options() { const model_configs = await this.instance.get_models(); return Object.entries(model_configs).map(([key, model_config]) => ({ label: model_config.name || key, value: model_config.key || key })).sort((a, b) => { if (a.label.toLowerCase().includes("free") && !b.label.toLowerCase().includes("free")) { return -1; } if (!a.label.toLowerCase().includes("free") && b.label.toLowerCase().includes("free")) { return 1; } return a.label.localeCompare(b.label); }); } model_changed(key, value, elm) { if (key === "model_key") { this.data.model_key = value; const model_defaults = this.data.provider_models?.[this.data.model_key] || {}; const adapter_defaults = this.ProviderAdapterClass.defaults || {}; delete this.data.test_passed; this.data = { ...this.data, ...adapter_defaults, ...model_defaults }; } if (!["api_key", "meta.name"].includes(key)) { this.emit_event("model:changed"); } } /** * @abstract should be implemented by subclasses */ async test_model() { } get display_name() { return this.data.meta?.name || `${this.data.provider_key} - ${this.data.model_key}`; } get settings_config() { return { provider_key: { type: "html", value: `

Provider: ${this.data.provider_key}

` }, "meta.name": { type: "text", name: "Name", description: "A friendly name for this model configuration." }, model_key: { type: "dropdown", name: "Model", description: "The model to use from the selected provider.", options_callback: "get_model_key_options", callback: "model_changed" }, // add model_changed callback to each provider setting that doesn't already have callback defined ...Object.fromEntries( Object.entries(this.provider_config.settings_config || {}).map( ([setting_key, setting_config]) => [setting_key, { ...setting_config, callback: setting_config.callback || "model_changed" }] ) ) }; } delete_model() { this.delete(); this.debounce_save(); } /** * Reactive settings view for this model. * Mutating any property (including nested objects/arrays) via this proxy * will call queue_save(). * * @returns {Object} Proxied view of this.data. */ get settings() { return this.create_settings_proxy(this.data); } get model_key() { return this.data.model_key; } /** * @deprecated included for backward compatibility */ get opts() { return this.settings; } }; // node_modules/obsidian-smart-env/node_modules/smart-models/collections/models.js var Models = class extends Collection { model_type = "Model type"; // replace in subclass new_model(data = {}) { if (!data.provider_key) throw new Error("provider_key is required to create a new model"); const existing_from_provider = this.filter((m) => m.provider_key === data.provider_key).sort((a, b) => b.data.created_at - a.data.created_at)[0]; if (existing_from_provider) { if (!data.api_key && existing_from_provider.data.api_key) { data.api_key = existing_from_provider.data.api_key; } } const item = new this.item_type(this.env, { ...data }); this.set(item); this.settings.default_model_key = item.key; this.emit_event("model:changed"); item.queue_save(); return item; } /** * Retrieve the provider key used when creating a default model. * @abstract * @returns {string} provider key for the default model. */ get default_provider_key() { throw new Error("default_provider_key not implemented"); } get default_model_key() { const should_update_default = !this.settings.default_model_key || !this.get(this.settings.default_model_key) || this.get(this.settings.default_model_key).deleted; if (should_update_default) { const existing = this.filter((m) => !m.deleted).sort((a, b) => b.data.created_at - a.data.created_at)[0]; if (existing) { this.settings.default_model_key = existing.key; } else { const new_default = this.new_model({ provider_key: this.default_provider_key }); new_default.queue_save(); this.process_save_queue(); this.settings.default_model_key = new_default.key; } } return this.settings.default_model_key; } get default() { return this.get(this.default_model_key); } get env_config() { return this.env.config.collections[this.collection_key]; } get_model_key_options() { return this.filter((i) => !i.deleted && i.ProviderAdapterClass).map((model) => ({ label: model.data.meta?.name || `${model.provider_key} - ${model.data.model_key}`, value: model.key })); } }; function settings_config5(scope) { return { default_model_key: { type: "dropdown", name: `Default ${scope.model_type.toLowerCase()} model`, description: `Used as the default ${scope.model_type.toLowerCase()} model when no other is specified.`, options_callback: () => { return scope.get_model_key_options(); }, callback: async (value, setting) => { scope.emit_event("model:changed"); } } }; } // node_modules/obsidian-smart-env/node_modules/smart-models/items/embedding_model.js var EmbeddingModel = class extends Model { /** * Default properties for an instance of CollectionItem. * @returns {Object} */ static get defaults() { return { data: { api_key: "", provider_key: "transformers", model_key: "TaylorAI/bge-micro-v2", dims: 384, // ??? max_tokens: 512 // ??? } }; } async embed(input) { if (typeof input === "string") { input = [{ embed_input: input }]; } return (await this.embed_batch(input))[0]; } async embed_batch(inputs) { return this.instance.embed_batch(inputs); } async test_model() { try { const resp = await this.embed("test input"); const success = resp && !resp?.error; this.data.test_passed = success; this.debounce_save(); return { success, response: resp }; } catch (e) { this.data.test_passed = false; return { error: e.message || String(e) }; } } }; // node_modules/obsidian-smart-env/node_modules/smart-models/collections/embedding_models.js var EmbeddingModels = class extends Models { model_type = "Embedding"; get default_provider_key() { return "transformers"; } }; var embedding_models_collection = { class: EmbeddingModels, data_dir: "embedding_models", collection_key: "embedding_models", data_adapter: ajson_single_file_default, item_type: EmbeddingModel, providers: { // transformers // replace with platform-specific import in obsidian-smart-env }, settings_config: settings_config5 }; var embedding_models_default = embedding_models_collection; // node_modules/obsidian-smart-env/src/adapters/embedding-model/transformers_iframe.js var TransformersIframeEmbeddingModelAdapter = class extends SmartEmbedTransformersIframeAdapter { constructor(model_item) { super(model_item); } get models() { return { "TaylorAI/bge-micro-v2": { "id": "TaylorAI/bge-micro-v2", "batch_size": 1, "dims": 384, "max_tokens": 512, "name": "BGE-micro-v2 (fastest)", "description": "Local, 512 tokens, 384 dim (recommended)", "adapter": "transformers" }, "Snowflake/snowflake-arctic-embed-xs": { "id": "Snowflake/snowflake-arctic-embed-xs", "batch_size": 1, "dims": 384, "max_tokens": 512, "name": "Snowflake Arctic Embed XS (fast)", "description": "Local, 512 tokens, 384 dim", "adapter": "transformers" }, "intfloat/multilingual-e5-small": { "id": "intfloat/multilingual-e5-small", "batch_size": 1, "dims": 384, "max_tokens": 512, "name": "Multilingual E5 Small", "description": "Local, 512 tokens, 384 dim", "adapter": "transformers" } // "onnx-community/embeddinggemma-300m-ONNX": { // "id": "onnx-community/embeddinggemma-300m-ONNX", // "batch_size": 1, // "dims": 768, // "max_tokens": 2048, // "name": "EmbeddingGemma 300M", // "description": "Local, 512 tokens, 768 dim", // "adapter": "transformers" // } }; } }; var transformers_iframe_default = { class: TransformersIframeEmbeddingModelAdapter, settings_config: settings_config4 }; // node_modules/obsidian-smart-env/src/collections/embedding_models.js embedding_models_default.providers = { transformers: transformers_iframe_default }; var embedding_models_default2 = embedding_models_default; // node_modules/obsidian-smart-env/src/items/lookup_list.js var LookupList = class extends CollectionItem { static key = "lookup_list"; static get defaults() { return { data: {} }; } async pre_process(params) { if (typeof this.actions.lookup_list_pre_process === "function") { await this.actions.lookup_list_pre_process(params); } } async get_results(params = {}) { await this.pre_process(params); let results = this.filter_and_score(params); if (this.should_post_process) results = await this.post_process(results, params); this.emit_event("lookup:get_results"); return results; } filter_and_score(params = {}) { const collection = this.env[params.results_collection_key] || this.env[this.collection.results_collection_key]; const score_errors = []; const { results: raw_results } = Object.values(collection.items).reduce((acc, target) => { const scored = target.filter_and_score(params); if (!scored?.score) { if (scored?.error) score_errors.push(scored.error); return acc; } results_acc(acc, scored, params.limit || 20); return acc; }, { min: 0, results: /* @__PURE__ */ new Set() }); const results = Array.from(raw_results).sort(sort_by_score_descending); if (!results.length) return results; while (!results.some((r) => r.score > 0.5)) { results.forEach((r) => r.score *= 2); } return results; } async post_process(results, params = {}) { return results; } get should_post_process() { return this.settings.lookup_post_process && this.settings.lookup_post_process !== "none"; } // for compatibility with v3 connections list item get item() { return this; } }; // node_modules/obsidian-smart-env/src/utils/create_settings_section_heading.js function create_settings_section_heading(heading) { return { type: "heading", name: `${heading}` }; } // node_modules/obsidian-smart-env/src/collections/lookup_lists.js var settings_config6 = { results_collection_key: { name: "Lookup results type", type: "dropdown", description: "Choose whether results should be sources or blocks.", option_1: "smart_sources|Sources", option_2: "smart_blocks|Blocks", options_callback: (scope) => { const options = [ { value: "smart_sources", name: "Sources" } ]; if (scope.env.smart_blocks) { options.push({ value: "smart_blocks", name: "Blocks" }); } return options; } } }; var LookupLists = class extends Collection { static get default_settings() { return { results_collection_key: "smart_blocks", score_algo_key: "similarity", results_limit: 20 }; } static version = 0.01; new_item({ query, filter }) { if (!query || typeof query !== "string" || !query.trim()) { throw new Error("LookupLists.new_item requires a non-empty query string."); } const date = format_ymd(/* @__PURE__ */ new Date()); const hash = murmur_hash_32_alphanumeric(query); const key = `${date}+${hash}`; if (this.items[key]) return this.items[key]; const list = new LookupList(this.env, { key, query, filter }); this.set(list); return list; } get settings_config() { return { ...settings_config6 }; } process_load_queue() { } get results_collection_key() { const stored_key = this.settings?.results_collection_key; if (this.env.collections?.[stored_key]) return stored_key; return "smart_sources"; } }; function format_ymd(d) { const y = d.getFullYear(); const m = String(d.getMonth() + 1).padStart(2, "0"); const day = String(d.getDate()).padStart(2, "0"); return `${y}-${m}-${day}`; } var lookup_lists_default = { class: LookupLists, collection_key: "lookup_lists", item_type: LookupList, settings_config: settings_config6 }; // node_modules/obsidian-smart-env/src/utils/format_collection_name.js function format_collection_name(key) { return key.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()); } // node_modules/obsidian-smart-env/src/components/collection_settings.js async function build_html2(collection, opts = {}) { const settings_html = Object.entries(collection.settings_config).map(([setting_key, setting_config]) => { if (!setting_config.setting) setting_config.setting = setting_key; return this.render_setting_html(setting_config); }).join("\n"); const html = `

${format_collection_name(collection.collection_key)}

${settings_html}
`; return html; } async function render2(collection, opts = {}) { const html = await build_html2.call(this, collection, opts); const frag = this.create_doc_fragment(html); await this.render_setting_components(frag, { scope: collection }); if (opts.settings_container) { this.empty(opts.settings_container); opts.settings_container.appendChild(frag.querySelector(".collection-settings")); } else { collection.settings_container = frag.querySelector(".collection-settings-container"); } return collection.settings_container; } // node_modules/obsidian-smart-env/src/utils/register_block_hover_popover.js var import_obsidian8 = require("obsidian"); function register_block_hover_popover(parent, target, env, block_key, params = {}) { const app = env?.plugin?.app || window.app; target.addEventListener("mouseover", async (ev) => { if (import_obsidian8.Keymap.isModEvent(ev)) { const block = env.smart_blocks.get(block_key); const markdown = await block?.read(); if (markdown) { const popover = new import_obsidian8.HoverPopover(parent, target); const frag = env.smart_view.create_doc_fragment(`
`); popover.hoverEl.classList.add("smart-block-popover"); popover.hoverEl.appendChild(frag); const sizer = popover.hoverEl.querySelector(".markdown-preview-sizer"); import_obsidian8.MarkdownRenderer.render(app, markdown, sizer, "/", popover); const event_domain = params.event_key_domain || "block"; block.emit_event(`${event_domain}:hover_preview`); } } }); } // node_modules/obsidian-smart-env/src/utils/register_item_hover_popover.js var import_obsidian9 = require("obsidian"); function register_item_hover_popover(container, item, params = {}) { const app = item.env?.plugin?.app || window.app; if (item.key.indexOf("{") === -1) { container.addEventListener("mouseover", (event) => { const linktext_path = item.key.replace(/#$/, ""); app.workspace.trigger("hover-link", { event, source: "smart-connections-view", hoverParent: container.parentElement, targetEl: container, linktext: linktext_path }); if (import_obsidian9.Keymap.isModEvent(event)) { const event_domain = params.event_key_domain || item.collection_key || "item"; item.emit_event(`${event_domain}:hover_preview`); } }); } else { register_block_hover_popover(container.parentElement, container, item.env, item.key); } } // node_modules/obsidian-smart-env/src/components/context-item/leaf.js var import_obsidian10 = require("obsidian"); function format_score(score) { const numeric_score = typeof score === "number" ? score : Number.parseFloat(score); if (!Number.isFinite(numeric_score)) return null; return Number.parseFloat(numeric_score.toFixed(2)).toString(); } function format_size(size) { const numeric_size = typeof size === "number" ? size : Number.parseFloat(size); if (!Number.isFinite(numeric_size) || numeric_size < 0) return null; const units = ["B", "KB", "MB", "GB"]; let size_value = numeric_size; let unit_index = 0; while (size_value >= 1024 && unit_index < units.length - 1) { size_value /= 1024; unit_index += 1; } const precision = size_value >= 10 || Number.isInteger(size_value) ? 0 : 1; const rounded_value = Number.parseFloat(size_value.toFixed(precision)); return `${rounded_value.toString()} ${units[unit_index]}`; } function build_badge_html(label, class_name) { if (!label) return ""; return `${label}`; } function build_html3(context_item, params = {}) { let name; if (context_item.item_ref) { if (context_item.item_ref.key.includes("#")) { const name_pcs = context_item.item_ref.key.split("/").pop().split("#").filter(Boolean); const last_pc = name_pcs.pop(); const segments = []; if (last_pc && last_pc.startsWith("{")) { segments.push(name_pcs.pop()); segments.push(context_item.item_ref.lines.join("-")); name = segments.join(" > Lines: "); } } else { name = context_item.item_ref.key.split("/").pop(); } } else { name = context_item.key.split("/").pop(); } const score = format_score(context_item?.data?.score); const size = format_size(context_item?.size || context_item?.data?.size); const score_html = build_badge_html(score, "sc-context-item-score"); const size_html = build_badge_html(size, "sc-context-item-size"); return ` \xD7 ${score_html} ${name || context_item.key} ${size_html} `; } async function render3(context_item, params = {}) { const html = build_html3(context_item, params); const frag = this.create_doc_fragment(html); const container = frag.firstElementChild; post_process2.call(this, context_item, container, params); return container; } async function post_process2(context_item, container, params = {}) { const env = context_item.env; const remove_btn = container.querySelector(".sc-context-item-remove"); if (remove_btn) { remove_btn.addEventListener("click", (event) => { const target = event.currentTarget; const tree_container = target.closest("[data-context-key]"); const ctx_key = tree_container?.getAttribute("data-context-key"); const ctx = env.smart_contexts.get(ctx_key); ctx.remove_item(context_item.key); }); } if (context_item.item_ref) { const name2 = container.querySelector(".sc-context-item-name"); name2.setAttribute("title", `Hold ${import_obsidian10.Platform.isMacOS ? "\u2318" : "Ctrl"} to preview`); register_item_hover_popover(name2, context_item.item_ref); } const name = container.querySelector(".sc-context-item-name"); name.addEventListener("click", (event) => { context_item.open(event); }); return container; } // node_modules/obsidian-smart-env/src/components/env_stats.js async function build_html4(env, opts = {}) { const lines = []; lines.push(`

Collections

`); const collection_keys = Object.keys(env.collections).filter((key) => ["smart_sources", "smart_blocks"].includes(key)).sort((a, b) => { if (a === "smart_sources" || a === "smart_blocks") return -1; if (b === "smart_sources" || b === "smart_blocks") return 1; return a.localeCompare(b); }); for (const collection_key of collection_keys) { const collection = env[collection_key]; if (!collection || !collection.items) { lines.push(`

${format_collection_name(collection_key)}

No valid items.

`); continue; } const snippet = generate_collection_stats(collection, collection_key); lines.push(snippet); } return `
${lines.join("\n")}
`; } async function render4(env, opts = {}) { const html = await build_html4.call(this, env, opts); const frag = this.create_doc_fragment(html); return await post_process3.call(this, env, frag, opts); } async function post_process3(env, frag, opts = {}) { return frag; } function generate_collection_stats(collection, collectionKey) { const total_items = Object.values(collection.items).length; const niceName = format_collection_name(collectionKey); const state = collection.env.collections[collectionKey]; if (state !== "loaded") { return `

${niceName}

Not loaded yet (${total_items} items known).

`; } const load_time_html = collection.load_time_ms ? `

Load time: ${collection.load_time_ms}ms

` : ""; const state_html = `

State: ${state}

`; let html = get_generic_collection_stats(collection, niceName, total_items); let embed_stats = ""; if (typeof collection.process_embed_queue === "function") { embed_stats = calculate_embed_coverage(collection, total_items); } return `

${niceName}

${embed_stats} ${html} ${load_time_html} ${state_html}
`; } function get_generic_collection_stats(collection, niceName, total_items, load_time_html) { return `

Total: ${total_items}

`; } function calculate_embed_coverage(collection, total_items) { const embedded_items = Object.values(collection.items).filter((item) => item.vec); if (!embedded_items.length) return "

No items embedded

"; const stats = Object.values(collection.items).reduce((acc, i) => { if (i.should_embed) acc.should_embed += 1; else acc.should_not_embed += 1; if (i.vec) acc.embedded += 1; if (i.should_embed && !i.vec) acc.missing_embed += 1; if (!i.should_embed && i.vec) acc.extraneous_embed += 1; return acc; }, { should_embed: 0, embedded: 0, missing_embed: 0, extraneous_embed: 0, should_not_embed: 0 }); const pct = stats.embedded / stats.should_embed * 100; const percent = Math.round(pct); return `

Embedding coverage: ${percent}% (${stats.embedded} / ${stats.should_embed})

` + (stats.missing_embed ? `

Missing embeddings: ${stats.missing_embed}

` : "") + (stats.extraneous_embed ? `

Extraneous embeddings: ${stats.extraneous_embed}

` : "") + (stats.should_not_embed ? `

Other items (e.g. less than minimum length to embed): ${stats.should_not_embed}

` : ""); } // node_modules/obsidian-smart-env/src/components/form/dropdown.js var import_obsidian11 = require("obsidian"); function build_html5(scope, params = {}) { return `
`; } async function render5(scope, params = {}) { const html = build_html5.call(this, scope, params); const frag = this.create_doc_fragment(html); return await post_process4.call(this, scope, frag, params); } async function post_process4(scope, container, params = {}) { if (!scope) { container.textContent = "Error: scope is required for dropdown component."; return container; } const settings = scope.settings; if (!settings || typeof settings !== "object") { container.textContent = "Error: scope.settings{} is required for dropdown component."; return container; } const setting_key = params.setting_key; if (!setting_key) { container.textContent = "Error: setting_key is required for dropdown component."; return container; } const options = params.options; if (!Array.isArray(options) || options.length === 0) { container.textContent = "Error: options[] is required for dropdown component."; return container; } const setting = new import_obsidian11.Setting(container); if (params.label && typeof setting.setName === "function") { setting.setName(params.label); } if (params.description && typeof setting.setDesc === "function") { setting.setDesc(params.description); } if (params.tooltip && typeof setting.setTooltip === "function") { setting.setTooltip(params.tooltip); } const current_value = get_by_path(settings, setting_key) ?? ""; let select_el = null; setting.addDropdown((dropdown) => { console.log({ dropdown, current_value, scope, options }); select_el = dropdown.selectEl; if (params.required) { select_el.setAttribute("required", "true"); } options.forEach((opt) => { dropdown.addOption(opt.value, opt.label); }); select_el.childNodes.forEach((option_el) => { if (option_el.value === current_value) { option_el.selected = true; } if (options.find((o) => o.value === option_el.value)?.disabled) { option_el.disabled = true; } }); if (select_el) { select_el.value = current_value; } }); const handler = () => { const value = select_el.value; if (typeof params.on_change === "function") { params.on_change(value, { scope, setting_key, select_el, container }); } else { set_by_path(scope.settings, setting_key, value); } }; select_el.addEventListener("change", handler); this.attach_disposer(select_el, () => { select_el.removeEventListener("change", handler); }); return container; } render5.version = 0.2; // node_modules/obsidian-smart-env/src/components/lean_coffee_callout.js var import_obsidian12 = require("obsidian"); function build_html6(env, opts = {}) { return `
Community Lean Coffee

Ask questions. Bring challenges. Request features. Show workflows. Be ready to share.
Join the next Community Lean Coffee meeting. Unable to attend? Submit a question here \u{1F334}

`; } function render6(env, opts = {}) { const html = build_html6.call(this, env, opts); const frag = this.create_doc_fragment(html); const callout = frag.querySelector("#lean-coffee-callout"); const icon_container = callout.querySelector(".callout-icon"); const icon = (0, import_obsidian12.getIcon)("smart-chat"); if (icon) { this.empty(icon_container); icon_container.appendChild(icon); } post_process5.call(this, env, callout, opts); return callout; } function post_process5(env, callout) { } // node_modules/obsidian-smart-env/src/components/milestones.css var milestones_default = `.sc-events-checklist {\r display: flex;\r flex-direction: column;\r gap: var(--size-4-3);\r padding: var(--size-4-2);\r \r .sc-events-checklist__header {\r display: flex;\r align-items: baseline;\r justify-content: space-between;\r gap: var(--size-4-3);\r }\r \r .sc-events-checklist__title {\r margin: 0;\r font-size: var(--h2-size);\r }\r \r .sc-events-checklist__summary {\r font-size: var(--font-ui-small);\r color: var(--text-muted);\r white-space: nowrap;\r }\r \r .sc-events-checklist__group {\r border-top: 1px solid var(--background-modifier-border);\r padding-top: var(--size-4-3);\r }\r \r .sc-events-checklist__group-title {\r margin: 0 0 var(--size-4-2) 0;\r font-size: var(--h3-size);\r }\r \r .sc-events-checklist__items {\r list-style: none;\r padding: 0;\r margin: 0;\r display: flex;\r flex-direction: column;\r gap: var(--size-2-2);\r }\r \r .sc-events-checklist__item {\r display: flex;\r flex-direction: column;\r gap: 2px;\r padding: 6px 8px;\r border-radius: var(--radius-s);\r \r &:hover {\r background: var(--background-modifier-hover);\r }\r }\r \r .sc-events-checklist__label {\r display: flex;\r align-items: flex-start;\r gap: var(--size-2-2);\r cursor: default;\r }\r \r .sc-events-checklist__checkbox {\r margin-top: 2px;\r }\r \r .sc-events-checklist__milestone {\r line-height: 1.3;\r }\r \r .sc-events-checklist__meta {\r padding-left: calc(var(--size-2-2) + 18px);\r }\r \r .sc-events-checklist__event-key {\r font-size: var(--font-ui-smaller);\r color: var(--text-muted);\r }\r \r .sc-events-checklist__item[data-checked='true'] {\r .sc-events-checklist__milestone {\r color: var(--text-normal);\r }\r }\r }\r \r /* 1) Host elements that should get a PRO badge */\r :is(\r .pro-milestone > span\r ) {\r position: relative; /* safe default, keeps ::after anchored */\r }\r \r /* 2) The PRO badge itself */\r :is(\r .pro-milestone > span\r )::after {\r content: "PRO";\r \r /* layout */\r display: inline-flex;\r align-items: center;\r justify-content: center;\r margin-left: 0.4em;\r padding: 0.08em 0.55em;\r border-radius: 999px;\r white-space: nowrap;\r vertical-align: middle;\r \r /* typography */\r font-size: 0.7em;\r font-weight: 600;\r letter-spacing: 0.14em;\r text-transform: uppercase;\r line-height: 1;\r \r /* color system: only Obsidian variables */\r background-color: var(--interactive-accent);\r background-image: linear-gradient(\r 135deg,\r var(--interactive-accent),\r var(--interactive-accent-hover)\r );\r color: var(--text-on-accent, var(--background-primary));\r border: 1px solid var(--background-modifier-border);\r \r /* subtle separation & depth, theme-aware */\r box-shadow:\r 0 0 0 1px var(--background-primary),\r 0 1px 3px rgba(0, 0, 0, 0.35);\r transform: translateY(-0.03em);\r }\r \r /* 3) Interactive refinement: follow Obsidian's accent hover behavior */\r :is(\r .pro-milestone > span\r ):hover::after {\r background-color: var(--interactive-accent-hover);\r filter: brightness(1.05);\r }\r \r `; // node_modules/obsidian-smart-env/src/utils/onboarding_events.js var import_obsidian13 = require("obsidian"); function register_first_of_event_notifications(env) { env.events.on("event_log:first", (data) => { const event_key = data?.first_of_event_key; if (typeof event_key === "string" && event_key in EVENTS_CHECKLIST_ITEMS_BY_EVENT_KEY) { const frag = document.createDocumentFragment(); const msg = `You achieved a new Smart Milestone \u{1F389}`; const msg_el = document.createElement("p"); msg_el.textContent = msg; frag.appendChild(msg_el); const milestone_el = document.createElement("p"); milestone_el.textContent = `\u2705 ${EVENTS_CHECKLIST_ITEMS_BY_EVENT_KEY[event_key].milestone}`; milestone_el.style.color = "var(--color-green)"; milestone_el.style.fontStyle = "italic"; frag.appendChild(milestone_el); const btn = document.createElement("button"); btn.textContent = "View milestones"; btn.addEventListener("click", () => { env.open_milestones_modal(); }); frag.appendChild(btn); new import_obsidian13.Notice(frag, 7e3); } }); } var EVENTS_CHECKLIST_ITEMS_BY_EVENT_KEY = { // Environment "sources:import_completed": { group: "Environment", milestone: "Initial vault import completed (all sources discovered)." }, "embedding:completed": { group: "Environment", milestone: "Initial embedding completed, you are ready to make connections!" }, // Connections "connections:opened": { group: "Connections", milestone: "Opened the connections view." }, "connections:drag_result": { group: "Connections", milestone: "Dragged a Smart Connections result into a note to create a link." }, "connections:open_result": { group: "Connections", milestone: "Opened a Smart Connections result from the UI (list item or inline popover)." }, "connections:sent_to_context": { group: "Connections", milestone: "Sent Connections results to Smart Context (turn discovery into a context pack)." }, "connections:copied_list": { group: "Connections", milestone: "Copied Connections results as a list of links." }, "connections:hover_preview": { group: "Connections", milestone: "Previewed a connection by holding cmd/ctrl while hovering the result." }, // Lookup "lookup:hover_preview": { group: "Lookup", milestone: "Previewed a Smart Lookup result by holding cmd/ctrl while hovering." }, "lookup:get_results": { group: "Lookup", milestone: "Submitted a lookup query (started a semantic search)." }, "lookup:drag_result": { group: "Lookup", milestone: "Dragged a Smart Lookup result into a note to create a link." }, "lookup:open_result": { group: "Lookup", milestone: "Opened a Lookup result." }, // Context "context:created": { group: "Context", milestone: "First context created!" }, "context:copied": { group: "Context", milestone: "Copied context to clipboard." }, "context_selector:open": { group: "Context", milestone: "Opened the Context Builder selector modal." }, "context:named": { group: "Context", milestone: "Named a Smart Context (created a reusable saved context)." }, "context:renamed": { group: "Context", milestone: "Renamed a Smart Context (increased clarity)." }, "context:copied_with_media": { group: "Context", milestone: "Copied context with media (images/PDF pages) for multimodal workflows.", is_pro: true }, // Chat "chat_codeblock:saved_thread": { group: "Chat", milestone: "Started a chat in a Smart Chat codeblock (opened the loop)." }, "completion:completed": { group: "Chat", milestone: "Received the first Smart Chat response (a completion finished).", is_pro: true }, "chat_codeblock:marked_done": { group: "Chat", milestone: "Marked the chat thread as done (closed the loop)." }, // Pro "smart_plugins_oauth_completed": { group: "Pro", milestone: "Connected account (enabled Pro plugins)." }, // Inline connections (Pro) "inline_connections:show": { group: "Inline connections", milestone: "Opened inline connections in-note (used the inline workflow).", is_pro: true }, "inline_connections:open_result": { group: "Inline connections", milestone: "Opened an inline connections result (navigated from discovery to source).", is_pro: true }, "inline_connections:drag_result": { group: "Inline connections", milestone: "Inserted an inline link from an inline connection (converted discovery into a durable link).", is_pro: true } }; var EVENTS_CHECKLIST_GROUP_ORDER = [ "Environment", "Connections", "Lookup", "Context", "Chat", "Pro", "Inline connections" ]; function derive_events_checklist_groups(items_by_event_key) { const group_map = Object.entries(items_by_event_key || {}).reduce( (acc, [event_key, item]) => { const group = item?.group || "Other"; if (!acc[group]) acc[group] = []; acc[group].push({ event_key, group, milestone: item?.milestone || "", ...item }); return acc; }, /** @type {Record>} */ {} ); const all_groups = Object.keys(group_map); const order_index = EVENTS_CHECKLIST_GROUP_ORDER.reduce( (acc, name, idx) => { acc[name] = idx; return acc; }, /** @type {Record} */ {} ); const sorted_groups = all_groups.sort((a, b) => { const a_has = Object.prototype.hasOwnProperty.call(order_index, a); const b_has = Object.prototype.hasOwnProperty.call(order_index, b); if (a_has && b_has) return order_index[a] - order_index[b]; if (a_has) return -1; if (b_has) return 1; return a.localeCompare(b); }); return sorted_groups.map((group) => { const items = (group_map[group] || []).slice(); return { group, items }; }); } // node_modules/obsidian-smart-env/src/components/milestones.js function check_if_event_emitted(env, event_key) { return !!env.event_logs.items[event_key]; } function build_html7(env, params = {}) { const groups = derive_events_checklist_groups(EVENTS_CHECKLIST_ITEMS_BY_EVENT_KEY); const checked_count = groups.reduce((acc, g) => { const c = g.items.reduce((inner, item) => { return inner + (check_if_event_emitted(env, item.event_key) ? 1 : 0); }, 0); return acc + c; }, 0); const total_count = groups.reduce((acc, g) => acc + g.items.length, 0); const groups_html = groups.map((group) => { const items_html = group.items.map((item) => { const checked = check_if_event_emitted(env, item.event_key) === true; return build_item_html(item, { checked }); }).join("\n"); return `

${escape_html(group.group)}

    ${items_html}
`; }).join("\n"); return `
${checked_count.toString()} / ${total_count.toString()}
${groups_html}
`; } async function render7(env, params = {}) { this.apply_style_sheet(milestones_default); const html = build_html7.call(this, env, params); const frag = this.create_doc_fragment(html); const container = frag.firstElementChild; post_process6.call(this, env, container, params); return container; } async function post_process6(env, container, params = {}) { return container; } function build_item_html(item, state) { const checked = state.checked === true; const checked_attr = checked ? "checked" : ""; const checked_flag = checked ? "true" : "false"; return `
  • ${escape_html(item.event_key)}
  • `; } // node_modules/obsidian-smart-env/src/components/notifications_feed.js function build_html8() { return `
    `; } async function render8(env, params = {}) { const frag = this.create_doc_fragment(build_html8()); const container = frag.firstElementChild; post_process7.call(this, env, container, params); return frag; } async function post_process7(env, container, params = {}) { const feed_container = container.querySelector(".smart-env-notifications-feed"); this.empty(feed_container); const entries = Array.isArray(env.event_logs.session_events) ? [...env.event_logs.session_events] : []; if (!entries.length) { const empty2 = feed_container.ownerDocument.createElement("p"); empty2.className = "smart-env-notifications-empty"; empty2.textContent = "No Smart Env notifications yet."; feed_container.appendChild(empty2); return; } entries.slice(-100).reverse().forEach((entry) => { append_entry(feed_container, entry); }); const copy_btn = container.querySelector(".copy-all-notifications-btn"); copy_btn.addEventListener("click", () => { const all_text = feed_container.textContent; navigator.clipboard.writeText(all_text).then(() => { copy_btn.textContent = "Copied!"; setTimeout(() => { copy_btn.textContent = "Copy All Notifications"; }, 2e3); }); }); } function get_level(entry) { const [event_domain, event_type] = entry.event_key.split(":"); if (event_domain === "notification") { return event_type; } if (event_type === "error") { return "error"; } return "info"; } function append_entry(feed_container, entry) { const row = feed_container.ownerDocument.createElement("div"); row.className = "smart-env-notification"; row.dataset.level = get_level(entry); feed_container.appendChild(row); const meta = feed_container.ownerDocument.createElement("div"); meta.className = "smart-env-notification__meta"; const timestamp = typeof entry.event.at === "number" ? entry.event.at : Date.now(); meta.textContent = `${entry.event.collection_key ? entry.event.collection_key + " - " : ""}${entry.event_key} - ${to_time_ago(timestamp)} `; row.appendChild(meta); const event_payload_content = Object.entries(entry.event).filter(([k, v]) => !["at", "collection_key"].includes(k)).map(([k, v]) => ` ${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`).join("\n"); if (event_payload_content.trim().length) { row.style.cursor = "pointer"; const message = feed_container.ownerDocument.createElement("pre"); message.className = "smart-env-notification__message"; message.textContent = event_payload_content; message.textContent += "\n\n"; message.style.display = "none"; row.appendChild(message); row.addEventListener("click", () => { if (message.style.display === "none") { message.style.display = "block"; } else { message.style.display = "none"; } }); } else { meta.textContent += "\n"; } } function to_time_ago(ms) { const now_ms = Date.now(); const seconds = Math.floor((now_ms - ms) / 1e3); if (seconds < 60) return `${seconds} seconds ago`; const minutes = Math.floor(seconds / 60); if (minutes < 60) return `${minutes} minutes ago`; const hours = Math.floor(minutes / 60); if (hours < 24) return `${hours} hours ago`; const days = Math.floor(hours / 24); return `${days} days ago`; } // node_modules/obsidian-smart-env/src/components/pro-plugins/list.js var import_obsidian15 = require("obsidian"); // node_modules/obsidian-smart-env/src/utils/smart_plugins.js var import_obsidian14 = require("obsidian"); function get_smart_server_url() { if (typeof window !== "undefined" && window.SMART_SERVER_URL_OVERRIDE) { return window.SMART_SERVER_URL_OVERRIDE; } return "https://connect.smartconnections.app"; } function try_get_zlib() { if (typeof window?.require === "function") { try { return window.require("zlib"); } catch { } } return null; } function inflate_deflate_data(compressed) { const zlib = try_get_zlib(); if (!zlib) { throw new Error("zlib not available (maybe Obsidian mobile?)."); } const buf = Buffer.from(compressed); const out = zlib.inflateRawSync(buf); return new Uint8Array(out.buffer, out.byteOffset, out.length); } async function parse_zip_into_files(zipBuffer) { const dv = new DataView(zipBuffer); let offset = 0; const length = dv.byteLength; const files = []; let pluginManifest = null; while (offset + 4 <= length) { const localSig = dv.getUint32(offset, true); if (localSig === 33639248 || localSig === 134695760) { break; } if (localSig !== 67324752) { break; } offset += 4; const versionNeeded = dv.getUint16(offset, true); const generalPurposeBitFlag = dv.getUint16(offset + 2, true); const compressionMethod = dv.getUint16(offset + 4, true); offset += 6; const lastModTimeDate = dv.getUint32(offset, true); offset += 4; let crc32 = dv.getUint32(offset, true); let compressedSize = dv.getUint32(offset + 4, true); let uncompressedSize = dv.getUint32(offset + 8, true); offset += 12; const fileNameLen = dv.getUint16(offset, true); const extraLen = dv.getUint16(offset + 2, true); offset += 4; const fileNameBytes = new Uint8Array(zipBuffer.slice(offset, offset + fileNameLen)); const fileName = new TextDecoder("utf-8").decode(fileNameBytes); offset += fileNameLen; offset += extraLen; const hasDataDescriptor = (generalPurposeBitFlag & 8) !== 0; let compDataStart = offset; let compDataEnd; if (!hasDataDescriptor) { compDataEnd = compDataStart + compressedSize; } else { let scanPos = compDataStart; let foundSig = false; while (scanPos + 4 <= length) { const sig = dv.getUint32(scanPos, true); if (sig === 134695760 || sig === 67324752 || sig === 33639248) { foundSig = true; break; } scanPos++; } compDataEnd = foundSig ? scanPos : length; } if (compDataEnd > length) { break; } const fileDataCompressed = new Uint8Array(zipBuffer.slice(compDataStart, compDataEnd)); offset = compDataEnd; if (hasDataDescriptor) { if (offset + 4 <= length) { const ddSig = dv.getUint32(offset, true); if (ddSig === 134695760) { offset += 4; } if (offset + 12 <= length) { crc32 = dv.getUint32(offset, true); compressedSize = dv.getUint32(offset + 4, true); uncompressedSize = dv.getUint32(offset + 8, true); offset += 12; } else { break; } } } let rawData; if (compressionMethod === 0) { rawData = fileDataCompressed; } else if (compressionMethod === 8) { rawData = inflate_deflate_data(fileDataCompressed); } else { continue; } files.push({ fileName, data: rawData }); if (fileName.toLowerCase().endsWith("manifest.json") && !fileName.includes("/")) { try { pluginManifest = JSON.parse(new TextDecoder("utf-8").decode(rawData)); } catch { } } } return { files, pluginManifest }; } function validate_zip_buffer(zip_buffer, source_label = "Response") { if (!zip_buffer || zip_buffer.byteLength < 4) { throw new Error(`${source_label} returned too few bytes, not a valid ZIP.`); } const dv = new DataView(zip_buffer); if (dv.getUint32(0, true) !== 67324752) { const txt = new TextDecoder().decode(new Uint8Array(zip_buffer)); throw new Error(`${source_label} did not return a valid ZIP. Text: ${txt}`); } return zip_buffer; } async function write_files_with_adapter(adapter, baseFolder, files) { const hasWriteBinary = typeof adapter.writeBinary === "function"; if (!await adapter.exists(baseFolder)) { await adapter.mkdir(baseFolder); } for (const { fileName, data } of files) { const fullPath = baseFolder + "/" + fileName; if (hasWriteBinary) { await adapter.writeBinary(fullPath, data); } else { const base642 = btoa(String.fromCharCode(...data)); await adapter.write(fullPath, base642); } } } function is_server_version_newer(localVer, serverVer) { if (!serverVer || serverVer === "unknown") return false; const lv = localVer.replace(/[^\d.]/g, ""); const sv = serverVer.replace(/[^\d.]/g, ""); const la = lv.split(".").map(Number); const sa = sv.split(".").map(Number); for (let i = 0; i < Math.max(la.length, sa.length); i++) { const l = la[i] || 0; const s = sa[i] || 0; if (s > l) return true; if (s < l) return false; } return false; } async function fetch_plugin_zip(repoName, token) { const resp = await (0, import_obsidian14.requestUrl)({ url: `${get_smart_server_url()}/plugin_download`, method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` }, body: JSON.stringify({ repo: repoName }) }); if (resp.status !== 200) { throw new Error(`plugin_download error ${resp.status}: ${resp.text}`); } return validate_zip_buffer(resp.arrayBuffer, "Smart Plugins server"); } async function fetch_zip_from_url(download_url, request_fn = import_obsidian14.requestUrl) { console.log(`[smart_plugins] download plugin from URL: ${download_url}`); const resp = await request_fn({ url: download_url, method: "GET", headers: { Accept: "application/zip" } }); if (resp.status && resp.status !== 200) { throw new Error(`Download error ${resp.status}: ${resp.text || ""}`); } return validate_zip_buffer(resp.arrayBuffer, "Download"); } async function fetch_plugin_readme(repo, token, request_fn = import_obsidian14.requestUrl) { const resp = await request_fn({ url: `${get_smart_server_url()}/plugin_readme`, method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` }, body: JSON.stringify({ repo }) }); if (resp.status !== 200) { throw new Error(`plugin_readme error ${resp.status}: ${resp.text}`); } return resp.json.readme; } async function enable_plugin(app, plugin_id) { await app.plugins.enablePlugin(plugin_id); app.plugins.enabledPlugins.add(plugin_id); app.plugins.requestSaveConfig(); app.plugins.loadManifests(); } function get_oauth_storage_prefix(app) { const vault_name = app?.vault?.getName?.() || ""; const safe = vault_name.toLowerCase().replace(/[^a-z0-9]/g, "_"); return `${safe}_smart_plugins_oauth_`; } async function fetch_server_plugin_list(token) { const resp = await (0, import_obsidian14.requestUrl)({ url: `${get_smart_server_url()}/plugin_list`, method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` }, body: JSON.stringify({}) }); if (resp.status !== 200) { throw new Error(`plugin_list error ${resp.status}: ${resp.text}`); } return resp.json; } // node_modules/obsidian-smart-env/src/components/pro-plugins/style.css var style_default = ".get-core-link {\r\n text-wrap: nowrap;\r\n font-size: var(--font-ui-small);\r\n}\r\n.core-installed-text {\r\n text-wrap: nowrap;\r\n font-size: var(--font-ui-small);\r\n color: var(--text-muted);\r\n}\r\n\r\n.pro-plugins-list {\r\n display: flex;\r\n flex-direction: column;\r\n row-gap: var(--size-4-3);\r\n margin-top: var(--size-5);\r\n}\r\n\r\n.pro-plugins-list-item {\r\n display: flex;\r\n align-items: center;\r\n padding: 0.75em 0;\r\n border-top: 1px solid var(--background-modifier-border);\r\n row-gap: var(--size-4-3);\r\n}\r\n\r\n.pro-plugins-container > .setting-group {\r\n .setting-item-name.pro-heading {\r\n font-size: var(--h1-size);\r\n }\r\n &> p {\r\n padding: 0 var(--size-4-4);\r\n }\r\n\r\n}\r\n\r\n.smart-plugins-login .setting-item {\r\n gap: var(--size-4-3);\r\n}\r\n\r\n.smart-plugins-login-manual {\r\n margin-top: var(--size-4-3);\r\n}\r\n\r\n.smart-plugins-login-manual-instructions {\r\n font-size: var(--font-ui-small);\r\n color: var(--text-muted);\r\n margin-bottom: var(--size-2-2);\r\n}\r\n\r\n.smart-plugins-login-manual-controls {\r\n display: flex;\r\n gap: var(--size-2-2);\r\n align-items: center;\r\n}\r\n\r\n.smart-plugins-login-manual-input {\r\n flex: 1;\r\n min-width: 240px;\r\n font-size: var(--font-ui-small);\r\n}\r\n"; // node_modules/obsidian-smart-env/src/components/pro-plugins/list.js var PRO_PLUGINS_DESC = `Core plugins provide essential functionality and a "just works" experience. Pro plugins enable advanced configuration and features for Obsidian AI experts.`; var PRO_PLUGINS_FOOTER = `All Pro plugins include advanced configurations and additional model providers. Pro users get priority support via email. Learn more about Pro plugins.`; function derive_fallback_plugins() { const pro_placeholders = [ { name: "Chat Pro", description: "Configure chat to use Local and Cloud API providers (Ollama, LM Studio, OpenAI, Gemini, Anthropic, Open Router, and more).", core_id: "smart-chatgpt", url: "https://smartconnections.app/smart-chat/" }, { name: "Connections Pro", description: "More opportunities for connections. Graph view for visualizing. Inline and footer views (great for mobile!). Configurable algorithms and additional embedding model providers.", core_id: "smart-connections", url: "https://smartconnections.app/smart-connections/" }, { name: "Context Pro", description: "Advanced tools for context engineering. Utilize Bases, images, and external sources (great for coders!) in your contexts.", core_id: "smart-context", url: "https://smartconnections.app/smart-context/" } ]; return pro_placeholders; } function build_html9(env, params = {}) { return `
    Pro plugins

    ${PRO_PLUGINS_DESC}

    ${PRO_PLUGINS_FOOTER}

    `; } async function render9(env, params = {}) { this.apply_style_sheet(style_default); const html = build_html9.call(this, env, params); const frag = this.create_doc_fragment(html); const container = frag.firstElementChild; await post_process8.call(this, env, container, params); return container; } async function post_process8(env, container, params = {}) { const plugin = env.plugin || null; const app = plugin?.app || window.app; const oauth_storage_prefix = get_oauth_storage_prefix(app); const login_container = container.querySelector(".smart-plugins-login"); const pro_list_el = container.querySelector(".pro-plugins-list"); const placeholders = derive_fallback_plugins(); let login_click_count = 0; let last_login_url = ""; let manual_login_el = null; const render_manual_login_link = (login_url) => { if (!login_container) return; if (!login_url) return; if (!manual_login_el || !manual_login_el.isConnected) { manual_login_el = document.createElement("div"); manual_login_el.classList.add("smart-plugins-login-manual"); login_container.appendChild(manual_login_el); } manual_login_el.innerHTML = ""; const instructions = document.createElement("div"); instructions.classList.add("smart-plugins-login-manual-instructions"); instructions.textContent = "If the login page did not open, copy this link and paste it into your browser to open the login page:"; manual_login_el.appendChild(instructions); const controls = document.createElement("div"); controls.classList.add("smart-plugins-login-manual-controls"); manual_login_el.appendChild(controls); const input = document.createElement("input"); input.classList.add("smart-plugins-login-manual-input"); input.type = "text"; input.value = login_url; input.readOnly = true; input.addEventListener("focus", () => input.select()); controls.appendChild(input); const btn = document.createElement("button"); btn.classList.add("mod-cta"); btn.textContent = "Copy"; btn.addEventListener("click", async () => { const ok = await copy_to_clipboard(login_url); if (ok) { new import_obsidian15.Notice("Copied login link to clipboard."); } else { new import_obsidian15.Notice("Copy failed. Please select and copy the link manually."); } }); controls.appendChild(btn); }; const get_installed_info = async () => { const installed_map = {}; let { manifests } = app.plugins; while (Object.keys(manifests).length === 0) { manifests = app.plugins.manifests; await new Promise((resolve) => setTimeout(resolve, 100)); } for (const plugin_id in manifests) { if (!Object.prototype.hasOwnProperty.call(manifests, plugin_id)) continue; const { name, version } = manifests[plugin_id]; installed_map[plugin_id] = { name, version }; } return installed_map; }; const initiate_oauth_login = async () => { login_click_count++; if (login_click_count >= 2 && last_login_url) { render_manual_login_link(last_login_url); } if (env && typeof env.initiate_smart_plugins_oauth === "function") { last_login_url = initiate_smart_plugins_oauth(); } new import_obsidian15.Notice("Please complete the login in your browser."); }; const render_oauth_login_section = () => { this.empty(login_container); manual_login_el = null; const token = localStorage.getItem(oauth_storage_prefix + "token") || ""; if (!token) { const setting2 = new import_obsidian15.Setting(login_container).setName("Connect account").setDesc("Log in with the key provided in your Pro welcome email."); setting2.addButton((btn) => { btn.setButtonText("Login"); btn.onClick(async () => { await initiate_oauth_login(); }); }); return; } const setting = new import_obsidian15.Setting(login_container); setting.setDesc("Signed in to Smart Plugins Pro account."); setting.addButton((btn) => { btn.setButtonText("Logout"); btn.onClick(() => { localStorage.removeItem(oauth_storage_prefix + "token"); localStorage.removeItem(oauth_storage_prefix + "refresh"); new import_obsidian15.Notice("Logged out of Smart Plugins"); render_oauth_login_section(); render_plugin_list_section(); }); }); }; const render_fallback_plugin_list = async () => { this.empty(pro_list_el); if (!pro_list_el || placeholders.length === 0) return; for (const item of placeholders) { const row = await env.smart_components.render_component("pro_plugins_list_item", item, { env, app, installed_map: {} }); pro_list_el.appendChild(row); } }; const add_update_sub_to_login_section = () => { const setting = new import_obsidian15.Setting(login_container).setName("Subscription Expired").setDesc("Your Smart Connections Pro subscription has expired. Please update your subscription to retain access to Pro plugins."); setting.addButton((btn) => { btn.setButtonText("Get Pro"); btn.onClick(() => { window.open("https://smartconnections.app/subscribe/", "_external"); }); }); setting.addButton((btn) => { btn.setButtonText("Update subscription"); btn.onClick(() => { window.open("https://smartconnections.app/subscription-update/", "_external"); }); }); setting.addButton((btn) => { btn.setButtonText("Refresh"); btn.onClick(() => { env.events.emit("pro_plugins:refresh"); }); }); }; const render_plugin_list_section = async () => { this.empty(pro_list_el); const token = localStorage.getItem(oauth_storage_prefix + "token") || ""; if (!token) { await render_fallback_plugin_list(); return; } try { const installed_map = await get_installed_info(); const resp = await fetch_server_plugin_list(token); const { list = [], unauthorized = [], sub_exp } = resp; if (typeof sub_exp === "number" && sub_exp < Date.now()) { add_update_sub_to_login_section(); await render_fallback_plugin_list(); return; } if (!Array.isArray(list) || list.length === 0) { await render_fallback_plugin_list(); return; } for (const item of list) { const row = await env.smart_components.render_component("pro_plugins_list_item", item, { env, app, token, installed_map, on_installed: render_plugin_list_section }); pro_list_el.appendChild(row); } } catch (err) { console.error("[pro-plugins:list] Failed to fetch plugin list:", err); pro_list_el.textContent = "Error fetching plugin list. Check console."; } }; const render_smart_plugins = async () => { render_oauth_login_section(); await render_plugin_list_section(); }; env.events.on("smart_plugins_oauth_completed", () => { render_smart_plugins(); }); await render_smart_plugins(); return container; } function initiate_smart_plugins_oauth() { console.log("initiate_smart_plugins_oauth"); const state = Math.random().toString(36).slice(2); const redirect_uri = encodeURIComponent("obsidian://smart-plugins/callback"); const url = `${get_smart_server_url()}/oauth?client_id=smart-plugins-op&redirect_uri=${redirect_uri}&state=${state}`; window.open(url, "_external"); return url; } var copy_to_clipboard = async (text) => { if (!text) return false; try { if (navigator?.clipboard?.writeText) { await navigator.clipboard.writeText(text); return true; } } catch { } try { const ta = document.createElement("textarea"); ta.value = text; ta.setAttribute("readonly", "true"); ta.style.position = "fixed"; ta.style.left = "-9999px"; ta.style.top = "0"; document.body.appendChild(ta); ta.select(); const ok = document.execCommand("copy"); document.body.removeChild(ta); return Boolean(ok); } catch { } return false; }; // node_modules/obsidian-smart-env/src/components/pro-plugins/list_item.js var import_obsidian16 = require("obsidian"); var PRO_PLUGINS_URL = "https://smartconnections.app/pro-plugins/"; function build_html10(item, params = {}) { return `
    `; } function is_fallback_item(item) { return !item || !item.repo; } function compute_display_state(item, local_info) { const repo_name = item.repo; const server_version = item.version || "unknown"; const plugin_id = item.manifest_id || repo_name.replace("/", "_"); const local_version = local_info?.version || null; const display_name5 = local_info?.name || item.name || repo_name; let desc = `Server version: ${server_version}`; let button_label = "Install"; let is_disabled = false; if (local_version) { desc += ` | Installed version: ${local_version}`; const is_update = is_server_version_newer(local_version, server_version); if (is_update) { button_label = "Update"; } else { button_label = "Installed"; is_disabled = true; } } if (item.description) { desc += ` ${item.description}`; } return { plugin_id, display_name: display_name5, desc, button_label, is_disabled, server_version, local_version }; } async function render10(item, params = {}) { const html = build_html10(item, params); const frag = this.create_doc_fragment(html); const container = frag.firstElementChild; await post_process9.call(this, item, container, params); return container; } async function post_process9(item, container, params = {}) { const { app, token, installed_map = {}, on_installed } = params; if (is_fallback_item(item)) { const row2 = new import_obsidian16.Setting(container).setName(item.name || "Pro plugin").setDesc(item.description || "Login to unlock Pro plugins."); if (item.core_id) { if (app.plugins.manifests[item.core_id]) { const core_installed_text = document.createElement("i"); core_installed_text.classList.add("core-installed-text"); core_installed_text.textContent = "Core installed!"; row2.controlEl.appendChild(core_installed_text); } else { const get_core_link = document.createElement("a"); get_core_link.setAttribute("href", `obsidian://show-plugin?id=${item.core_id}`); get_core_link.setAttribute("target", "_external"); get_core_link.textContent = "Install Core"; get_core_link.style.marginLeft = "10px"; get_core_link.classList.add("get-core-link"); row2.controlEl.appendChild(get_core_link); } } row2.addButton((btn) => { btn.setButtonText("Get Pro"); btn.onClick(() => { window.open(PRO_PLUGINS_URL, "_external"); }); }); row2.addButton((btn) => { btn.setButtonText("Learn more"); btn.onClick(() => { window.open(item.url, "_external"); }); }); return container; } const plugin_id = item.manifest_id || item.repo.replace("/", "_"); const local = installed_map[plugin_id] || null; const state = compute_display_state(item, local); const row = new import_obsidian16.Setting(container).setName(state.display_name).setDesc(state.desc); row.addButton((btn) => { btn.setButtonText(state.button_label); btn.setDisabled(state.is_disabled); btn.onClick(() => install_plugin(item, { app, token, on_installed })); }); row.addButton((btn) => { btn.setButtonText("Docs"); if (item.docs_url) { btn.onClick(() => window.open(item.docs_url, "_external")); } else { btn.onClick(() => show_plugin_readme(item, { app, token, display_name: state.display_name })); } }); return container; } var download_plugin_zip = async (item, token) => { const resolved_download_url = typeof item.resolve_download_url === "function" ? await item.resolve_download_url() : item.download_url; if (resolved_download_url) { return fetch_zip_from_url(resolved_download_url); } if (!token) { throw new Error("Login required to install this plugin."); } return fetch_plugin_zip(item.repo, token); }; var install_plugin = async (item, params = {}) => { const { app, token, on_installed } = params; try { new import_obsidian16.Notice(`Installing "${item.repo}" ...`); const zip_data = await download_plugin_zip(item, token); const { files, pluginManifest } = await parse_zip_into_files(zip_data); const folder_name = item.plugin_id; const base_folder = `${app.vault.configDir}/plugins/${folder_name}`; await write_files_with_adapter(app.vault.adapter, base_folder, files); await app.plugins.loadManifests(); const plugin_id = pluginManifest?.id || item.manifest_id || folder_name; if (app.plugins.enabledPlugins.has(plugin_id)) { await app.plugins.disablePlugin(plugin_id); } await enable_plugin(app, plugin_id); new import_obsidian16.Notice(`${item.repo} installed successfully.`); if (typeof on_installed === "function") { await on_installed(); } } catch (err) { console.error("[pro-plugins:list_item] Install error:", err); new import_obsidian16.Notice(`Install failed: ${err.message}`); } }; var show_plugin_readme = async (item, params = {}) => { const { app, token, display_name: display_name5 } = params; try { const readme = await fetch_plugin_readme(item.repo, token); const modal = new import_obsidian16.Modal(app); modal.setTitle(display_name5 || item.name || item.repo); await import_obsidian16.MarkdownRenderer.render(app, readme, modal.contentEl, "", new import_obsidian16.Component()); modal.open(); } catch (err) { console.error("[pro-plugins:list_item] Failed to load README:", err); new import_obsidian16.Notice("Failed to load README"); } }; // node_modules/obsidian-smart-env/src/modals/smart_model_modal.js var import_obsidian17 = require("obsidian"); var SmartModelModal = class extends import_obsidian17.Modal { /** * @param {App} app * @param {EditModelModalOpts} opts */ constructor(model, params = {}) { const app = model.env.plugin.app || window.app; super(app); this.model = model; this.collection = this.model.collection; this.env = this.model.env; this.params = params; } onOpen() { this.titleEl.setText("Edit model"); this.contentEl.addClass("smart-model-modal"); this.render_form(); } onClose() { this.contentEl.empty(); if (typeof this.params.on_close === "function") { this.params.on_close(); } } async render_form() { const container = this.contentEl; container.empty(); const model = this.model; const model_actions_bar = await this.env.smart_components.render_component("settings_model_actions", model, { // these callbacks should probably be handled via events instead on_before_new: async () => { this.close(); }, on_after_delete: async () => { this.close(); } }); container.appendChild(model_actions_bar); const settings = model.settings_config; const form = await this.env.smart_view.render_settings(settings, { scope: model }); container.appendChild(form); const test_btn = container.createEl("button", { text: "Test model" }); const test_results_el = container.createDiv({ cls: "model-test-container" }); test_btn.addEventListener("click", async () => { await this.run_test(test_results_el, model); }); if (this.params.test_on_open) { await this.run_test(test_results_el, model); } } async run_test(test_results_el, model) { test_results_el.empty(); const test_result_el = test_results_el.createEl("pre", { cls: "model-test-result", text: "Testing..." }); test_results_el.appendChild(test_result_el); const test_result = await model.test_model(); test_result_el.textContent = JSON.stringify(test_result, null, 2); } }; // node_modules/obsidian-smart-env/src/components/settings/env_model.css var env_model_default = '.model-settings .model-info {\r\n border-radius: var(--radius-m);\r\n padding: 1rem;\r\n margin-bottom: 1rem;\r\n background-color: var(--background-secondary);\r\n pre {\r\n margin: 0;\r\n font-size: 0.9rem;\r\n }\r\n .test-result-icon {\r\n vertical-align: middle;\r\n margin-left: 0.5rem;\r\n }\r\n .test-result-icon[data-icon="square-check-big"]{\r\n color: var(--color-green);\r\n }\r\n .test-result-icon[data-icon="circle-x"]{\r\n color: var(--color-red);\r\n }\r\n}'; // node_modules/obsidian-smart-env/src/components/settings/env_model.js var import_obsidian18 = require("obsidian"); function build_html11(model, params) { const details = [ `Provider: ${model.data.provider_key}`, `Model: ${model.data.model_key || "**MISSING - EDIT & SELECT MODEL**"}` ]; return `
    Current: ${model.display_name}
    ${details.join("\n")}
    `; } async function render11(model, params) { this.apply_style_sheet(env_model_default); const frag = this.create_doc_fragment(build_html11.call(this, model, params)); const container = frag.firstElementChild; post_process10.call(this, model, container, params); return container; } async function post_process10(model, container, params) { const edit_btn = container.querySelector(".edit-model"); const test_btn = container.querySelector(".test-model"); const icon_el = container.querySelector(".test-result-icon"); (0, import_obsidian18.setIcon)(icon_el, get_test_result_icon_name(model)); edit_btn.addEventListener("click", () => { new SmartModelModal(model).open(); }); test_btn.addEventListener("click", () => { new SmartModelModal(model, { test_on_open: true }).open(); }); return container; } function get_test_result_icon_name(model) { switch (model.data.test_passed) { case true: return "square-check-big"; case false: return "circle-x"; default: return "square"; } } // node_modules/obsidian-smart-env/src/utils/smart-models/show_new_model_menu.js var import_obsidian19 = require("obsidian"); // node_modules/obsidian-smart-env/src/utils/smart-models/provider_options.js var provider_options = { chat_completion_models: [ { label: "Open Router (cloud)", value: "open_router" }, { label: "PRO: LM Studio (local, requires LM Studio app)", value: "lm_studio", disabled: true }, { label: "PRO: Ollama (local, requires Ollama app)", value: "ollama", disabled: true }, { label: "PRO: OpenAI (cloud)", value: "openai", disabled: true }, { label: "PRO: Google Gemini (cloud)", value: "google", disabled: true }, { label: "PRO: Cohere (cloud)", value: "cohere", disabled: true }, { label: "PRO: xAI Grok (cloud)", value: "xai", disabled: true }, { label: "PRO: Anthropic Claude (cloud)", value: "anthropic", disabled: true }, { label: "PRO: Deepseek (cloud)", value: "deepseek", disabled: true }, { label: "PRO: Azure OpenAI (cloud)", value: "azure", disabled: true } ], embedding_models: [ { label: "Transformers (easy, local, built-in)", value: "transformers" }, { label: "PRO: LM Studio (local, requires LM Studio app)", value: "lm_studio", disabled: true }, { label: "PRO: Ollama (local, requires Ollama app)", value: "ollama", disabled: true }, { label: "PRO: OpenAI (cloud)", value: "openai", disabled: true }, { label: "PRO: Google Gemini (cloud)", value: "google", disabled: true }, { label: "PRO: Open Router (cloud)", value: "open_router", disabled: true } ], ranking_models: [ { label: "PRO: Cohere (cloud)", value: "cohere", disabled: true } ] }; // node_modules/obsidian-smart-env/src/utils/smart-models/show_new_model_menu.js function show_new_model_menu(models_collection, event, params = {}) { const providers = (provider_options[models_collection.collection_key] || []).map((p) => ({ ...p, disabled: !models_collection.env_config.providers[p.value] })); if (providers.length === 0) { if (event.target.tagName.toLowerCase() === "button") { event.target.disabled = true; event.title = "No providers available to create new models."; } } else { const menu = new import_obsidian19.Menu(); providers.forEach((provider) => { menu.addItem((item) => { item.setTitle(provider.label); if (provider.disabled) { item.setDisabled(true); } item.onClick(async () => { if (typeof params.on_before_new === "function") { await params.on_before_new(); } const model = models_collection.new_model({ provider_key: provider.value }); const on_new_close = async () => { }; new SmartModelModal(model, { on_close: on_new_close }).open(); }); }); }); menu.showAtMouseEvent(event); } } // node_modules/obsidian-smart-env/src/utils/render_settings_config.js var import_obsidian20 = require("obsidian"); var SettingGroupPolyfill = class { constructor(container) { this.components = []; this.groupEl = container.createDiv("setting-group"); this.headerEl = this.groupEl.createDiv("setting-item setting-item-heading"); this.headerInnerEl = this.headerEl.createDiv("setting-item-name"); this.controlEl = this.headerEl.createDiv("setting-item-control"); this.listEl = this.groupEl.createDiv("setting-items"); } setHeading(heading) { this.headerInnerEl.setText(heading); } addSetting(callback) { const setting = new import_obsidian20.Setting(this.listEl); this.components.push(setting); callback(setting); return setting; } addClass(class_name) { this.groupEl.addClass(class_name); } }; function render_settings_config(settings_config12, scope, container, params = {}) { const { default_group_name = "Settings" } = params; settings_config12 = ensure_settings_config(settings_config12, scope); const group_map = Object.entries(settings_config12 || {}).reduce((acc, [key, config]) => { const group = config.group || default_group_name; if (!acc[group]) acc[group] = {}; acc[group][key] = config; return acc; }, { [default_group_name]: {} }); const settings_groups = Object.entries(group_map).sort(([a], [b]) => a === default_group_name ? -1 : b === default_group_name ? 1 : 0).filter(([, group_config]) => Object.keys(group_config).length > 0).map(([group_name, group_config]) => { const group_container = container.createDiv(); const group_params = { ...params, ...params.group_params?.[group_name] || {} }; return render_settings_group( group_name, scope, group_config, group_container, group_params ); }); return settings_groups; } function render_settings_group(group_name, scope, settings_config12, container, params = {}) { let SettingGroup; try { const obsidian_module = require("obsidian"); if (obsidian_module.SettingGroup) { SettingGroup = obsidian_module.SettingGroup; } else { SettingGroup = SettingGroupPolyfill; } } catch (e) { SettingGroup = SettingGroupPolyfill; } settings_config12 = ensure_settings_config(settings_config12, scope); const { heading_btn = null } = params; let setting_group = new SettingGroup(container); if (heading_btn && typeof heading_btn === "object") { if (Array.isArray(heading_btn)) { for (const btn_config of heading_btn) { render_heading_button(setting_group, scope, btn_config); } } else { render_heading_button(setting_group, scope, heading_btn); } } setting_group.setHeading(group_name); for (const [setting_path, setting_config] of Object.entries(settings_config12)) { if (!setting_config || typeof setting_config !== "object") { console.warn(`Invalid setting config for ${setting_path}:`, setting_config); continue; } setting_group.addSetting((setting) => { if (setting_config.name) setting.setName(setting_config.name); setting.setClass(setting_path.replace(/[^a-zA-Z0-9]/g, "-")); if (setting_config.type) setting.setClass(`setting-type-${setting_config.type}`); if (setting_config.description) { setting.setDesc(setting_config.description); } switch (setting_config.type) { case "button": setting.addButton((btn) => { btn.setButtonText(setting_config.name || "Run"); btn.onClick(async (event) => { if (typeof setting_config.callback === "function") { await handle_config_callback(setting, event, setting_config.callback, { scope }); } }); }); break; case "toggle": setting.addToggle((toggle) => { toggle.setValue(get_by_path(scope.settings, setting_path) || false); toggle.onChange((value) => { set_by_path(scope.settings, setting_path, value); if (typeof setting_config.callback === "function") { handle_config_callback(setting, value, setting_config.callback, { scope }); } }); }); break; case "text": setting.addText((text) => { text.setValue(String(get_by_path(scope.settings, setting_path) || "")); text.onChange((value) => { set_by_path(scope.settings, setting_path, value); }); }); break; case "number": setting.addText((text) => { text.setValue(String(get_by_path(scope.settings, setting_path) || "")); text.inputEl.setAttribute("type", "number"); text.onChange((value) => { const num_value = Number(value); if (!isNaN(num_value)) { set_by_path(scope.settings, setting_path, num_value); } if (typeof setting_config.callback === "function") { handle_config_callback(setting, num_value, setting_config.callback, { scope }); } }); }); break; case "dropdown": setting.addDropdown((dropdown) => { const options_callback = setting_config.options_callback; if (typeof options_callback === "function") { const options = options_callback.call(scope, scope); options.forEach((opt) => { const label = opt.label || opt.name || opt.value; dropdown.addOption(opt.value, label); }); } dropdown.setValue(get_by_path(scope.settings, setting_path) || ""); dropdown.onChange((value) => { set_by_path(scope.settings, setting_path, value); if (typeof setting_config.callback === "function") { handle_config_callback(setting, value, setting_config.callback, { scope }); } }); }); break; case "textarea": setting.addTextArea((text) => { text.setValue(String(get_by_path(scope.settings, setting_path) || "")); text.onChange((value) => { set_by_path(scope.settings, setting_path, value); }); }); break; case "slider": setting.addSlider((slider) => { const min = setting_config.min || 0; const max = setting_config.max || 100; const step = setting_config.step || 1; slider.setLimits(min, max, step); slider.setValue(get_by_path(scope.settings, setting_path) || min); slider.setDynamicTooltip(); slider.onChange((value) => { set_by_path(scope.settings, setting_path, value); if (typeof setting_config.callback === "function") { handle_config_callback(setting, value, setting_config.callback, { scope }); } }); }); break; case "heading": setting.setHeading(); break; case "html": if (setting_config.value) { setting.descEl.replaceChildren( document.createRange().createContextualFragment(setting_config.value) ); } break; default: console.warn(`Unsupported setting type for ${setting_path}:`, setting_config.type); break; } if (setting_config.scope_class) { setting.settingEl.addClass(setting_config.scope_class); } }); } return setting_group; } function render_heading_button(setting_group, scope, heading_btn) { const btn_el = setting_group.controlEl.createEl("button", { cls: "" }); if (heading_btn.btn_icon) { (0, import_obsidian20.setIcon)(btn_el, heading_btn.btn_icon); } if (heading_btn.btn_text) { btn_el.setText(heading_btn.btn_text); } if (heading_btn.label) { btn_el.setAttr("aria-label", heading_btn.label); } btn_el.addEventListener("click", async (event) => { if (typeof heading_btn.callback === "function") { await handle_config_callback(null, event, heading_btn.callback, { scope }); } else { console.warn("No callback defined for heading button"); } }); setting_group.controlEl.appendChild(btn_el); } async function handle_config_callback(setting, event_or_value, cb, params = {}) { const { scope = null } = params; if (scope) { return await cb.call(scope, event_or_value, setting); } else { return await cb(event_or_value, setting); } } function ensure_settings_config(settings_config12, scope) { try { if (typeof settings_config12 === "function") { settings_config12 = settings_config12(scope); } } catch (e) { console.error("Error evaluating settings_config function:", e); settings_config12 = { error: { name: "Error", description: `Failed to load settings. ${e.message} (logged to console)` } }; } return settings_config12; } // node_modules/obsidian-smart-env/src/components/settings/env_model_type.js function build_html12(models_collection, params) { return `
    `; } async function render12(models_collection, params) { const frag = this.create_doc_fragment(build_html12.call(this, models_collection, params)); const container = frag.firstElementChild; post_process11.call(this, models_collection, container, params); return container; } async function post_process11(models_collection, container, params) { const disposers = []; const render_current_model_info = async (current_model) => { this.empty(container); const [settings_group] = render_settings_config( models_collection.env_config.settings_config, models_collection, container, { default_group_name: `${models_collection.model_type} models`, heading_btn: { btn_text: "+ New", callback: (event, setting) => { show_new_model_menu(models_collection, event); } } } ); models_collection.env.smart_components.render_component("settings_env_model", current_model, {}).then((model_info_el) => { settings_group.listEl.appendChild(model_info_el); }); }; render_current_model_info(models_collection.default); disposers.push(models_collection.on_event("settings:changed", async (payload) => { const default_setting_path = `${models_collection.collection_key}.default_model_key`; if (payload.path_string === default_setting_path) { await render_current_model_info(models_collection.default); } })); disposers.push(models_collection.on_event("model:changed", async () => { await render_current_model_info(models_collection.default); })); this.attach_disposer(container, disposers); } // node_modules/obsidian-smart-env/src/components/settings/env_models.js function build_html13(env, params) { const models_collections = [ env.embedding_models, env.chat_completion_models, env.ranking_models ].filter(Boolean); const type_containers = models_collections.map((models_collection) => { return `
    `; }).join("\n"); return `
    ${type_containers}
    `; } async function render13(env, params) { const frag = this.create_doc_fragment(build_html13(env, params)); const container = frag.firstElementChild; post_process12.call(this, env, container, params); return container; } async function post_process12(env, container, params) { const collection_containers = container.querySelectorAll("div[data-collection-key]"); for (const collection_container of collection_containers) { const collection_key = collection_container.getAttribute("data-collection-key"); const models_collection = env[collection_key]; env.smart_components.render_component("settings_env_model_type", models_collection).then((model_type_el) => { this.empty(collection_container); collection_container.appendChild(model_type_el); }); } return container; } // node_modules/obsidian-smart-env/src/modals/exclude_folders_fuzzy.js var import_obsidian21 = require("obsidian"); // node_modules/obsidian-smart-env/src/utils/exclusions.js function ensure_smart_sources_settings(env) { if (!env.settings) env.settings = {}; if (!env.settings.smart_sources) env.settings.smart_sources = {}; const smart_sources_settings = env.settings.smart_sources; if (!smart_sources_settings.folder_exclusions) smart_sources_settings.folder_exclusions = ""; if (!smart_sources_settings.file_exclusions) smart_sources_settings.file_exclusions = ""; return smart_sources_settings; } function parse_exclusions_csv(exclusions = "") { return exclusions.split(",").map((value) => value.trim()).filter(Boolean); } function add_exclusion(exclusions, value) { const trimmed = (value ?? "").trim(); if (!trimmed) return exclusions || ""; const current = parse_exclusions_csv(exclusions); if (!current.includes(trimmed)) current.push(trimmed); return current.join(","); } function remove_exclusion(exclusions, value) { const trimmed = (value ?? "").trim(); if (!trimmed) return exclusions?.trim() || ""; const filtered = parse_exclusions_csv(exclusions).filter((entry) => entry !== trimmed); return filtered.join(","); } // node_modules/obsidian-smart-env/src/modals/exclude_folders_fuzzy.js var ExcludedFoldersFuzzy = class extends import_obsidian21.FuzzySuggestModal { /** * @param {App} app - The Obsidian app * @param {Object} env - An environment-like object, must have .settings and .fs.folder_paths */ constructor(app, env) { super(app); this.env = env; this.setPlaceholder("Select a folder to exclude..."); } /** * Open the modal with an optional callback invoked after an item is chosen. * The current exclusion list is rendered at the top of the modal. * @param {Function} [selection_callback] */ open(selection_callback) { this.callback = selection_callback; super.open(); this.render_excluded_list(); } /** * Return candidate folder paths that are not already excluded. * @returns {string[]} */ getItems() { const smart_sources_settings = ensure_smart_sources_settings(this.env); const folder_exclusions2 = parse_exclusions_csv(smart_sources_settings.folder_exclusions); const candidates = (this.env.smart_sources?.fs?.folder_paths || []).filter((path) => !folder_exclusions2.includes(path)); return candidates; } getItemText(item) { return item; } /** * Handle selecting a folder to exclude. * @param {string} item */ onChooseItem(item) { this.prevent_close = true; if (!item) return; const smart_sources_settings = ensure_smart_sources_settings(this.env); smart_sources_settings.folder_exclusions = add_exclusion(smart_sources_settings.folder_exclusions, item); this.render_excluded_list(); this.updateSuggestions(); this.callback?.(); } /** * Render the current list of excluded folders at the top of the modal, * with inline remove buttons. */ render_excluded_list() { if (!this.modalEl) return; const smart_sources_settings = ensure_smart_sources_settings(this.env); const excluded_folders = parse_exclusions_csv(smart_sources_settings.folder_exclusions); let header = this.modalEl.querySelector(".sc-excluded-folders-header"); if (!header) { header = this.modalEl.createEl("div", { cls: "sc-excluded-folders-header" }); this.modalEl.prepend(header); } header.empty(); const title_el = header.createEl("h3"); title_el.setText("Excluded folders"); if (!excluded_folders.length) { const empty_el = header.createEl("p"); empty_el.setText("No folders excluded yet."); return; } const list_el = header.createEl("ul"); excluded_folders.forEach((folder_path) => { const li = list_el.createEl("li", { cls: "excluded-folder-item" }); li.setText(folder_path + " "); const remove_btn = li.createEl("button", { text: "(x)", cls: "remove-excluded-folder-btn" }); remove_btn.addEventListener("click", () => { smart_sources_settings.folder_exclusions = remove_exclusion( smart_sources_settings.folder_exclusions, folder_path ); this.env.update_exclusions?.(); this.render_excluded_list(); this.updateSuggestions(); }); }); } close() { setTimeout(() => { if (!this.prevent_close) super.close(); this.prevent_close = false; }, 10); } }; // node_modules/obsidian-smart-env/src/modals/excluded_sources.js var import_obsidian22 = require("obsidian"); var ExcludedSourcesModal = class extends import_obsidian22.Modal { /** * @param {Object} app - Obsidian app * @param {Object} env - The environment instance */ constructor(app, env) { super(app); this.env = env; } async onOpen() { this.titleEl.setText("Excluded Sources"); this.contentEl.addClass("excluded-sources-modal"); this.render_excluded_list(); } async render_excluded_list() { this.contentEl.empty(); const list_el = this.contentEl.createEl("ul"); const excluded_file_paths = this.env.smart_sources.excluded_file_paths; const too_long_files = this.app.vault.getMarkdownFiles().filter((file) => file.path.length > 200).map((file) => file.path); for (const file_path of excluded_file_paths) { const li = list_el.createEl("li"); li.setText(file_path); } this.contentEl.createEl("hr"); this.contentEl.createEl("h3", { text: "Paths too long to import into Smart Environment" }); const too_long_list_ul = this.contentEl.createEl("ul", { cls: "too-long-exclusions" }); for (const file_path of too_long_files) { const li = too_long_list_ul.createEl("li"); li.setText(file_path); } } }; // node_modules/obsidian-smart-env/src/components/settings/env_sources.js async function build_html14(env, opts = {}) { return `
    `; } async function render14(env, opts = {}) { const html = await build_html14.call(this, env, opts); const frag = this.create_doc_fragment(html); const container = frag.firstElementChild; post_process13.call(this, env, container, opts); return container; } async function post_process13(env, container, opts = {}) { const settings_config12 = { folder_exclusions, view_exclusions, // reset_env_settings_btn, // TODO: manually tested before implementing reset button re_import_sources }; render_settings_config(settings_config12, env, container, { default_group_name: "Sources", heading_btn: { btn_icon: "help-circle", callback: (event, setting) => { window.open("https://smartconnections.app/smart-environment/settings/?utm_source=source-settings", "_external"); } } }); const disposers = []; disposers.push(env.events?.on("model:changed", highlight_reset_data(env, container))); this.attach_disposer(container, disposers); return container; } function highlight_reset_data(env, container) { return async (payload) => { if (payload.collection_key !== "embedding_models") return; const re_import_setting = container.querySelector(".re-import-sources"); re_import_setting.classList.add("env-setting-highlight"); const notice = re_import_setting.querySelector(".reimport-notice") ? re_import_setting.querySelector(".reimport-notice") : re_import_setting.createEl("div", { cls: "reimport-notice env-setting-note" }); notice.textContent = "Embedding model changed. Please re-import your sources to update their embeddings."; re_import_setting.appendChild(notice); env.events.once("sources:reimported", () => { re_import_setting.classList.remove("env-setting-highlight"); notice.remove(); }); }; } var folder_exclusions = { type: "button", name: "Manage excluded folders", description: "Manage the list of folders excluded from processing.", btn_text: "Manage folders", callback: async function(value, setting) { const env = this; const fuzzy = new ExcludedFoldersFuzzy(env.main.app, env); const selection_callback = () => { env.update_exclusions(); }; fuzzy.open(selection_callback); } }; var view_exclusions = { type: "button", name: "View all exclusions", description: "View all excluded sources.", btn_text: "Show", callback: async function(value, setting) { const env = this; const modal = new ExcludedSourcesModal(env.main.app, env); modal.open(); } }; var re_import_sources = { type: "button", name: "Reset data", description: "Clear sources data and re-import.", btn_text: "Re-import sources", callback: async function(value, setting) { const env = this; const container = setting.controlEl; const confirm_row = container.createEl("div", { cls: "sc-inline-confirm-row" }); const reimport_btn = container.querySelector("button"); reimport_btn.style.display = "none"; confirm_row.setText("Are you sure you want to clear all sources data? This cannot be undone."); let confirm_cancel = confirm_row.createEl("button", { text: "Cancel" }); let confirm_yes = confirm_row.createEl("button", { text: "Re-import", cls: "mod-warning" }); confirm_yes.addEventListener("click", async (e) => { confirm_cancel.style.display = "none"; confirm_yes.textContent = "Re-importing..."; confirm_yes.disabled = true; const confirm_row2 = e.target.closest(".sc-inline-confirm-row"); await env.smart_sources.run_clear_all(); const start = Date.now(); env.smart_sources.unload(); env.smart_blocks.unload(); await env.init_collections(); await env.load_collections(); await env.smart_sources.process_embed_queue(); const end = Date.now(); env.events?.emit("sources:reimported", { time_ms: end - start }); env.main.notices?.show("reload_sources", { time_ms: end - start }); confirm_row2.style.display = "none"; reimport_btn.style.display = "inline-block"; confirm_yes.textContent = "Yes"; confirm_yes.disabled = false; }); confirm_cancel.addEventListener("click", (e) => { confirm_row.style.display = "none"; reimport_btn.style.display = "inline-block"; }); } }; // node_modules/obsidian-smart-env/src/components/settings/model_actions.js function build_html15(model, params = {}) { return `
    `; } async function render15(model, params = {}) { const frag = this.create_doc_fragment(build_html15(model, params)); const container = frag.firstElementChild; post_process14.call(this, model, container, params); return container; } async function post_process14(model, container, params = {}) { const new_model_btn = container.querySelector(".new-model-btn"); new_model_btn.addEventListener("click", async (event) => { const on_before_new = params.on_before_new; const opts = {}; if (typeof on_before_new === "function") { opts.on_before_new = on_before_new; } show_new_model_menu(model.collection, event, opts); }); const delete_model_btn = container.querySelector(".delete-model-btn"); const confirm_delete_container = container.querySelector(".confirm-delete-container"); const confirm_delete_yes_btn = container.querySelector(".confirm-delete-yes-btn"); const confirm_delete_no_btn = container.querySelector(".confirm-delete-no-btn"); delete_model_btn.addEventListener("click", async () => { confirm_delete_container.style.display = ""; }); confirm_delete_no_btn.addEventListener("click", async () => { confirm_delete_container.style.display = "none"; }); confirm_delete_yes_btn.addEventListener("click", async () => { await model.delete_model(); if (typeof params.on_after_delete === "function") { params.on_after_delete(); } }); return container; } // node_modules/obsidian-smart-env/src/components/settings/notifications.js async function build_html16(env, opts = {}) { let html = `
    Muted notices
    `; if (Object.keys(env.notices.settings?.muted || {}).length) { for (const notice in env.notices.settings?.muted) { html += `
    ${notice}
    `; } } else { html += `
    No muted notices.
    `; } html += `
    `; return html; } async function render16(env, opts = {}) { let html = await build_html16.call(this, env, opts); const frag = this.create_doc_fragment(html); const container = frag.firstElementChild; post_process15.call(this, env, container, opts); return container; } async function post_process15(env, frag, opts = {}) { const unmute_buttons = frag.querySelectorAll(".unmute-button"); unmute_buttons.forEach((button) => { button.addEventListener("click", () => { const row = button.closest(".muted-notice"); const notice = row.dataset.notice; env.notices.settings.muted[notice] = false; delete env.notices.settings.muted[notice]; row.remove(); }); }); } // node_modules/obsidian-smart-env/src/components/settings/style.css var style_default2 = ".sc-env-settings-container {\r\n margin: 1rem 0;\r\n}\r\n\r\n.smart-env-settings-header {\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n margin-bottom: 0.5rem;\r\n}\r\n\r\n.toggle-env-settings-btn {\r\n cursor: pointer;\r\n}\r\n\r\n\r\n.setting-group .setting-items .setting-item.env-setting-highlight {\r\n border: 1px solid var(--interactive-accent);\r\n background-color: var(--interactive-hover);\r\n padding: 0.5rem;\r\n margin: 0.5rem 0;\r\n}\r\n\r\n.settings-group {\r\n .setting-item {\r\n border-top: none;\r\n }\r\n}"; // node_modules/obsidian-smart-env/src/components/settings/smart_env.js async function build_html17(env, params = {}) { return `

    Sources

    Models

    Notifications

    `; } async function render17(env, params = {}) { this.apply_style_sheet(style_default2); const html = await build_html17.call(this, env, params); const frag = this.create_doc_fragment(html); const container = frag.firstElementChild; post_process16.call(this, env, container, params); return container; } async function post_process16(env, container, opts = {}) { const models_container = container.querySelector(".models-container"); const sources_container = container.querySelector(".sources-container"); const notifications_container = container.querySelector(".notifications-container"); render_if_available.call(this, "settings_env_sources", env, sources_container); render_if_available.call(this, "settings_env_models", env, models_container); render_if_available.call(this, "settings_notifications", env, notifications_container); return container; } function render_if_available(component_key, env, container) { if (env.config.components[component_key]) { const placeholder = this.create_doc_fragment(`
    `).firstElementChild; container.appendChild(placeholder); env.smart_components.render_component(component_key, env).then((comp_el) => { this.empty(placeholder); placeholder.appendChild(comp_el); }); } } // node_modules/obsidian-smart-env/src/components/smart-context/actions.js var import_obsidian23 = require("obsidian"); function build_html18() { return `
    `; } async function render18(ctx, opts = {}) { const html = build_html18(); const frag = this.create_doc_fragment(html); const container = frag.firstElementChild; post_process17.call(this, ctx, container, opts); return container; } async function post_process17(ctx, container, opts = {}) { const render_ctx_actions = () => { const actions_left = container.querySelector(".sc-context-actions-left"); this.empty(actions_left); const actions_right = container.querySelector(".sc-context-actions-right"); this.empty(actions_right); render_btn_open_selector(ctx, actions_right); render_btn_copy_context(ctx, actions_right); render_btn_clear_context(ctx, actions_right); render_btn_help(ctx, actions_right); }; render_ctx_actions(); const disposers = []; disposers.push(ctx.on_event("context:updated", render_ctx_actions)); this.attach_disposer(container, disposers); return container; } function render_btn_open_selector(ctx, container) { const add_btn = document.createElement("button"); add_btn.type = "button"; add_btn.className = "sc-add-context-btn"; add_btn.textContent = "Add context"; container.appendChild(add_btn); add_btn.addEventListener("click", () => { ctx.emit_event("context_selector:open"); }); } function render_btn_copy_context(ctx, container) { const copy_btn = document.createElement("button"); copy_btn.type = "button"; copy_btn.className = "sc-copy-clipboard"; copy_btn.textContent = "Copy to clipboard"; if (!ctx.has_context_items) { copy_btn.style.display = "none"; } container.appendChild(copy_btn); copy_btn.addEventListener("click", async () => { ctx.actions.context_copy_to_clipboard(); }); } function render_btn_clear_context(ctx, container) { const clear_btn = document.createElement("button"); clear_btn.type = "button"; clear_btn.className = "sc-clear-context-btn"; clear_btn.textContent = "Clear"; if (!ctx.has_context_items) { clear_btn.style.display = "none"; } container.appendChild(clear_btn); clear_btn.addEventListener("click", () => { ctx.clear_all(); ctx.emit_event("context:cleared"); }); } function render_btn_help(ctx, container) { const help_btn = document.createElement("button"); help_btn.type = "button"; help_btn.className = "sc-help-btn"; help_btn.setAttribute("aria-label", "Learn more"); container.appendChild(help_btn); (0, import_obsidian23.setIcon)(help_btn, "help-circle"); help_btn.addEventListener("click", () => { window.open("https://smartconnections.app/smart-context/builder/?utm_source=context-selector-modal", "_external"); ctx.emit_event("context_selector:help"); }); } // node_modules/obsidian-smart-env/src/components/smart-context/styles.css var styles_default = "/* Modal view adjustments */\r\n.modal-container .sc-context-view {\r\n max-height: 60%;\r\n display: flex;\r\n flex-direction: column;\r\n .sc-context-view-body {\r\n overflow: auto;\r\n }\r\n .sc-add-context-btn {\r\n display: none;\r\n }\r\n .sc-context-view-header {\r\n padding: var(--size-4-2);\r\n }\r\n .sc-context-actions {\r\n display: flex;\r\n justify-content: space-between;\r\n }\r\n .sc-context-actions-right {\r\n display: flex;\r\n gap: var(--size-4-2);\r\n }\r\n\r\n .sc-context-view-body {\r\n padding: var(--size-4-2);\r\n }\r\n\r\n .sc-context-view-footer {\r\n padding: var(--size-4-2);\r\n }\r\n\r\n}\r\n\r\n/* make hover popover work in builder modal */\r\n.hover-popover {\r\n z-index: 100;\r\n}\r\n"; // node_modules/obsidian-smart-env/utils/copy_to_clipboard.js var import_obsidian24 = require("obsidian"); async function copy_to_clipboard2(text) { try { if (navigator?.clipboard?.writeText) { await navigator.clipboard.writeText(text); } else if (!import_obsidian24.Platform.isMobile) { const { clipboard } = require("electron"); clipboard.writeText(text); } else { new import_obsidian24.Notice("Unable to copy text: no valid method found."); } } catch (err) { console.error("Failed to copy text:", err); new import_obsidian24.Notice("Failed to copy."); } } // node_modules/obsidian-smart-env/src/components/smart-context/item.js function build_html19(ctx, opts = {}) { return `
    `; } async function render19(ctx, opts = {}) { const html = build_html19(ctx, opts); this.apply_style_sheet(styles_default); const frag = this.create_doc_fragment(html); const container = frag.querySelector(".sc-context-view"); post_process18.call(this, ctx, container, opts); return container; } async function post_process18(ctx, container, opts = {}) { const disposers = []; const render_children = async () => { const header = container.querySelector(".sc-context-view-header"); ctx.env.smart_components.render_component("smart_context_actions", ctx, opts).then((actions) => { this.empty(header); header.appendChild(actions); }); const body = container.querySelector(".sc-context-view-body"); ctx.env.smart_components.render_component("smart_context_tree", ctx, opts).then((tree) => { this.empty(body); body.appendChild(tree); }); const footer = container.querySelector(".sc-context-view-footer"); ctx.env.smart_components.render_component("smart_context_meta", ctx, opts).then((meta) => { this.empty(footer); footer.appendChild(meta); }); }; const plugin = ctx.env.plugin; const app = plugin?.app || window.app; const register = plugin?.registerDomEvent?.bind(plugin) || ((el, evt, cb) => el.addEventListener(evt, cb)); register(container, "contextmenu", (ev) => { ev.preventDefault(); ev.stopPropagation(); if (!app) return; const menu = new Menu(app); menu.addItem( (mi) => mi.setTitle("Copy link tree").setIcon("copy").onClick(async () => { const md = tree_dom_to_wikilinks(container); await copy_to_clipboard2(md); }) ); menu.showAtMouseEvent(ev); }); await render_children(); disposers.push(ctx.on_event("context:updated", render_children)); this.attach_disposer(container, disposers); return container; } function tree_dom_to_wikilinks(container) { const lines = []; const walk = (li, depth) => { const path = li.dataset.path; if (!path) return; let rel = path.replace(/^external:/, "").replace(/^selection:/, ""); const label = li.querySelector(":scope > .sc-tree-label")?.textContent?.trim() || ""; if (li.classList.contains("file")) { let file = rel.split("/").pop().replace(/\.md$/, ""); lines.push(`${" ".repeat(depth)}- [[${file}]]`); } else if (li.classList.contains("dir")) { lines.push(`${" ".repeat(depth)}- ${label}`); } li.querySelectorAll(":scope > ul > li").forEach((child) => walk(child, depth + 1)); }; container.querySelectorAll(":scope > ul > li").forEach((li) => walk(li, 0)); return lines.join("\n"); } // node_modules/obsidian-smart-env/src/components/smart-context/meta.js function estimate_tokens(char_count) { return Math.ceil((char_count || 0) / 4); } function build_html20() { return `
    `; } async function render20(ctx, params = {}) { const html = build_html20(); const frag = this.create_doc_fragment(html); const container = frag.firstElementChild; post_process19.call(this, ctx, container, params); return container; } async function post_process19(ctx, container, params = {}) { const render_meta = () => { if (ctx?.has_context_items) { const chars = ctx.size || 0; const tokens = estimate_tokens(chars); container.textContent = `\u2248 ${chars.toLocaleString()} chars \xB7 ${tokens.toLocaleString()} tokens`; } else { container.textContent = "No context items selected"; } }; render_meta(); const disposers = []; disposers.push(ctx.on_event("context:updated", render_meta)); this.attach_disposer(container, disposers); return container; } // node_modules/obsidian-smart-env/src/utils/smart-context/build_tree_item.js function build_tree_item(item, selected_paths, child_html = "") { let { key, path, name, is_file } = item; const has_children = child_html.trim() !== ""; let remove_btn = ""; let connections_btn = ""; let links_btn = ""; if (!key) key = path; if (selected_paths.has(key) || has_children) { remove_btn = `\xD7`; } if (selected_paths.has(key) && !key.startsWith("external:../")) { connections_btn = ``; links_btn = ``; } const label_classes = ["sc-tree-label"]; if (item.exists === false) label_classes.push("missing"); return `
  • ${is_file ? remove_btn : ""} ${name} ${connections_btn} ${links_btn} ${child_html}
  • `; } // node_modules/obsidian-smart-env/src/utils/smart-context/build_tree_html.js function build_tree_html(items) { const tree_root = build_path_tree(items); const selected_set = new Set(items.map((it) => it.key || it.path)); const tree_list_html = tree_to_html(tree_root, selected_set); return tree_list_html; } function build_path_tree(selected_items = []) { const split_path_segments = (item_path) => { const BLOCK_ID_RE = /#\{\d+\}$/u; let remainder = item_path; let block_id_seg = null; let block_key_seg = null; let has_block = false; const id_match = remainder.match(BLOCK_ID_RE); if (id_match) { block_id_seg = id_match[0]; remainder = remainder.slice(0, -block_id_seg.length); has_block = true; } const key_idx = remainder.indexOf("##"); if (key_idx !== -1) { block_key_seg = remainder.slice(key_idx); remainder = remainder.slice(0, key_idx); has_block = true; } const segments = []; if (remainder) { let seg = ""; let in_wikilink = false; for (let i = 0; i < remainder.length; i++) { if (!in_wikilink && remainder.slice(i, i + 2) === "[[") { in_wikilink = true; seg += "[["; i++; } else if (in_wikilink && remainder.slice(i, i + 2) === "]]") { in_wikilink = false; seg += "]]"; i++; } else if (!in_wikilink && remainder[i] === "/") { segments.push(seg); seg = ""; } else { seg += remainder[i]; } } if (seg) segments.push(seg); } if (block_key_seg) segments.push(block_key_seg); if (block_id_seg) segments.push(block_id_seg); return { segments, has_block }; }; const root = { name: "", children: {}, selected: false }; const is_redundant = (p, selected_folders2) => selected_folders2.some((folder) => p.startsWith(`${folder}/`)); const selected_folders = selected_items.filter((it) => { const for_ext_check = it.key.includes("#") ? it.key.split("#")[0] : it.key; return !for_ext_check.match(/\.[a-zA-Z0-9]+$/u); }).map((it) => it.key); for (const { key, exists } of selected_items) { if (is_redundant(key, selected_folders.filter((p) => p !== key))) continue; const { segments, has_block } = split_path_segments(key); let node = root; let running = ""; segments.forEach((seg, idx) => { running = running ? `${running}/${seg}` : seg; if (seg.startsWith("external:..")) return; const is_last = idx === segments.length - 1; const is_block_leaf = is_last && has_block; if (!node.children[seg]) { node.children[seg] = { name: seg, path: is_block_leaf ? key : running, // For blocks we store an empty *array* so AVA can assert `children.length === 0` children: is_block_leaf ? [] : {}, selected: false, is_file: is_block_leaf || is_last && seg.includes(".") }; } node = node.children[seg]; if (is_last) { node.selected = true; node.exists = exists; } }); } return root; } function tree_to_html(node, selected_paths) { if (!node.children || !Object.keys(node.children).length) return ""; const child_html = Object.values(node.children).sort((a, b) => { if (a.is_file !== b.is_file) return a.is_file ? 1 : -1; return a.name.localeCompare(b.name); }).map((child) => build_tree_item(child, selected_paths, tree_to_html(child, selected_paths))).join(""); return `
      ${child_html}
    `; } // node_modules/obsidian-smart-env/src/components/smart-context/tree.css var tree_default = ".sc-context-tree {\r\n ul {\r\n padding-inline-start: 1.7rem;\r\n }\r\n li:has(> .sc-context-item-leaf > .sc-context-item-remove) {\r\n list-style-type: none;\r\n }\r\n .sc-context-item-remove:hover {\r\n font-weight: bold;\r\n filter: brightness(1.8);\r\n }\r\n .sc-context-item-remove {\r\n padding: 0 0.2rem;\r\n margin-left: -1.4rem;\r\n }\r\n}\r\n.sc-context-item-leaf {\r\n cursor: pointer;\r\n}\r\n.sc-context-item-score,\r\n.sc-context-item-size {\r\n display: inline-block;\r\n min-width: 4.5ch;\r\n height: 1.7em;\r\n line-height: 1.7em;\r\n text-align: center;\r\n font-weight: 600 !important;\r\n font-size: 0.8em !important;\r\n color: var(--nav-item-color) !important;\r\n background: var(--background-modifier-hover);\r\n border-radius: 6px;\r\n padding: 0 0.4em;\r\n margin-right: 0.35em;\r\n}\r\n.sc-context-item-size {\r\n min-width: 0;\r\n}"; // node_modules/obsidian-smart-env/src/components/smart-context/tree.js function build_html21(ctx, params = {}) { return `
    `; } async function render21(ctx, params = {}) { this.apply_style_sheet(tree_default); const html = build_html21(ctx, params); const frag = this.create_doc_fragment(html); const container = frag.firstElementChild; post_process20.call(this, ctx, container, params); return container; } async function post_process20(ctx, container, params = {}) { const render_tree_leaves = () => { const env = ctx.env; const items = ctx.context_items.filter(params.filter); const list_html = build_tree_html(items); const list_frag = this.create_doc_fragment(list_html); this.empty(container); container.appendChild(list_frag); for (let i = 0; i < items.length; i++) { const item = items[i]; const li = container.querySelector(`.sc-tree-item[data-path="${item.key}"]`); if (!li) { console.warn(`Smart Context: Could not find tree item for path: ${item.key}`); continue; } env.smart_components.render_component("context_item_leaf", item).then((leaf) => { this.empty(li); li.appendChild(leaf); }); } }; render_tree_leaves(); const disposers = []; disposers.push(ctx.on_event("context:updated", render_tree_leaves)); this.attach_disposer(container, disposers); return container; } // node_modules/obsidian-smart-env/src/components/source_inspector.css var source_inspector_default = ".source-inspector {\r\n background-color: var(--background-secondary-alt);\r\n margin: var(--size-4-3) 0;\r\n padding: var(--size-4-3);\r\n border-radius: var(--radius-m);\r\n}\r\n\r\n.source-inspector-blocks-container {\r\n margin-top: var(--size-4-2);\r\n display: flex;\r\n flex-direction: column;\r\n gap: var(--size-4-3);\r\n}\r\n\r\n.source-inspector-blocks-container blockquote {\r\n margin-left: var(--size-4-3);\r\n padding-left: var(--size-4-3);\r\n border-left: 2px solid var(--text-faint);\r\n}\r\n"; // node_modules/obsidian-smart-env/src/components/source_inspector.js function build_html22(source, opts = {}) { return `

    Blocks

    `; } async function render22(source, opts = {}) { const html = build_html22(source, opts); const frag = this.create_doc_fragment(html); this.apply_style_sheet(source_inspector_default); await post_process21.call(this, source, frag, opts); return frag; } async function post_process21(source, frag, opts = {}) { const container = frag.querySelector(".source-inspector .source-inspector-blocks-container"); if (!container) return frag; const source_info = frag.querySelector(".source-inspector-source-info"); const btn = frag.querySelector(".source-inspector-show-data-btn"); const data_div = frag.querySelector(".source-inspector-source-data"); const pre = data_div?.querySelector("pre"); if (btn && data_div && pre) { btn.addEventListener("click", () => { if (data_div.style.display === "none") { pre.textContent = JSON.stringify(source.data, null, 2); data_div.style.display = ""; btn.textContent = "Hide source data"; } else { data_div.style.display = "none"; btn.textContent = "Show source data"; } }); } const source_should_embed = source.should_embed ? `should embed` : `embedding skipped`; const source_embed_status = source.vec ? `vectorized` : `not vectorized`; const source_info_frag = this.create_doc_fragment(`

    ${source_should_embed} | ${source_embed_status}

    `); source_info.appendChild(source_info_frag); if (!source || !source.blocks || source.blocks.length === 0) { this.safe_inner_html(container, `

    No blocks

    `); return frag; } const sorted_blocks = source.blocks.sort((a, b) => a.line_start - b.line_start); for (const block of sorted_blocks) { const sub_key_display = block.sub_key.split("#").join(" > "); const block_info = `${sub_key_display} (${block.size} chars; lines: ${block.line_start}-${block.line_end})`; const should_embed = block.should_embed ? `should embed` : `embedding skipped`; const embed_status = block.vec ? `vectorized` : `not vectorized`; let block_content = ""; let embed_input = ""; try { const raw = await block.read(); block_content = raw.replace(//g, ">").replace(/\n/g, "
    ").replace(/\t/g, "  "); const embed_raw = await block.get_embed_input(raw); embed_input = embed_raw.replace(//g, ">"); } catch (err) { console.error("[source_inspector] Error reading block:", err); block_content = `Error reading block content`; } const block_frag = this.create_doc_fragment(`

    ${block_info}
    ${should_embed} | ${embed_status}

    Embed input
    ${embed_input}
    ${block_content}

    `); container.appendChild(block_frag); } return frag; } // node_modules/obsidian-smart-env/src/components/status_bar.js var import_obsidian28 = require("obsidian"); // node_modules/obsidian-smart-env/src/utils/register_status_bar_context_menu.js var import_obsidian27 = require("obsidian"); // node_modules/obsidian-smart-env/views/source_inspector.js var import_obsidian25 = require("obsidian"); var SmartNoteInspectModal = class extends import_obsidian25.Modal { constructor(smart_connections_plugin, entity) { super(smart_connections_plugin.app); this.smart_connections_plugin = smart_connections_plugin; this.entity = entity; } get env() { return this.smart_connections_plugin.env; } onOpen() { this.titleEl.innerText = this.entity.key; this.render(); } async render() { this.contentEl.empty(); const frag = await this.env.render_component("source_inspector", this.entity); this.contentEl.appendChild(frag); } }; // node_modules/obsidian-smart-env/src/modals/env_stats.js var import_obsidian26 = require("obsidian"); var EnvStatsModal = class extends import_obsidian26.Modal { constructor(app, env) { super(app); this.env = env; } onOpen() { this.titleEl.setText("Smart Environment"); this.contentEl.empty(); this.contentEl.createEl("p", { text: "Loading stats..." }); setTimeout(this.render.bind(this), 100); } async render() { const frag = await this.env.render_component("env_stats", this.env); this.contentEl.empty(); if (frag) { this.contentEl.appendChild(frag); } else { this.contentEl.createEl("p", { text: "Failed to load stats." }); } } }; // node_modules/obsidian-smart-env/src/utils/register_status_bar_context_menu.js function register_status_bar_context_menu(env, status_container, deps = {}) { const { Menu: MenuClass = import_obsidian27.Menu } = deps; const plugin = env.main; const on_context_menu = (ev) => { ev.preventDefault(); ev.stopPropagation(); const menu = new MenuClass(plugin.app); menu.addItem( (item) => item.setTitle("Inspect active note").setIcon("search").onClick(async () => { const active_file = plugin.app.workspace.getActiveFile(); if (!active_file) { new import_obsidian27.Notice("No active note found"); return; } const src = env.smart_sources?.get(active_file.path); if (!src) { new import_obsidian27.Notice("Active note is not indexed by Smart Environment"); return; } new SmartNoteInspectModal(plugin, src).open(); }) ); menu.addItem( (item) => item.setTitle("Show stats").setIcon("chart-pie").onClick(() => { const modal = new EnvStatsModal(plugin.app, env); modal.open(); }) ); menu.addItem( (item) => item.setTitle("Export data").setIcon("download").onClick(() => { env.export_json(); new import_obsidian27.Notice("Smart Env exported"); }) ); menu.addItem( (item) => item.setTitle("Milestones").setIcon("flag").onClick(() => { env.open_milestones_modal(); }) ); menu.addItem( (item) => item.setTitle("Notifications").setIcon("bell").onClick(() => { env.open_notifications_feed_modal(); }) ); menu.addSeparator(); menu.addItem( (item) => item.setTitle("Learn about Community Supporters").setIcon("hand-heart").onClick(() => { const url = "https://smartconnections.app/community-supporters/?utm_source=status-bar"; window.open(url, "_external"); }) ); menu.showAtPosition({ x: ev.pageX, y: ev.pageY }); }; plugin.registerDomEvent(status_container, "contextmenu", on_context_menu); return on_context_menu; } // node_modules/obsidian-smart-env/src/components/status_bar.css var status_bar_default = ".status-bar-item:has(.smart-env-status-container) {\n padding: 0 0.5em;\n\n &:hover {\n background-color: var(--background-modifier-hover);\n }\n &> .smart-env-status-container {\n display: flex;\n align-items: center;\n gap: 0.5em;\n text-decoration: none;\n color: var(--status-bar-text-color);\n }\n}\n\n.smart-env-status-indicator {\n width: 0.6em;\n height: 0.6em;\n border-radius: 999px;\n background-color: var(--interactive-accent);\n opacity: 0;\n transform: scale(0.3);\n transition: opacity 150ms ease, transform 150ms ease;\n}\n.smart-env-status-indicator[data-level='info'] {\n background-color: var(--interactive-accent);\n}\n.smart-env-status-indicator[data-level='attention'] {\n background-color: var(--color-yellow);\n}\n.smart-env-status-indicator[data-level='warning'] {\n background-color: var(--color-orange);\n}\n.smart-env-status-indicator[data-level='error'] {\n background-color: var(--color-red);\n}\n\n.smart-env-status-indicator[data-count] {\n opacity: 1;\n transform: scale(1);\n}\n\n.smart-env-notifications-feed {\n display: flex;\n flex-direction: column;\n padding: 0.5rem 0;\n gap: 0.42rem;\n}\n\n.smart-env-notifications-empty {\n margin: 0;\n color: var(--text-muted);\n}\n\n.smart-env-notification {\n font-size: var(--font-smaller);\n display: flex;\n flex-direction: column;\n border-left: 3px solid var(--interactive-accent);\n padding-left: 0.75rem;\n}\n\n.smart-env-notification[data-level='attention'] {\n border-color: var(--color-yellow);\n}\n\n.smart-env-notification[data-level='warning'] {\n border-color: var(--color-orange);\n}\n\n.smart-env-notification[data-level='error'] {\n border-color: var(--color-red);\n}\n\n.smart-env-notification__message {\n margin: 0;\n font-weight: 500;\n white-space: pre-wrap;\n}\n\n.smart-env-notification__meta {\n color: var(--text-muted);\n padding: 0.37rem 0;\n}\n\n.status-bar-mobile {\n position: var(--status-bar-position);\n bottom: 0;\n border-radius: 0 8px 0 0;\n border-style: solid;\n border-width: 1px;\n border-color: var(--status-bar-border-color);\n background-color: var(--status-bar-background);\n color: var(--status-bar-text-color);\n font-size: var(--status-bar-font-size);\n min-height: 18px;\n padding: var(--size-4-1);\n user-select: none;\n z-index: var(--layer-status-bar);\n font-variant-numeric: tabular-nums;\n &> .smart-env-status-container {\n padding: 5px 5px 5px 0;\n }\n}\n\n/* footer view on mobile */\n.embedded-backlinks > .status-bar-mobile {\n position: relative;\n border-style: none;\n}"; // node_modules/obsidian-smart-env/src/components/status_bar.js function build_html23() { return ` `; } async function render23(env, opts = {}) { this.apply_style_sheet(status_bar_default); const frag = this.create_doc_fragment(build_html23()); const anchor = frag.firstElementChild; post_process22.call(this, env, anchor, opts); return anchor; } function post_process22(env, container, opts = {}) { const icon_slot = container?.querySelector?.(".smart-env-status-icon"); const status_indicator = container?.querySelector?.(".smart-env-status-indicator"); const status_msg = container?.querySelector?.(".smart-env-status-msg"); const version = env.is_pro ? "Pro" : env.constructor?.version; const get_session_event_count = () => { return env.event_logs?.session_events?.length || 0; }; const get_embed_queue = () => { return Object.keys(env.smart_sources.sources_re_import_queue || {}).length; }; const render_status_elm = () => { const embed_queue = get_embed_queue(); let message = `Smart Env${version ? " " + version : ""}`; let title = "Smart Environment status"; let indicator_count = get_session_event_count(); let indicator_level = env.event_logs?.notification_status || "info"; if (embed_queue > 0) { message = `Embed now (${embed_queue})`; title = "Click to re-import."; indicator_level = "attention"; } if (icon_slot) { (0, import_obsidian28.setIcon)(icon_slot, "smart-connections"); } if (status_indicator) { if (!status_indicator._click_handler) { status_indicator._click_handler = (event) => { event.stopPropagation(); env.open_notifications_feed_modal?.(); }; status_indicator.addEventListener("click", status_indicator._click_handler); } if (indicator_count > 0) { status_indicator.dataset.count = String(indicator_count); } else { delete status_indicator.dataset.count; } if (indicator_level) { status_indicator.dataset.level = String(indicator_level); } else { delete status_indicator.dataset.level; } } status_msg.setText?.(message); container.setAttribute?.("title", title); container.removeAttribute?.("href"); container.removeAttribute?.("target"); if (!container._click_handler) { container._click_handler = (event) => { const curr_embed_queue = get_embed_queue(); if (curr_embed_queue > 0) { event.preventDefault(); event.stopPropagation(); status_msg?.setText?.("Embedding..."); env.run_re_import?.(); } else { const context_event = new MouseEvent("contextmenu", event); container.dispatchEvent?.(context_event); } }; container.addEventListener("click", container._click_handler); } }; register_status_bar_context_menu(env, container); render_status_elm(); let debounce_timeout = null; const debounce_refresh_status_bar = () => { if (debounce_timeout) clearTimeout(debounce_timeout); debounce_timeout = setTimeout(() => { render_status_elm(); debounce_timeout = null; }, 100); }; const disposers = []; disposers.push(env.events.on("*", debounce_refresh_status_bar)); this.attach_disposer(container, disposers); } // node_modules/obsidian-smart-env/src/components/supporter_callout.js var import_obsidian29 = require("obsidian"); function build_html24(plugin, opts = {}) { const { plugin_name = plugin.manifest.name } = opts; return `
    `; } function render24(plugin, opts = {}) { const html = build_html24.call(this, plugin, opts); const frag = this.create_doc_fragment(html); const container = frag.querySelector(".wrapper"); post_process23.call(this, plugin, container, opts); return container; } async function post_process23(plugin, container) { const icon_container = container.querySelector(".callout-icon"); const icon = (0, import_obsidian29.getIcon)("hand-heart"); if (icon) { this.empty(icon_container); icon_container.appendChild(icon); } const oauth_storage_prefix = plugin.app.vault.getName().toLowerCase().replace(/[^a-z0-9]/g, "_") + "_smart_plugins_oauth_"; const is_logged_in = !!localStorage.getItem(oauth_storage_prefix + "token"); if (is_logged_in) container.querySelector("#footer-callout").style.display = "none"; await this.render_setting_components(container, { scope: plugin.env }); } // node_modules/obsidian-smart-env/src/components/user_agreement_callout.js var import_obsidian30 = require("obsidian"); function build_html25(plugin, opts = {}) { const { plugin_name = plugin.manifest.name } = opts; return `
    `; } function render25(plugin, opts = {}) { const html = build_html25.call(this, plugin, opts); const frag = this.create_doc_fragment(html); const callout = frag.querySelector("#footer-callout"); const icon_container = callout.querySelector(".callout-icon"); const icon = (0, import_obsidian30.getIcon)("smart-connections"); if (icon) { this.empty(icon_container); icon_container.appendChild(icon); } post_process24.call(this, plugin, callout, opts); return callout; } function post_process24(plugin, callout) { } // node_modules/obsidian-smart-env/src/actions/context/copy_to_clipboard.js var import_obsidian31 = require("obsidian"); async function copy_to_clipboard3(params = {}) { const context_items = this.context_items.filter(params.filter); if (!context_items.length) { this.emit_event("notification:warning", { message: "No context items to copy." }); return new import_obsidian31.Notice("No context items to copy."); } const content = await this.get_text(params); await copy_to_clipboard2(content); const message = format_stats_message({ item_count: context_items.length, char_count: content.length, max_depth: params.max_depth, exclusions: params.exclusions }); this.emit_event("context:copied"); new import_obsidian31.Notice(message); } function format_stats_message(stats = {}) { const item_count = Number.isFinite(stats.item_count) ? stats.item_count : 0; const char_count = Number.isFinite(stats.char_count) ? stats.char_count : 0; const segments = []; segments.push(`${item_count} file(s)`); segments.push(`${format_char_count(char_count)} chars`); if (Number.isFinite(stats.max_depth)) { segments.push(`depth\u2264${stats.max_depth}`); } const excluded_total = sum_exclusions(stats.exclusions); if (excluded_total > 0) { segments.push(`${excluded_total} section(s) excluded`); } return `Copied to clipboard! (${segments.join(", ")})`; } function format_char_count(char_count) { if (!Number.isFinite(char_count)) return "0"; if (char_count >= 1e5) { return `~${Math.round(char_count / 1e3)}k`; } return char_count.toLocaleString(); } function sum_exclusions(exclusions) { if (!exclusions) return 0; return Object.values(exclusions).reduce((total, value) => { const numeric = Number.isFinite(value) ? value : 0; return total + numeric; }, 0); } // node_modules/obsidian-smart-env/src/actions/context-item/merge_template.js async function merge_template(context_items_text, context_items) { const MERGE_VARS = { "KEY": this.key, "TIME_AGO": convert_to_time_ago(this.mtime) || "Missing", "LINK_DEPTH": this.data.d || 0 }; const replace_vars = async (template) => { const re_var = /{{([\w_]+)}}/g; const number_of_var_matches = (template.match(re_var) || []).length; for (let i = 0; i < number_of_var_matches; i++) { template = template.replace(/{{(\w+)}}/g, (match, p1) => { return MERGE_VARS[p1] || ""; }); } return template; }; const before = await replace_vars(this.settings.template_before || default_settings2.template_before); const after = await replace_vars(this.settings.template_after || default_settings2.template_after); return ["", before, context_items_text, after, ""].join("\n"); } var settings_config7 = { item_explanation: { type: "html", group: "Item templates", value: ` Available variables:
    • {{KEY}} - Full path of the item
    • {{TIME_AGO}} - Time since the item was last modified
    • {{LINK_DEPTH}} - Depth level of the item
    ` }, template_before: { group: "Item templates", type: "textarea", name: "Template Before", description: "Template to wrap before the context item content." }, template_after: { group: "Item templates", type: "textarea", name: "Template After", description: "Template to wrap after the context item content." } }; var default_settings2 = { template_before: '', template_after: "" }; // node_modules/obsidian-smart-env/node_modules/smart-utils/file_tree.js function build_file_tree_string(paths = []) { if (!Array.isArray(paths) || paths.length === 0) return ""; const root = {}; for (const path of paths) { const isFolder = is_folder_path(path); const parts = path.split("/").filter(Boolean); let node = root; for (let i = 0; i < parts.length; i++) { const part = parts[i]; const isLast = i === parts.length - 1; if (isLast) { if (isFolder) { node[part] = node[part] ?? { __isExplicitFolder: true }; } else { node[part] = null; } } else { node = node[part] ??= {}; } } } compress_single_child_dirs(root); return build_tree_string(root).trimEnd(); } function is_folder_path(path) { return typeof path === "string" && path.endsWith("/"); } function compress_single_child_dirs(node) { if (!node || typeof node !== "object") return; for (const key of Object.keys(node)) { const child = node[key]; if (child && typeof child === "object") { if (child.__isExplicitFolder) { delete child.__isExplicitFolder; compress_single_child_dirs(child); continue; } const childKeys = Object.keys(child); if (childKeys.length === 1 && child[childKeys[0]] !== null && !child[childKeys[0]].__isExplicitFolder) { const mergedKey = `${key}/${childKeys[0]}`; node[mergedKey] = child[childKeys[0]]; delete node[key]; compress_single_child_dirs(node[mergedKey]); } else { compress_single_child_dirs(child); } } } } function build_tree_string(node, prefix = "") { let output = ""; const entries = Object.entries(node).sort((a, b) => { const aIsDir = a[1] !== null; const bIsDir = b[1] !== null; if (aIsDir && !bIsDir) return -1; if (!aIsDir && bIsDir) return 1; return a[0].localeCompare(b[0]); }); entries.forEach(([name, child], idx) => { const isLast = idx === entries.length - 1; const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 "; if (child === null) { output += `${prefix}${connector}${name} `; } else { output += `${prefix}${connector}${name}/ `; output += build_tree_string(child, prefix + (isLast ? " " : "\u2502 ")); } }); return output; } // node_modules/obsidian-smart-env/src/actions/context/merge_template.js async function merge_template2(context_items_text, context_items) { const MERGE_VARS = { "FILE_TREE": () => { return build_file_tree_string(context_items.map((c) => c.key)); } }; const replace_vars = async (template) => { const number_of_var_matches = (template.match(/{{(\w+)}}/g) || []).length; for (let i = 0; i < number_of_var_matches; i++) { template = template.replace(/{{(\w+)}}/gi, (match, p1) => { return MERGE_VARS[p1]?.() || ""; }); } return template; }; const before = await replace_vars(this.settings.template_before || default_settings3.template_before); const after = await replace_vars(this.settings.template_after || default_settings3.template_after); return [before, context_items_text, after].join("\n"); } var settings_config8 = { context_explanation: { type: "html", group: "Context templates", value: `Available variables:
    • {{FILE_TREE}} - Shows hierarchical view of all files
    ` }, template_before: { type: "textarea", group: "Context templates", name: "Template Before", description: "Template to wrap before the context." }, template_after: { type: "textarea", group: "Context templates", name: "Template After", description: "Template to wrap after the context." } }; var default_settings3 = { template_before: "\n{{FILE_TREE}}", template_after: "" }; // node_modules/obsidian-smart-env/src/actions/context-suggest/blocks.js function context_suggest_blocks(params = {}) { let blocks = []; if (params.source_key) { const src = this.env.smart_sources.get(params.source_key); blocks = src.blocks; } else { blocks = Object.values(this.env.smart_blocks.items); } return blocks.sort((a, b) => { const a_line = Array.isArray(a.lines) && a.lines.length ? a.lines[0] : Infinity; const b_line = Array.isArray(b.lines) && b.lines.length ? b.lines[0] : Infinity; return a_line - b_line; }).map((block) => ({ key: block.key, display: get_block_display_name2(block, { show_full_path: false }), select_action: () => { this.add_item(block.key); } })); } function get_block_display_name2(item, settings = {}) { if (!item?.key) return ""; const show_full_path = settings.show_full_path ?? true; if (show_full_path) { return item.key.replace(/#/g, " > ").replace(/\//g, " > "); } const pcs = []; const [source_key, ...block_parts] = item.key.split("#"); const filename = source_key.split("/").pop(); pcs.push(filename); if (block_parts.length) { const last = block_parts[block_parts.length - 1]; if (last.startsWith("{") && last.endsWith("}")) { block_parts.pop(); pcs.push(block_parts.pop()); if (item.lines) pcs.push(`Lines: ${item.lines.join("-")}`); } else { pcs.push(block_parts.pop()); } } return pcs.filter(Boolean).join(" > "); } var display_name = "Add blocks"; // node_modules/obsidian-smart-env/src/actions/context-suggest/sources.js function context_suggest_sources(params = {}) { const unselected = Object.values(this.env.smart_sources.items).map((src) => ({ key: src.key, // DEPRECATED??? display: src.key, select_action: () => { this.add_item(src.key); }, mod_select_action: ({ modal }) => { modal.last_input_value = modal.inputEl.value; modal.inputEl.value = ""; return context_suggest_blocks.call(this, { source_key: src.key }); }, arrow_right_action: ({ modal }) => { modal.last_input_value = modal.inputEl.value; modal.inputEl.value = ""; return context_suggest_blocks.call(this, { source_key: src.key }); } })); return unselected; } var display_name2 = "Add sources"; // node_modules/obsidian-smart-env/src/actions/lookup-list/pre_process.js async function pre_process(params) { const query = params.query; if (!query || typeof query !== "string" || query.trim().length === 0) { throw new Error("Invalid or empty query provided to lookup list."); } const embed_model = this.env.smart_sources.embed_model; if (!embed_model) { throw new Error("No embed model available in environment for lookup list."); } const embedding = await embed_model.embed(query); params.to_item = { ...embedding }; if (!params.score_algo_key) params.score_algo_key = "similarity"; return params; } // node_modules/obsidian-smart-env/src/actions/similarity.js function similarity(params) { if (!this.vec) return { score: null, error: `Missing this.vec for ${this.key}` }; if (!params.to_item?.vec) return { score: null, error: "Missing params.to_item.vec" }; return { score: cos_sim(this.vec || [], params.to_item.vec || []) }; } similarity.action_type = "score"; var display_name3 = "Cosine Similarity"; var display_description = "Ranks by cosine similarity between the current note and candidates."; var settings_config9 = { similarity_algo_description: { group: "Score algorithm", type: "html", name: `${display_name3} algorithm`, value: `${display_description}` } }; // node_modules/obsidian-smart-env/src/utils/open_source.js var import_obsidian32 = require("obsidian"); async function open_source(item, event = null) { try { const env = item.env; const obsidian_app = env.obsidian_app; let target_path = item.key; if (target_path.endsWith("#")) target_path = target_path.slice(0, -1); let target_file; if (target_path.includes("#")) { const [file_path] = target_path.split("#"); target_file = obsidian_app.metadataCache.getFirstLinkpathDest(file_path, ""); } else { target_file = obsidian_app.metadataCache.getFirstLinkpathDest(target_path, ""); } if (!target_file) { console.warn(`[open_note] Unable to resolve file for ${target_path}`); return; } let leaf; if (event) { const is_mod = import_obsidian32.Keymap.isModEvent(event); const is_alt = import_obsidian32.Keymap.isModifier(event, "Alt"); if (is_mod && is_alt) { leaf = obsidian_app.workspace.splitActiveLeaf("vertical"); } else if (is_mod) { leaf = obsidian_app.workspace.getLeaf(true); } else { leaf = obsidian_app.workspace.getMostRecentLeaf(); } } else { leaf = obsidian_app.workspace.getMostRecentLeaf(); } await leaf.openFile(target_file); if (typeof item?.line_start === "number") { const { editor } = leaf.view; const pos = { line: item.line_start, ch: 0 }; editor.setCursor(pos); editor.scrollIntoView({ to: pos, from: pos }, true); } item.emit_event("sources:opened", { event_source: "open_source method" }); } catch (e) { console.error("Error in open_source:", e); item.emit_event("notification:error", { message: e.message, event_source: "open_source method" }); } } // node_modules/obsidian-smart-env/src/actions/source/open.js async function source_open(event = null) { await open_source(this, event); } // node_modules/obsidian-smart-env/smart_env.config.js var smart_env_config = { collections: { embedding_models: embedding_models_default2, lookup_lists: lookup_lists_default }, item_types: { EmbeddingModel, LookupList }, items: { embedding_model: { class: EmbeddingModel }, lookup_list: { class: LookupList } }, modules: {}, components: { collection_settings: { render: render2 }, context_item_leaf: { render: render3 }, env_stats: { render: render4 }, form_dropdown: { render: render5 }, lean_coffee_callout: { render: render6 }, milestones: { render: render7 }, notifications_feed: { render: render8 }, pro_plugins_list: { render: render9 }, pro_plugins_list_item: { render: render10 }, settings_env_model: { render: render11 }, settings_env_model_type: { render: render12 }, settings_env_models: { render: render13 }, settings_env_sources: { render: render14 }, settings_model_actions: { render: render15 }, settings_notifications: { render: render16 }, settings_smart_env: { render: render17 }, smart_context_actions: { render: render18 }, smart_context_item: { render: render19 }, smart_context_meta: { render: render20 }, smart_context_tree: { render: render21 }, source_inspector: { render: render22 }, status_bar: { render: render23 }, supporter_callout: { render: render24 }, user_agreement_callout: { render: render25 } }, actions: { context_copy_to_clipboard: { action: copy_to_clipboard3 }, context_item_merge_template: { action: merge_template, settings_config: settings_config7, default_settings: default_settings2 }, context_merge_template: { action: merge_template2, settings_config: settings_config8, default_settings: default_settings3 }, context_suggest_blocks: { action: context_suggest_blocks, display_name }, context_suggest_sources: { action: context_suggest_sources, display_name: display_name2 }, lookup_list_pre_process: { action: pre_process, pre_process }, similarity: { action: similarity, settings_config: settings_config9, display_name: display_name3, display_description }, source_open: { action: source_open } } }; // node_modules/obsidian-smart-env/default.config.js var smart_env_config2 = { env_path: "", modules: { smart_fs: { class: SmartFs, adapter: ObsidianFsAdapter }, smart_view: { class: SmartView, adapter: SmartViewObsidianAdapter }, // smart_notices: { // class: SmartNotices, // adapter: Notice, // }, smart_embed_model: { class: SmartEmbedModel, adapters: { transformers: SmartEmbedTransformersIframeAdapter, openai: SmartEmbedOpenAIAdapter, ollama: SmartEmbedOllamaAdapter, gemini: GeminiEmbedModelAdapter, lm_studio: LmStudioEmbedModelAdapter } }, smart_chat_model: { class: SmartChatModel, // DEPRECATED FORMAT: will be changed (requires SmartModel adapters getters update) adapters: { anthropic: SmartChatModelAnthropicAdapter, azure: SmartChatModelAzureAdapter, custom: SmartChatModelCustomAdapter, google: SmartChatModelGoogleAdapter, gemini: SmartChatModelGeminiAdapter, groq: SmartChatModelGroqAdapter, lm_studio: SmartChatModelLmStudioAdapter, ollama: SmartChatModelOllamaAdapter, open_router: SmartChatModelOpenRouterAdapter, openai: SmartChatModelOpenaiAdapter, xai: SmartChatModelXaiAdapter, deepseek: SmartChatModelDeepseekAdapter }, http_adapter: new SmartHttpRequest({ adapter: SmartHttpObsidianRequestAdapter, obsidian_request_url: import_obsidian35.requestUrl }) }, http_adapter: { class: SmartHttpRequest, adapter: SmartHttpObsidianRequestAdapter, obsidian_request_url: import_obsidian35.requestUrl } }, collections: { context_items: context_items_default, event_logs: event_logs_default, smart_components: smart_components_default2, smart_contexts: smart_contexts_default, smart_sources: { collection_key: "smart_sources", class: SmartSources, data_adapter: AjsonMultiFileSourcesDataAdapter, source_adapters: { "md": ObsidianMarkdownSourceContentAdapter, "txt": ObsidianMarkdownSourceContentAdapter, "excalidraw.md": ExcalidrawSourceContentAdapter, "base": BasesSourceContentAdapter, "canvas": CanvasSourceContentAdapter // "canvas": MarkdownSourceContentAdapter, // "default": MarkdownSourceContentAdapter, }, content_parsers: [ parse_blocks ], // process_embed_queue: false, process_embed_queue: true, // trigger embedding on load load_order: 100 // load last }, smart_blocks: { collection_key: "smart_blocks", class: SmartBlocks, data_adapter: AjsonMultiFileBlocksDataAdapter, block_adapters: { "md": MarkdownBlockContentAdapter, "txt": MarkdownBlockContentAdapter, "excalidraw.md": MarkdownBlockContentAdapter // "canvas": MarkdownBlockContentAdapter, } } }, item_types: { SmartSource, SmartBlock }, items: { smart_source: smart_source_default, smart_block: smart_block_default }, default_settings, // begin obsidian-smart-env specific modules (need to update build_env_config.js to handle) modals: { context_selector: { class: ContextModal, default_suggest_action_keys: [ "context_suggest_sources" ] }, milestones_modal: { class: MilestonesModal }, notifications_feed_modal: { class: NotificationsFeedModal } } }; merge_env_config(smart_env_config2, smart_env_config); var default_config_default = smart_env_config2; // node_modules/obsidian-smart-env/utils/add_icons.js var import_obsidian36 = require("obsidian"); function add_smart_chat_icon() { (0, import_obsidian36.addIcon)("smart-chat", ` `); } function add_smart_connections_icon() { (0, import_obsidian36.addIcon)("smart-connections", ` `); } function add_smart_lookup_icon() { (0, import_obsidian36.addIcon)("smart-lookup", ` `); } // node_modules/obsidian-smart-env/node_modules/smart-notices/smart_notices.js var import_obsidian37 = require("obsidian"); // node_modules/obsidian-smart-env/node_modules/smart-notices/notices.js var NOTICES = { item_excluded: { en: "Cannot show Smart Connections for excluded entity: {{entity_key}}" }, load_env: { en: "Mobile detected: to prevent performance issues, click to load Smart Environment when ready.", button: { en: `Load Smart Env`, callback: (env) => { env.load(true); } }, timeout: 1e4 }, /** @deprecated in favor of in-component insctructions (2025-06-22) */ missing_entity: { en: "No entity found for key: {{key}}" }, notice_muted: { en: "Notice muted" }, new_version_available: { en: "A new version is available! (v{{version}})", timeout: 15e3, button: { en: "Release notes", callback: (scope) => { window.open("https://github.com/brianpetro/obsidian-smart-connections/releases", "_blank"); } } }, new_early_access_version_available: { en: "A new early access version is available! (v{{version}})" }, supporter_key_required: { en: "Supporter license key required for early access update" }, revert_to_stable_release: { en: 'Click "Check for Updates" in the community plugins tab and complete the update for Smart Connections to finish reverting to the stable release.', timeout: 0 }, action_installed: { en: 'Installed action "{{name}}"' }, action_install_error: { en: 'Error installing action "{{name}}": {{error}}', timeout: 0 }, embed_model_not_loaded: { en: "Embed model not loaded. Please wait for the model to load and try again." }, embed_search_text_failed: { en: "Failed to embed search text." }, error_in_embedding_search: { en: "Error in embedding search. See console for details." }, copied_to_clipboard: { en: "Message: {{content}} copied successfully." }, copy_failed: { en: "Unable to copy message to clipboard." }, copied_chatgpt_url_to_clipboard: { en: "ChatGPT URL copied to clipboard." }, loading_collection: { en: "Loading {{collection_key}}..." }, done_loading_collection: { en: "{{collection_key}} loaded." }, saving_collection: { en: "Saving {{collection_key}}..." }, initial_scan: { en: "[{{collection_key}}] Starting initial scan...", timeout: 0 }, done_initial_scan: { en: "[{{collection_key}}] Initial scan complete.", timeout: 3e3 }, pruning_collection: { en: "Pruning {{collection_key}}..." }, done_pruning_collection: { en: "Pruned {{count}} items from {{collection_key}}." }, embedding_progress: { en: "Embedding progress: {{progress}} / {{total}}\n{{tokens_per_second}} tokens/sec using {{model_name}}", button: { en: "Pause", callback: (env) => { console.log("pausing"); env.smart_sources.entities_vector_adapter.halt_embed_queue_processing(); } }, timeout: 0 }, embedding_complete: { en: "Embedding complete. {{total_embeddings}} embeddings created. {{tokens_per_second}} tokens/sec using {{model_name}}", timeout: 0 }, embedding_paused: { en: "Embedding paused. Progress: {{progress}} / {{total}}\n{{tokens_per_second}} tokens/sec using {{model_name}}", button: { en: "Resume", callback: (env) => { env.smart_sources.entities_vector_adapter.resume_embed_queue_processing(100); } }, timeout: 0 }, embedding_error: { en: "Error embedding: {{error}}", timeout: 0 }, import_progress: { en: "Importing... {{progress}} / {{total}} sources", timeout: 0 }, done_import: { en: "Import complete. {{count}} sources imported in {{time_in_seconds}}s", timeout: 0 }, no_import_queue: { en: "No items in import queue" }, clearing_all: { en: "Clearing all data...", timeout: 0 }, done_clearing_all: { en: "All data cleared and reimported", timeout: 3e3 }, image_extracting: { en: "Extracting text from Image(s)", timeout: 0 }, pdf_extracting: { en: "Extracting text from PDF(s)", timeout: 0 }, insufficient_settings: { en: "Insufficient settings for {{key}}, missing: {{missing}}", timeout: 0 }, unable_to_init_source: { en: "Unable to initialize source: {{key}}", timeout: 0 }, reload_sources: { en: "Reloaded sources in {{time_ms}}ms" } }; // node_modules/obsidian-smart-env/node_modules/smart-notices/smart_notices.js function define_default_create_methods(notices) { for (const key of Object.keys(notices)) { const notice_obj = notices[key]; if (typeof notice_obj.create !== "function") { notice_obj.create = function(opts = {}) { let text = this.en ?? key; for (const [k, v] of Object.entries(opts)) { text = text.replace(new RegExp(`{{${k}}}`, "g"), String(v)); } let button; if (!opts.button && this.button) { const btn_label = typeof this.button.en === "string" ? this.button.en : "OK"; button = { text: btn_label, callback: typeof this.button.callback === "function" ? this.button.callback : () => { } // no-op }; } else { button = opts.button; } let final_timeout = opts.timeout ?? this.timeout ?? 5e3; return { text, button, timeout: final_timeout, confirm: opts.confirm, // pass any user-provided confirm immutable: opts.immutable // pass any user-provided immutable }; }; } } return notices; } var SmartNotices = class { /** * @param {Object} scope - The main plugin instance */ constructor(env, opts = {}) { env?.create_env_getter(this); this.active = {}; this.adapter = opts.adapter || this.env.config.modules.smart_notices.adapter; define_default_create_methods(NOTICES); } /** plugin settings for notices (muted, etc.) */ get settings() { if (!this.env?.settings?.smart_notices) { this.env.settings.smart_notices = {}; } if (!this.env?.settings?.smart_notices?.muted) { this.env.settings.smart_notices.muted = {}; } return this.env?.settings?.smart_notices; } /** * Displays a notice by key or custom message. * Usage: * notices.show('load_env', { scope: this }); * * @param {string} id - The notice key or custom ID * @param {object} opts - Additional user opts */ show(id, opts = {}) { let message = null; if (typeof opts === "string") { message = opts; } else { opts = opts || {}; } const normalized_id = this._normalize_notice_key(id); if (this.settings?.muted?.[normalized_id]) { if (opts.confirm?.callback) { opts.confirm.callback(); } return; } const notice_entry = NOTICES[id]; let derived = { text: message || id, timeout: opts.timeout ?? 5e3, button: opts.button, immutable: opts.immutable, confirm: opts.confirm }; if (notice_entry?.create) { const result = notice_entry.create({ ...opts }); derived.text = message || result.text; derived.timeout = result.timeout; derived.button = result.button; derived.immutable = result.immutable; derived.confirm = result.confirm; } const content_fragment = this._build_fragment(normalized_id, derived.text, derived); if (this.active[normalized_id]?.noticeEl?.isConnected) { return this.active[normalized_id].setMessage(content_fragment, derived.timeout); } return this._render_notice(normalized_id, content_fragment, derived); } /** * Normalizes the notice key to a safe string. */ _normalize_notice_key(key) { return key.replace(/[^a-zA-Z0-9_-]/g, "_"); } /** * Creates and tracks the notice instance */ _render_notice(normalized_id, content_fragment, { timeout }) { this.active[normalized_id] = new this.adapter(content_fragment, timeout); return this.active[normalized_id]; } /** * Builds a DocumentFragment with notice text & possible buttons */ _build_fragment(id, text, { button, confirm: confirm2, immutable }) { const frag = document.createDocumentFragment(); frag.createEl("p", { cls: "sc-notice-head", text: `[Smart Env v${this.env.constructor.version}]` }); const content = frag.createEl("p", { cls: "sc-notice-content", text }); const actions = frag.createEl("div", { cls: "sc-notice-actions" }); if (confirm2?.text && typeof confirm2.callback === "function") { this._add_button(confirm2, actions); } if (button?.text && typeof button.callback === "function") { this._add_button(button, actions); } if (!immutable) { this._add_mute_button(id, actions); } return frag; } /** * Creates a
    `); frag.querySelector("button").addEventListener("click", this.load.bind(this, true)); new import_obsidian40.Notice(frag, 0); return; } await super.load(); this.smart_sources?.register_source_watchers?.(this.smart_sources); const plugin = this.main; plugin.registerEvent( plugin.app.workspace.on("active-leaf-change", (leaf) => { this.smart_sources?.debounce_re_import_queue?.(); const current_path = leaf.view?.file?.path; this.emit_source_opened(current_path, "active-leaf-change"); }) ); plugin.registerEvent( plugin.app.workspace.on("file-open", (file) => { this.smart_sources?.debounce_re_import_queue?.(); const current_path = file?.path; this.emit_source_opened(current_path, "file-open"); }) ); if (this._config.collections.smart_completions?.completion_adapters?.SmartCompletionVariableAdapter) { register_completion_variable_adapter_replacements(this._config.collections.smart_completions.completion_adapters.SmartCompletionVariableAdapter); } const ContextModal2 = this._config.modals.context_selector.class; ContextModal2.register_modal(this.main); this.register_status_bar(); register_first_of_event_notifications(this); } emit_source_opened(current_path, event_source = null) { if (this._current_opened_source === current_path) return; const current_source = this.smart_sources.get(current_path); if (current_source) { this._current_opened_source = current_path; current_source.emit_event("sources:opened", { event_source }); } } // queue re-import the file queue_source_re_import(source) { this.smart_sources?.queue_source_re_import?.(source); } // prevent importing when user is acting within the workspace debounce_re_import_queue() { this.smart_sources?.debounce_re_import_queue?.(); } async run_re_import() { await this.smart_sources?.run_re_import?.(); } register_status_bar() { const status_container = this.main?.app?.statusBar?.containerEl; status_container?.querySelector?.(".smart-env-status-container")?.closest?.(".status-bar-item")?.remove?.(); this.status_elm = this.main.addStatusBarItem(); this.smart_components?.render_component("status_bar", this).then((container) => { this.status_elm.empty?.(); this.status_elm.appendChild(container); }); } /** * @deprecated see events */ get notices() { if (!this._notices) { this._notices = new SmartNotices(this, { adapter: import_obsidian40.Notice }); } return this._notices; } // Smart Plugins /** * This is the function that is called by the new "Sign in with Smart Plugins" button. * @deprecated 2025-12-13 moved to components/pro-plugins/list.js * It replicates the old 'initiate_oauth()' logic from sc_settings_tab.js */ initiate_smart_plugins_oauth() { console.log("initiate_smart_plugins_oauth"); const state = Math.random().toString(36).slice(2); const redirect_uri = encodeURIComponent("obsidian://smart-plugins/callback"); const url = `${get_smart_server_url2()}/oauth?client_id=smart-plugins-op&redirect_uri=${redirect_uri}&state=${state}`; window.open(url, "_external"); return url; } /** * Handles the OAuth callback from the Smart Plugins server. * @param {Object} params - The URL parameters from the OAuth callback. */ async handle_smart_plugins_oauth_callback(params) { const code = params.code; if (!code) { new import_obsidian40.Notice("No OAuth code provided in URL. Login failed."); return; } try { await exchange_code_for_tokens(code, this.plugin); this.events.emit("smart_plugins_oauth_completed"); } catch (err) { console.error("OAuth callback error", err); new import_obsidian40.Notice(`OAuth callback error: ${err.message}`); } } /** * Serializes the environment and, when in a browser, triggers a download. * @param {string} [filename='smart_env.json'] * @returns {string} stringified JSON */ export_json(filename = "smart_env.json") { const json = JSON.stringify(this.to_json(), null, 2); if (typeof document !== "undefined") { download_json(json, filename); } return json; } // WAIT FOR OBSIDIAN SYNC async ready_to_load_collections() { await new Promise((r) => setTimeout(r, 3e3)); await this.wait_for_obsidian_sync(); } async wait_for_obsidian_sync() { while (this.obsidian_is_syncing) { console.log("Smart Connections: Waiting for Obsidian Sync to finish"); await new Promise((r) => setTimeout(r, 1e3)); if (!this.plugin) throw new Error("Plugin disabled while waiting for obsidian sync, reload required."); } } get obsidian_is_syncing() { const obsidian_sync_instance = this.plugin?.app?.internalPlugins?.plugins?.sync?.instance; if (!obsidian_sync_instance) return false; if (obsidian_sync_instance?.syncStatus.startsWith("Uploading")) return false; if (obsidian_sync_instance?.syncStatus.startsWith("Fully synced")) return false; return obsidian_sync_instance?.syncing; } // get obsidian app instance get obsidian_app() { return this.plugin?.app ?? window.app; } // open notifications feed modal open_notifications_feed_modal() { const NotificationsModalClass = this.config.modals.notifications_feed_modal.class; const modal = new NotificationsModalClass(this.obsidian_app, this); modal.open(); } // open milestones modal open_milestones_modal() { const MilestonesModalClass = this.config.modals.milestones_modal.class; const modal = new MilestonesModalClass(this.obsidian_app, this); modal.open(); } run_migrations() { remove_smart_plugins_plugin({ app: this.plugin.app, plugin_ids: ["smart-plugins"] }); remove_smart_plugins_plugin({ app: this.plugin.app, plugin_ids: ["smart-editor"] }); remove_smart_plugins_plugin({ app: this.plugin.app, plugin_ids: ["smart-sources"] }); } }; function download_json(json, filename) { const blob = new Blob([json], { type: "application/json" }); const url = URL.createObjectURL(blob); const anchor = document.createElement("a"); anchor.href = url; anchor.download = filename; document.body.appendChild(anchor); anchor.click(); document.body.removeChild(anchor); URL.revokeObjectURL(url); } // node_modules/obsidian-smart-env/src/views/smart_plugin_settings_tab.js var import_obsidian42 = require("obsidian"); // node_modules/obsidian-smart-env/utils/wait_for_env_to_load.js var import_obsidian41 = require("obsidian"); async function wait_for_env_to_load(scope, opts = {}) { const { wait_for_states = ["loaded"] } = opts; const container = scope.container || scope.containerEl; if (!wait_for_states.includes(scope.env?.state)) { let clicked_load_env = false; while (scope.env.state === "init" && import_obsidian41.Platform.isMobile && !clicked_load_env) { if (container) { container.empty(); scope.env.smart_view.safe_inner_html(container, ""); container.querySelector("button").addEventListener("click", () => { scope.env.load(true); clicked_load_env = true; }); } else { console.log("Waiting for env to load (mobile)..."); } await new Promise((r) => setTimeout(r, 2e3)); } while (!wait_for_states.includes(scope.env.state)) { if (container) { const loading_msg = scope.env?.obsidian_is_syncing ? "Waiting for Obsidian Sync to finish..." : "Loading Obsidian Smart Environment..."; container.empty(); scope.env.smart_view.safe_inner_html(container, loading_msg); } else { console.log("Waiting for env to load..."); } await new Promise((r) => setTimeout(r, 2e3)); } } } // node_modules/obsidian-smart-env/src/views/settings.css var settings_default = `/* 1) Host elements that should get a PRO badge */\r :is(\r .pro-setting .setting-item-name\r ) {\r position: relative; /* safe default, keeps ::after anchored */\r }\r \r /* 2) The PRO badge itself */\r :is(\r .pro-setting .setting-item-name:not(:empty)\r )::after {\r content: "PRO";\r \r /* layout */\r display: inline-flex;\r align-items: center;\r justify-content: center;\r margin-left: 0.4em;\r padding: 0.08em 0.55em;\r border-radius: 999px;\r white-space: nowrap;\r vertical-align: middle;\r \r /* typography */\r font-size: 0.7em;\r font-weight: 600;\r letter-spacing: 0.14em;\r text-transform: uppercase;\r line-height: 1;\r \r /* color system: only Obsidian variables */\r background-color: var(--interactive-accent);\r background-image: linear-gradient(\r 135deg,\r var(--interactive-accent),\r var(--interactive-accent-hover)\r );\r color: var(--text-on-accent, var(--background-primary));\r border: 1px solid var(--background-modifier-border);\r \r /* subtle separation & depth, theme-aware */\r box-shadow:\r 0 0 0 1px var(--background-primary),\r 0 1px 3px rgba(0, 0, 0, 0.35);\r transform: translateY(-0.03em);\r }\r \r /* 3) Interactive refinement: follow Obsidian's accent hover behavior */\r :is(\r .pro-setting .setting-item-name\r ):hover::after {\r background-color: var(--interactive-accent-hover);\r filter: brightness(1.05);\r }\r \r .pro-plugins-container {\r border: 1px solid var(--interactive-accent);\r border-radius: var(--radius-m);\r padding: 1rem;\r margin-top: 1rem;\r background-color: var(--background-secondary);\r }\r \r .smart-plugin-settings-header .actions-container {\r display: flex;\r flex-wrap: wrap;\r gap: var(--pill-padding-y);\r }\r \r .setting-component:has(.dropdown-no-options) {\r display: none;\r }\r \r /* wrap Obsidian native styles within smart plugin settings main class */\r .smart-plugin-settings-main, .smart-plugin-settings-env {\r .setting-group {\r margin-top: var(--size-4-6);\r margin-bottom: var(--size-4-6);\r }\r /* polyfill */\r .setting-group .setting-items {\r background-color: var(--setting-items-background, var(--background-primary-alt));\r padding: var(--setting-items-padding, var(--size-4-5));\r border-radius: var(--setting-items-radius, var(--radius-l));\r border: var(--setting-items-border-width, 0) solid var(--setting-items-border-color, var(--background-modifier-border));\r }\r }\r `; // node_modules/obsidian-smart-env/src/views/smart_plugin_settings_tab.js var SmartPluginSettingsTab = class extends import_obsidian42.PluginSettingTab { constructor(app, plugin) { super(app, plugin); this.plugin = plugin; this.header_container = null; this.plugin_container = null; this.global_settings_container = null; this.plugin?.env?.create_env_getter?.(this); if (this.env.is_pro && !this.env_settings_tab) this.plugin.addSettingTab(new SmartEnvSettingTab(this.plugin.app, this.plugin)); this.icon = "smart-connections"; if (this.env.is_pro) { this.name = this.name.replace("Smart ", ""); } } get smart_view() { return this.env?.smart_view; } async display() { await this.render(); } async render() { this.containerEl.empty(); render_pre_env_load(this); await this.env.constructor.wait_for({ loaded: true }); this.prepare_layout(); await this.render_header(this.header_container); await this.render_plugin_settings(this.plugin_container); await this.render_global_settings(this.global_settings_container); } prepare_layout() { this.smart_view.apply_style_sheet(settings_default); this.containerEl.empty(); this.header_container = this.containerEl.createDiv({ cls: "smart-plugin-settings-header" }); this.plugin_container = this.containerEl.createDiv({ cls: "smart-plugin-settings-main" }); this.global_settings_container = this.containerEl.createDiv({ cls: "smart-plugin-settings-env" }); this.pro_plugins_container = this.containerEl.createDiv({ cls: "smart-plugin-settings-pro-plugins" }); } /** * @abstract */ async render_header(container) { } /** * @abstract */ async render_plugin_settings(container) { } async render_global_settings(container) { if (!container) return; container.empty?.(); if (!this.env) return; if (this.env.is_pro) { const settings_item_div = container.createDiv({ cls: "setting-item" }); const info_div = settings_item_div.createDiv({ cls: "setting-item-info" }); info_div.createDiv({ cls: "setting-item-name", text: "Smart Environment" }); info_div.createDiv({ cls: "setting-item-description", text: "Manage global settings in the dedicated Smart Environment settings tab." }); const control_div = settings_item_div.createDiv({ cls: "setting-item-control" }); const button = control_div.createEl("button", { text: "Open settings" }); button.addEventListener("click", () => { this.app.setting.openTabById("smart-environment"); }); } else { const settings_smart_env = await this.render_component("settings_smart_env", this.env); if (settings_smart_env) container.appendChild(settings_smart_env); } const smart_plugins_settings = await this.render_component("pro_plugins_list", this.env); this.pro_plugins_container.empty?.(); this.pro_plugins_container.appendChild(smart_plugins_settings); } async render_component(name, scope, params = {}) { return await this.env.smart_components.render_component(name, scope, params); } // Detect Existing Smart Environment Settings Tab get env_settings_tab() { const app = this.plugin.app || window.app; return app.setting.pluginTabs.find((t) => t.id === "smart-environment"); } }; var SmartEnvSettingTab = class extends import_obsidian42.PluginSettingTab { constructor(app, plugin) { super(app, plugin); this.plugin = plugin; this.header_container = null; this.plugin_container = null; this.global_settings_container = null; this.plugin?.env?.create_env_getter?.(this); this.plugin = plugin; this.name = "Smart Env Pro"; this.id = "smart-environment"; this.icon = "smart-connections"; } get smart_view() { return this.env?.smart_view; } display() { this.render(); } async render_component(name, scope, params = {}) { return await this.env.smart_components.render_component(name, scope, params); } async render() { this.containerEl.empty(); this.smart_view.apply_style_sheet(settings_default); render_pre_env_load(this); await this.env.constructor.wait_for({ loaded: true }); this.containerEl.empty(); this.header_container = this.containerEl.createDiv({ cls: "smart-plugin-settings-header" }); this.plugin_container = this.containerEl.createDiv({ cls: "smart-plugin-settings-main" }); this.pro_plugins_container = this.containerEl.createDiv({ cls: "smart-plugin-settings-pro-plugins" }); this.header_container.createEl("p", { text: "Manage all global Smart Environment settings from one tab. These settings apply to all Smart Plugins." }); const settings_smart_env = await this.render_component("settings_smart_env", this.env); if (settings_smart_env) this.plugin_container.appendChild(settings_smart_env); const smart_plugins_settings = await this.render_component("pro_plugins_list", this.env); this.pro_plugins_container.empty?.(); this.pro_plugins_container.appendChild(smart_plugins_settings); } }; function render_pre_env_load(scope) { const container = scope.containerEl; const env = scope.env; if (env.state !== "loaded") { if (env.state === "loading") { container.createEl("p", { text: "Smart Environment is loading\u2026" }); } else { container.createEl("p", { text: "Smart Environment not yet initialized." }); const load_btn = container.createEl("button", { text: "Load Smart Environment" }); load_btn.addEventListener("click", async () => { load_btn.disabled = true; load_btn.textContent = "Loading Smart Environment\u2026"; await env.load(true); }); } } } // node_modules/obsidian-smart-env/smart_plugin.js var import_obsidian43 = require("obsidian"); var SmartPlugin = class extends import_obsidian43.Plugin { /** * override in subclass to provide commands. * use property key to override commands in further subclasses. */ get commands() { return { show_release_notes: { id: "show-release-notes", name: "Show release notes", callback: () => this.show_release_notes() } }; } register_commands() { Object.values(this.commands).forEach((cmd) => { this.addCommand(cmd); }); } /** * override in subclass to provide ribbon icons. * use property key to override ribbon icons in further subclasses. */ get ribbon_icons() { return {}; } register_ribbon_icons() { const icons = Object.values(this.ribbon_icons); for (let i = 0; i < icons.length; i++) { const ri = icons[i]; this.addRibbonIcon(ri.icon_name, ri.description, ri.callback); } } get item_views() { return {}; } register_item_views() { const views = Object.values(this.item_views); for (let i = 0; i < views.length; i++) { const ViewClass = views[i]; if (typeof ViewClass.register_item_view === "function") { ViewClass.register_item_view(this); } } } /** * user version and first seen handling */ async is_new_user() { const data = await this.loadData() || {}; if (!data.installed_at) { data.installed_at = Date.now(); await this.saveData(data); return true; } return false; } /** * Returns the last saved plugin version or an empty string. * @returns {Promise} */ async get_last_known_version() { const data = await this.loadData() || {}; return data.last_version || ""; } /** * Persists the provided plugin version as last shown. * @param {string} version * @returns {Promise} */ async set_last_known_version(version) { const data = await this.loadData() || {}; data.last_version = version; await this.saveData(data); } /** * Determines if release notes should be shown for `current_version`. * @param {string} current_version * @returns {Promise} */ async is_new_plugin_version(current_version) { return await this.get_last_known_version() !== current_version; } }; // node_modules/smart-collections/utils/collection_instance_name_from.js function collection_instance_name_from2(class_name) { if (class_name.endsWith("Item")) { return class_name.replace(/Item$/, "").replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase(); } return class_name.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase().replace(/y$/, "ie") + "s"; } // node_modules/smart-utils/deep_merge.js function deep_merge2(target = {}, source = {}) { for (const key in source) { if (!Object.prototype.hasOwnProperty.call(source, key)) continue; if (is_plain_object4(source[key]) && is_plain_object4(target[key])) { deep_merge2(target[key], source[key]); } else { target[key] = source[key]; } } return target; } function is_plain_object4(o) { return o && typeof o === "object" && !Array.isArray(o); } // node_modules/smart-collections/utils/helpers.js function create_uid2(data) { const str = JSON.stringify(data); let hash = 0; if (str.length === 0) return hash; for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i); hash = (hash << 5) - hash + char; hash = hash & hash; if (hash < 0) hash = hash * -1; } return hash.toString() + str.length; } // node_modules/smart-utils/camel_case_to_snake_case.js function camel_case_to_snake_case2(str = "") { return str.replace(/([A-Z])/g, (m) => `_${m.toLowerCase()}`).replace(/^_/, "").replace(/2$/, ""); } // node_modules/smart-collections/utils/deep_equal.js function deep_equal2(obj1, obj2, visited = /* @__PURE__ */ new WeakMap()) { if (obj1 === obj2) return true; if (obj1 === null || obj2 === null || obj1 === void 0 || obj2 === void 0) return false; if (typeof obj1 !== typeof obj2 || Array.isArray(obj1) !== Array.isArray(obj2)) return false; if (Array.isArray(obj1)) { if (obj1.length !== obj2.length) return false; return obj1.every((item, index) => deep_equal2(item, obj2[index], visited)); } if (typeof obj1 === "object") { if (visited.has(obj1)) return visited.get(obj1) === obj2; visited.set(obj1, obj2); const keys1 = Object.keys(obj1); const keys2 = Object.keys(obj2); if (keys1.length !== keys2.length) return false; return keys1.every((key) => deep_equal2(obj1[key], obj2[key], visited)); } return obj1 === obj2; } // node_modules/smart-collections/utils/get_item_display_name.js function get_item_display_name2(key, show_full_path) { if (show_full_path) { return key.split("/").join(" > ").replace(".md", ""); } return key.split("/").pop().replace(".md", ""); } // node_modules/smart-collections/utils/create_actions_proxy.js function create_actions_proxy2(ctx, actions_source) { const input = actions_source || {}; const is_plain_object6 = (val) => typeof val === "object" && val !== null && !Array.isArray(val); const is_function = (val) => typeof val === "function"; const is_class_export = (val) => is_function(val) && /^class\s/.test(Function.prototype.toString.call(val)); const is_action_object = (val) => is_plain_object6(val) && is_function(val.action); const is_action_candidate = (val) => is_function(val) || is_action_object(val) || is_class_export(val); const ignored_meta_keys = /* @__PURE__ */ new Set(["length", "name", "prototype"]); const clone_with_descriptors = (obj) => { if (!is_plain_object6(obj)) return obj; const out = Object.create(Object.getPrototypeOf(obj) || null); for (const key of Reflect.ownKeys(obj)) { const descriptor = Object.getOwnPropertyDescriptor(obj, key); if (!descriptor) continue; const next = { ...descriptor }; if ("value" in next && is_plain_object6(next.value)) { next.value = clone_with_descriptors(next.value); } try { Object.defineProperty(out, key, next); } catch { out[key] = next.value; } } return out; }; const should_bucket_actions = (val) => { if (!is_plain_object6(val)) return false; if (is_action_object(val)) return false; const keys = Reflect.ownKeys(val); if (keys.length === 0) return false; let found_candidate = false; for (const key of keys) { const descriptor = Object.getOwnPropertyDescriptor(val, key); if (!descriptor) continue; if ("value" in descriptor) { const entry = descriptor.value; if (is_action_candidate(entry)) { found_candidate = true; continue; } if (is_plain_object6(entry)) { if (should_bucket_actions(entry)) { found_candidate = true; continue; } return false; } if (typeof entry === "undefined") continue; return false; } return false; } return found_candidate; }; const clone_descriptor = (descriptor) => { if (!descriptor) return descriptor; if (!("value" in descriptor)) return { ...descriptor }; const cloned = is_plain_object6(descriptor.value) ? clone_with_descriptors(descriptor.value) : descriptor.value; return { ...descriptor, value: cloned }; }; const build_sources = (src) => { const global_source2 = /* @__PURE__ */ Object.create(null); const scoped_sources2 = /* @__PURE__ */ new Map(); for (const key of Reflect.ownKeys(src)) { const descriptor = Object.getOwnPropertyDescriptor(src, key); if (!descriptor) continue; if ("value" in descriptor && should_bucket_actions(descriptor.value)) { scoped_sources2.set(key, clone_with_descriptors(descriptor.value)); continue; } try { Object.defineProperty(global_source2, key, clone_descriptor(descriptor)); } catch { global_source2[key] = descriptor.value; } } return { global_source: global_source2, scoped_sources: scoped_sources2 }; }; const { global_source, scoped_sources } = build_sources(input); const has_own = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop); const cache = /* @__PURE__ */ Object.create(null); const copy_metadata = (source, target, omit = []) => { if (!source || !target) return; const skips = /* @__PURE__ */ new Set([...ignored_meta_keys, ...omit]); for (const key of Reflect.ownKeys(source)) { if (skips.has(key)) continue; const descriptor = Object.getOwnPropertyDescriptor(source, key); if (!descriptor) continue; try { Object.defineProperty(target, key, descriptor); } catch { target[key] = descriptor.value; } } }; const instantiate_class = (Ctor) => { const instance = new Ctor(ctx); const candidate = instance.action || instance.run || instance.execute || instance.call; if (is_function(candidate)) { const bound = candidate.bind(instance); copy_metadata(Ctor, bound); copy_metadata(instance, bound); bound.instance = instance; return bound; } copy_metadata(Ctor, instance); return instance; }; const bind_or_clone = (val) => { if (is_class_export(val)) { return instantiate_class(val); } if (is_action_object(val)) { const bound = val.action.bind(ctx); copy_metadata(val, bound, ["action"]); return bound; } if (is_function(val)) { const bound = val.bind(ctx); copy_metadata(val, bound); return bound; } if (is_plain_object6(val)) { return clone_with_descriptors(val); } return val; }; const scope_actions_for = () => { const scope_key = ctx?.constructor?.key; if (typeof scope_key === "undefined" || scope_key === null) return null; const bucket = scoped_sources.get(scope_key); return bucket && is_plain_object6(bucket) ? bucket : null; }; const cache_result = (target, prop, value) => { target[prop] = value; return value; }; const compute_and_cache = (target, prop) => { const scoped = scope_actions_for(); if (scoped && has_own(scoped, prop)) { return cache_result(target, prop, bind_or_clone(scoped[prop])); } if (has_own(global_source, prop)) { return cache_result(target, prop, bind_or_clone(global_source[prop])); } return cache_result(target, prop, void 0); }; const union_keys = () => { const scoped = scope_actions_for(); const keys = new Set(Reflect.ownKeys(cache)); for (const key of Reflect.ownKeys(global_source)) { keys.add(key); } if (scoped) { for (const key of Reflect.ownKeys(scoped)) { keys.add(key); } } return Array.from(keys); }; const descriptor_for = (target, prop) => ({ configurable: true, enumerable: true, value: target[prop] }); return new Proxy(cache, { get: (target, prop) => { if (prop === Symbol.toStringTag) return "ActionsProxy"; if (prop in target) return target[prop]; return compute_and_cache(target, prop); }, has: (target, prop) => { if (prop in target) return true; const scoped = scope_actions_for(); if (scoped && has_own(scoped, prop)) return true; return has_own(global_source, prop); }, ownKeys: () => union_keys(), getOwnPropertyDescriptor: (target, prop) => { if (has_own(target, prop)) { return descriptor_for(target, prop); } const scoped = scope_actions_for(); if (scoped && has_own(scoped, prop)) { if (!has_own(target, prop)) { compute_and_cache(target, prop); } return descriptor_for(target, prop); } if (has_own(global_source, prop)) { if (!has_own(target, prop)) { compute_and_cache(target, prop); } return descriptor_for(target, prop); } return void 0; }, defineProperty: (target, prop, descriptor) => { if ("value" in descriptor) { target[prop] = descriptor.value; return true; } return false; }, set: (target, prop, value) => { target[prop] = value; return true; }, deleteProperty: (target, prop) => { if (has_own(target, prop)) { delete target[prop]; } return true; } }); } // node_modules/smart-collections/item.js var CollectionItem2 = class _CollectionItem { static version = 2e-3; /** * Default properties for an instance of CollectionItem. * Override in subclasses to define different defaults. * @returns {Object} */ static get defaults() { return { data: {} }; } /** * @param {Object} env - The environment/context. * @param {Object|null} [data=null] - Initial data for the item. */ constructor(env, data = null) { env.create_env_getter(this); this.config = this.env?.config; this.merge_defaults(); if (data) deep_merge2(this.data, data); if (!this.data.class_name) this.data.class_name = this.collection.item_class_name; } /** * Loads an item from data and initializes it. * @param {Object} env * @param {Object} data * @returns {CollectionItem} */ static load(env, data) { const item = new this(env, data); item.init(); return item; } /** * Merge default properties from the entire inheritance chain. * @private */ merge_defaults() { let current_class = this.constructor; while (current_class) { for (let key in current_class.defaults) { const default_val = current_class.defaults[key]; if (typeof default_val === "object") { this[key] = { ...default_val, ...this[key] }; } else { this[key] = this[key] === void 0 ? default_val : this[key]; } } current_class = Object.getPrototypeOf(current_class); } } /** * Generates or retrieves a unique key for the item. * Key syntax supports: * - `[i]` for sequences * - `/` for super-sources (groups, directories, clusters) * - `#` for sub-sources (blocks) * @returns {string} The unique key */ get_key() { return create_uid2(this.data); } /** * Updates the item data and returns true if changed. * @param {Object} data * @returns {boolean} True if data changed. */ update_data(data) { const sanitized_data = this.sanitize_data(data); const current_data = { ...this.data }; deep_merge2(current_data, sanitized_data); const changed = !deep_equal2(this.data, current_data); if (!changed) return false; this.data = current_data; return true; } /** * Sanitizes data for saving. Ensures no circular references. * @param {*} data * @returns {*} Sanitized data. */ sanitize_data(data) { if (data instanceof _CollectionItem) return data.ref; if (Array.isArray(data)) return data.map((val) => this.sanitize_data(val)); if (typeof data === "object" && data !== null) { return Object.keys(data).reduce((acc, key) => { acc[key] = this.sanitize_data(data[key]); return acc; }, {}); } return data; } /** * Initializes the item. Override as needed. * @param {Object} [input_data] - Additional data that might be provided on creation. */ init(input_data) { } /** * Queues this item for saving. */ queue_save() { this._queue_save = true; } /** * Saves this item using its data adapter. * @returns {Promise} */ async save() { try { await this.data_adapter.save_item(this); this.init(); } catch (err) { this._queue_save = true; console.error(err, err.stack); } } /** * Queues this item for loading. */ queue_load() { this._queue_load = true; } /** * Loads this item using its data adapter. * @returns {Promise} */ async load() { try { await this.data_adapter.load_item(this); this.init(); } catch (err) { this._load_error = err; this.on_load_error(err); } } /** * Handles load errors by re-queuing for load. * Override if needed. * @param {Error} err */ on_load_error(err) { this.queue_load(); } /** * Validates the item before saving. Checks for presence and validity of key. * @deprecated should be better handled 2025-12-17 (wrong scope?) * @returns {boolean} */ validate_save() { if (!this.key) return false; if (this.key.trim() === "") return false; if (this.key === "undefined") return false; return true; } /** * Marks this item as deleted. This does not immediately remove it from memory, * but queues a save that will result in the item being removed from persistent storage. */ delete() { this.deleted = true; this.queue_save(); } /** * Filters items in the collection based on provided options. * functional filter (returns true or false) for filtering items in collection; called by collection class * @param {Object} filter_opts - Filtering options. * @param {string} [filter_opts.exclude_key] - A single key to exclude. * @param {string[]} [filter_opts.exclude_keys] - An array of keys to exclude. If exclude_key is provided, it's added to this array. * @param {string} [filter_opts.exclude_key_starts_with] - Exclude keys starting with this string. * @param {string[]} [filter_opts.exclude_key_starts_with_any] - Exclude keys starting with any of these strings. * @param {string} [filter_opts.exclude_key_includes] - Exclude keys that include this string. * @param {string[]} [filter_opts.exclude_key_includes_any] - Exclude keys that include any of these strings. * @param {string} [filter_opts.exclude_key_ends_with] - Exclude keys ending with this string. * @param {string[]} [filter_opts.exclude_key_ends_with_any] - Exclude keys ending with any of these strings. * @param {string} [filter_opts.key_ends_with] - Include only keys ending with this string. * @param {string} [filter_opts.key_starts_with] - Include only keys starting with this string. * @param {string[]} [filter_opts.key_starts_with_any] - Include only keys starting with any of these strings. * @param {string} [filter_opts.key_includes] - Include only keys that include this string. * @returns {boolean} True if the item passes the filter, false otherwise. */ filter(filter_opts = {}) { const { exclude_key, exclude_keys = exclude_key ? [exclude_key] : [], exclude_key_starts_with, exclude_key_starts_with_any, exclude_key_includes, exclude_key_includes_any, exclude_key_ends_with, exclude_key_ends_with_any, key_ends_with, key_starts_with, key_starts_with_any, key_includes, key_includes_any } = filter_opts; if (exclude_keys?.includes(this.key)) return false; if (exclude_key_starts_with && this.key.startsWith(exclude_key_starts_with)) return false; if (exclude_key_starts_with_any && exclude_key_starts_with_any.some((prefix) => this.key.startsWith(prefix))) return false; if (exclude_key_includes && this.key.includes(exclude_key_includes)) return false; if (exclude_key_includes_any && exclude_key_includes_any.some((include) => this.key.includes(include))) return false; if (exclude_key_ends_with && this.key.endsWith(exclude_key_ends_with)) return false; if (exclude_key_ends_with_any && exclude_key_ends_with_any.some((suffix) => this.key.endsWith(suffix))) return false; if (key_ends_with && !this.key.endsWith(key_ends_with)) return false; if (key_starts_with && !this.key.startsWith(key_starts_with)) return false; if (key_starts_with_any && !key_starts_with_any.some((prefix) => this.key.startsWith(prefix))) return false; if (key_includes && !this.key.includes(key_includes)) return false; if (key_includes_any && !key_includes_any.some((include) => this.key.includes(include))) return false; return true; } filter_and_score(params = {}) { if (this.filter(params.filter) === false) return null; return this.score(params); } score(params = {}) { const score_action = this.actions[params.score_algo_key]; if (typeof score_action !== "function") throw new Error(`Missing score action: ${params.score_algo_key}`); return { ...score_action(params) || {}, item: this }; } /** * Parses item data for additional processing. Override as needed. * @deprecated is this used anywhere? */ parse() { } get actions() { if (!this._actions) { this._actions = create_actions_proxy2(this, { ...this.env.config.actions || {}, // main actions scope for actions/ exports ...this.env.opts.items?.[this.item_type_key]?.actions || {} // DEPRECATED OR KEEP? }); } return this._actions; } /** * Derives the collection key from the class name. * @returns {string} */ static get collection_key() { let name = this.name; if (name.match(/\d$/)) name = name.slice(0, -1); return collection_instance_name_from2(name); } /** * @returns {string} The collection key for this item. */ get collection_key() { let name = this.constructor.name; if (name.match(/\d$/)) name = name.slice(0, -1); return collection_instance_name_from2(name); } /** * Retrieves the parent collection from the environment. * @returns {Collection} */ get collection() { return this.env[this.collection_key]; } /** * @returns {string} The item's key. */ get key() { return this.data?.key || this.get_key(); } get item_type_key() { let name = this.constructor.name; if (name.match(/\d$/)) name = name.slice(0, -1); return camel_case_to_snake_case2(name); } /** * Emits an event with item metadata. * * @param {string} event_key * @param {Object} [payload={}] * @returns {void} */ emit_event(event_key, payload = {}) { this.env.events?.emit(event_key, { collection_key: this.collection_key, item_key: this.key, ...payload }); } on_event(event_key, callback) { return this.env.events?.on(event_key, (payload) => { if (payload?.item_key && payload.item_key !== this.key) return; callback(payload); }); } once_event(event_key, callback) { return this.env.events?.once(event_key, (payload) => { if (payload?.item_key && payload.item_key !== this.key) return; callback(payload); }); } /** * @returns {Object} The data adapter for this item's collection. */ get data_adapter() { return this.collection.data_adapter; } /** * @returns {Object} The filesystem adapter. */ get data_fs() { return this.collection.data_fs; } /** * Access to collection-level settings. * @returns {Object} */ get settings() { if (!this.env.settings[this.collection_key]) this.env.settings[this.collection_key] = {}; return this.env.settings[this.collection_key]; } set settings(settings) { this.env.settings[this.collection_key] = settings; this.env.smart_settings.save(); } /** * A simple reference object for this item. * @deprecated 2025-11-11 lacks adoption * @returns {{collection_key: string, key: string}} */ get ref() { return { collection_key: this.collection_key, key: this.key }; } /** * @deprecated use env.smart_components~~env.smart_view~~ instead */ get smart_view() { if (!this._smart_view) this._smart_view = this.env.init_module("smart_view"); return this._smart_view; } /** * Retrieves the display name of the collection item. * @readonly * @deprecated Use `get_item_display_name(key, show_full_path)` instead (keep UI logic out of collections). * @returns {string} The display name. */ get name() { return get_item_display_name2( this.key, this.env.settings.smart_view_filter?.show_full_path ); } }; // node_modules/smart-collections/collection.js var AsyncFunction2 = Object.getPrototypeOf(async function() { }).constructor; var Collection2 = class { static version = 1e-3; /** * Constructs a new Collection instance. * * @param {Object} env - The environment context containing configurations and adapters. * @param {Object} [opts={}] - Optional configuration. * @param {string} [opts.collection_key] - Custom key to override default collection name. * @param {string} [opts.data_dir] - Custom data directory path. */ constructor(env, opts = {}) { env.create_env_getter(this); this.opts = opts; if (opts.collection_key) this.collection_key = opts.collection_key; this.env[this.collection_key] = this; this.config = this.env.config; this.items = {}; this.loaded = null; this._loading = false; this.load_time_ms = null; this.settings_container = null; } /** * Initializes a new collection in the environment. Override in subclass if needed. * * @param {Object} env * @param {Object} [opts={}] * @returns {Promise} */ static async init(env, opts = {}) { env[this.collection_key] = new this(env, opts); await env[this.collection_key].init(); env.collections[this.collection_key] = "init"; } /** * The unique collection key derived from the class name. * @returns {string} */ static get collection_key() { let name = this.name; if (name.match(/\d$/)) name = name.slice(0, -1); return name.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase(); } /** * Instance-level init. Override in subclasses if necessary. * @returns {Promise} */ async init() { } /** * Creates or updates an item in the collection. * - If `data` includes a key that matches an existing item, that item is updated. * - Otherwise, a new item is created. * After updating or creating, the item is validated. If validation fails, the item is logged and returned without being saved. * If validation succeeds for a new item, it is added to the collection and marked for saving. * * If the item’s `init()` method is async, a promise is returned that resolves once init completes. * * NOTE: wrapping in try/catch seems to fail to catch errors thrown in async init functions when awaiting create_or_update * * @param {Object} [data={}] - Data for creating/updating an item. * @returns {Promise|Item} The created or updated item. May return a promise if `init()` is async. */ create_or_update(data = {}) { const existing_item = this.find_by(data); const item = existing_item ? existing_item : new this.item_type(this.env); item._queue_save = !existing_item; const data_changed = item.update_data(data); if (!existing_item && !item.validate_save()) { return item; } if (!existing_item) { this.set(item); } if (existing_item && !data_changed) return existing_item; if (item.init instanceof AsyncFunction2) { return new Promise((resolve) => { item.init(data).then(() => resolve(item)); }); } item.init(data); return item; } /** * Finds an item by partial data match (first checks key). If `data.key` provided, * returns the item with that key; otherwise attempts a match by merging data. * * @param {Object} data - Data to match against. * @returns {Item|null} */ find_by(data) { if (data.key) return this.get(data.key); const temp = new this.item_type(this.env); const temp_data = JSON.parse(JSON.stringify(data, temp.sanitize_data(data))); deep_merge2(temp.data, temp_data); return temp.key ? this.get(temp.key) : null; } /** * Filters items based on provided filter options or a custom function. * * @param {Object|Function} [filter_opts={}] - Filter options or a predicate function. * @returns {Item[]} Array of filtered items. */ filter(filter_opts = {}) { if (typeof filter_opts === "function") { return Object.values(this.items).filter(filter_opts); } const results = []; const { first_n } = filter_opts; for (const item of Object.values(this.items)) { if (first_n && results.length >= first_n) break; if (item.filter(filter_opts)) results.push(item); } return results; } /** * Alias for `filter()` * @param {Object|Function} filter_opts * @returns {Item[]} */ list(filter_opts) { return this.filter(filter_opts); } /** * Retrieves an item by key. * @param {string} key * @returns {Item|undefined} */ get(key) { return this.items[key]; } /** * Retrieves multiple items by an array of keys. * @param {string[]} keys * @returns {Item[]} */ get_many(keys = []) { if (!Array.isArray(keys)) { console.error("get_many called with non-array keys:", keys); return []; } return keys.map((key) => this.get(key)).filter(Boolean); } /** * Retrieves a random item from the collection, optionally filtered by options. * @param {Object} [opts] * @returns {Item|undefined} */ get_rand(opts = null) { if (opts) { const filtered = this.filter(opts); return filtered[Math.floor(Math.random() * filtered.length)]; } const keys = this.keys; return this.items[keys[Math.floor(Math.random() * keys.length)]]; } /** * Adds or updates an item in the collection. * @param {Item} item */ set(item) { if (!item.key) throw new Error("Item must have a key property"); this.items[item.key] = item; } /** * Updates multiple items by their keys. * @param {string[]} keys * @param {Object} data */ update_many(keys = [], data = {}) { this.get_many(keys).forEach((item) => item.update_data(data)); } /** * Clears all items from the collection. */ clear() { this.items = {}; } /** * @returns {string} The collection key, can be overridden by opts.collection_key */ get collection_key() { return this._collection_key ? this._collection_key : this.constructor.collection_key; } set collection_key(key) { this._collection_key = key; } /** * Lazily initializes and returns the data adapter instance for this collection. * @returns {Object} The data adapter instance. */ get data_adapter() { if (!this._data_adapter) { const AdapterClass = this.get_adapter_class("data"); this._data_adapter = new AdapterClass(this); } return this._data_adapter; } get_adapter_class(type) { const config = this.env.opts.collections?.[this.collection_key]; const adapter_key = type + "_adapter"; const adapter_module = config?.[adapter_key] ?? this.env.opts.collections?.smart_collections?.[adapter_key]; if (typeof adapter_module === "function") return adapter_module; if (typeof adapter_module?.collection === "function") return adapter_module.collection; throw new Error(`No '${type}' adapter class found for ${this.collection_key} or smart_collections`); } /** * Data directory strategy for this collection. Defaults to 'multi'. * @deprecated should be handled in adapters (2025-12-09) * @returns {string} */ get data_dir() { return this.collection_key; } /** * File system adapter from the environment. * @returns {Object} */ get data_fs() { return this.env.data_fs; } /** * Derives the corresponding item class name based on this collection's class name. * @returns {string} */ get item_class_name() { let name = this.constructor.name; if (name.match(/\d$/)) name = name.slice(0, -1); if (name.endsWith("ies")) return name.slice(0, -3) + "y"; else if (name.endsWith("s")) return name.slice(0, -1); return name + "Item"; } /** * Derives a readable item name from the item class name. * @returns {string} */ get item_name() { return this.item_class_name.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase(); } /** * Retrieves the item type (constructor) from the environment. * @deprecated replace with item_class with strict adherence to conventions (2025-10-28) * @returns {Function} Item constructor. */ get item_type() { if (!this._item_type) this._item_type = this.resolve_item_type(); return this._item_type; } // TEMP resolver (2025-11-03): until better handled on merging configs at obsidian-smart-env startup resolve_item_type() { const available = [ this.env.config?.items?.[this.item_name], this.opts.item_type, this.env.item_types?.[this.item_class_name] ].filter(Boolean).sort((a, b) => { const a_version = a?.class?.version || a.version || 0; const b_version = b?.class?.version || b.version || 0; return b_version - a_version; }); if (available.length === 0) { throw new Error(`No item_type found for collection '${this.collection_key}' with item_name '${this.item_name}' or class_name '${this.item_class_name}'`); } return available[0].class || available[0]; } /** * Returns an array of all keys in the collection. * @returns {string[]} */ get keys() { return Object.keys(this.items); } /** * @deprecated use data_adapter instead (2024-09-14) */ get adapter() { return this.data_adapter; } /** * @method process_save_queue * @description * Saves items flagged for saving (_queue_save) back to AJSON or SQLite. This ensures persistent storage * of any updates made since last load/import. This method also writes changes to disk (AJSON files or DB). */ async process_save_queue(opts = {}) { if (opts.force) { Object.values(this.items).forEach((item) => item._queue_save = true); } await this.data_adapter.process_save_queue(opts); } /** * @alias process_save_queue * @returns {Promise} */ async save(opts = {}) { await this.process_save_queue(opts); } /** * @method process_load_queue * @description * Loads items that have been flagged for loading (_queue_load). This may involve * reading from AJSON/SQLite or re-importing from markdown if needed. * Called once initial environment is ready and collections are known. */ async process_load_queue() { await this.data_adapter.process_load_queue(); } /** * Retrieves processed settings configuration. * @returns {Object} */ get settings_config() { return this.process_settings_config({}); } /** * Processes given settings config, adding prefixes and handling conditionals. * @deprecated removing settings_config from collections (2025-11-24) * * @private * @param {Object} _settings_config * @param {string} [prefix=''] * @returns {Object} */ process_settings_config(_settings_config, prefix = "") { const add_prefix = (key) => prefix && !key.includes(`${prefix}.`) ? `${prefix}.${key}` : key; return Object.entries(_settings_config).reduce((acc, [key, val]) => { let new_val = { ...val }; if (new_val.conditional) { if (!new_val.conditional(this)) return acc; delete new_val.conditional; } if (new_val.callback) new_val.callback = add_prefix(new_val.callback); if (new_val.btn_callback) new_val.btn_callback = add_prefix(new_val.btn_callback); if (new_val.options_callback) new_val.options_callback = add_prefix(new_val.options_callback); const new_key = add_prefix(this.process_setting_key(key)); acc[new_key] = new_val; return acc; }, {}); } /** * Processes an individual setting key. Override if needed. * @param {string} key * @returns {string} */ process_setting_key(key) { return key; } /** * Default settings for this collection. Override in subclasses as needed. * @returns {Object} */ get default_settings() { return {}; } /** * Current settings for the collection. * Initializes with default settings if none exist. * @returns {Object} */ get settings() { if (!this.env.settings[this.collection_key]) { this.env.settings[this.collection_key] = this.default_settings; } return this.env.settings[this.collection_key]; } /** * Unloads collection data from memory. */ unload() { this.clear(); this.unloaded = true; this.env.collections[this.collection_key] = null; } /** * Displays a process notice if the operation exceeds one second. * * @param {string} process - Identifier for the ongoing process. * @param {Object} [opts={}] - Additional options passed to the notice. */ show_process_notice(process, opts = {}) { if (!this.debounce_process_notice) this.debounce_process_notice = {}; this.debounce_process_notice[process] = setTimeout(() => { this.debounce_process_notice[process] = null; this.env.notices?.show(process, { collection_key: this.collection_key, ...opts }); }, 1e3); } /** * Clears any pending process notice timers and removes active notices. * * @param {string} process - Identifier for the process notice to clear. */ clear_process_notice(process) { if (this.debounce_process_notice?.[process]) { clearTimeout(this.debounce_process_notice[process]); this.debounce_process_notice[process] = null; } else { this.env.notices?.remove(process); } } /** * Emits an event with collection metadata. * * @param {string} event_key * @param {Object} [payload={}] * @returns {void} */ emit_event(event_key, payload = {}) { this.env.events?.emit(event_key, { collection_key: this.collection_key, ...payload }); } on_event(event_key, callback) { return this.env.events?.on(event_key, (payload) => { if (payload?.collection_key && payload.collection_key !== this.collection_key) return; callback(payload); }); } /** * Lazily binds action functions to the collection instance. * * @returns {Object} Bound action functions keyed by name. */ get actions() { if (!this.constructor.key) this.constructor.key = this.collection_key; if (!this._actions) { const actions_modules = { ...this.env?.config?.actions || {}, ...this.env?.config?.collections?.[this.collection_key]?.actions || {}, ...this.env?.opts?.collections?.[this.collection_key]?.actions || {}, ...this.opts?.actions || {} }; this._actions = create_actions_proxy2(this, actions_modules); } return this._actions; } /** * Clears cached actions proxy and rebuilds on next access. * @returns {Object} Rebuilt proxy with latest source snapshot. */ refresh_actions() { this._actions = null; return this.actions; } // debounce running process save queue queue_save() { if (this._debounce_queue_save) clearTimeout(this._debounce_queue_save); this._debounce_queue_save = setTimeout(() => { this.process_save_queue(); }, 750); } // BEGIN DEPRECATED /** * @deprecated use env.smart_components~~env.smart_view~~ instead * @returns {Object} smart_view instance */ get smart_view() { if (!this._smart_view) this._smart_view = this.env.init_module("smart_view"); return this._smart_view; } /** * Renders the settings for the collection into a given container. * @deprecated use env.render_component('collection_settings', this) instead (2025-05-25: decouple UI from collections) * @param {HTMLElement} [container=this.settings_container] * @param {Object} opts * @returns {Promise} */ async render_settings(container = this.settings_container, opts = {}) { return await this.render_collection_settings(container, opts); } /** * Helper function to render collection settings. * @deprecated use env.render_component('collection_settings', this) instead (2025-05-25: decouple UI from collections) * @param {HTMLElement} [container=this.settings_container] * @param {Object} opts * @returns {Promise} */ async render_collection_settings(container = this.settings_container, opts = {}) { if (container && (!this.settings_container || this.settings_container !== container)) { this.settings_container = container; } else if (!container) { container = this.env.smart_view.create_doc_fragment("
    "); } this.env.smart_view.safe_inner_html(container, `
    Loading ${this.collection_key} settings...
    `); const frag = await this.env.render_component("settings", this, opts); this.env.smart_view.empty(container); container.appendChild(frag); return container; } }; // node_modules/smart-utils/results_acc.js function results_acc2(_acc, result, ct = 10) { if (_acc.results.size < ct) { _acc.results.add(result); if (_acc.results.size === ct && _acc.min === Number.POSITIVE_INFINITY) { let { minScore, minObj } = find_min2(_acc.results); _acc.min = minScore; _acc.minResult = minObj; } } else if (result.score > _acc.min) { _acc.results.add(result); _acc.results.delete(_acc.minResult); let { minScore, minObj } = find_min2(_acc.results); _acc.min = minScore; _acc.minResult = minObj; } } function find_min2(results) { let minScore = Number.POSITIVE_INFINITY; let minObj = null; for (const obj of results) { if (obj.score < minScore) { minScore = obj.score; minObj = obj; } } return { minScore, minObj }; } // node_modules/smart-utils/sort_by_score.js function sort_by_score2(a, b) { const epsilon = 1e-9; const score_diff = a.score - b.score; if (Math.abs(score_diff) < epsilon) return 0; return score_diff > 0 ? -1 : 1; } function sort_by_score_descending2(a, b) { return sort_by_score2(a, b); } // src/utils/merge_pinned_results.js function merge_pinned_results(base_results, params) { if (!params.pinned?.length) return base_results; const pinned_keys = new Set(params.pinned_keys || params.pinned.map((item) => item.key)); const pinned_results = params.pinned.map((item) => ({ item, ...item.score?.(params) || {} })); const filtered_results = base_results.filter((result) => { const key = result?.item?.key; return key ? !pinned_keys.has(key) : true; }); return [...pinned_results, ...filtered_results]; } // migrations/migrate_hidden_connections.js var BLOCK_KEY_MARKER = "#"; var COLLECTION_SEPARATOR = ":"; var SOURCE_COLLECTION = "smart_sources"; var BLOCK_COLLECTION = "smart_blocks"; function is_plain_object5(value) { return Boolean(value) && typeof value === "object" && !Array.isArray(value); } function select_hidden_entries(hidden_connections) { if (!is_plain_object5(hidden_connections)) return []; return Object.entries(hidden_connections).filter(([, timestamp]) => timestamp !== void 0 && timestamp !== null); } function get_collection_prefix(key) { return key.includes(BLOCK_KEY_MARKER) ? BLOCK_COLLECTION : SOURCE_COLLECTION; } function ensure_prefixed_key(key) { if (typeof key !== "string") return key; if (key.includes(COLLECTION_SEPARATOR)) return key; return `${get_collection_prefix(key)}${COLLECTION_SEPARATOR}${key}`; } function migrate_hidden_connections(source) { const hidden_entries = select_hidden_entries(source?.data?.hidden_connections); if (!hidden_entries.length) return false; if (!is_plain_object5(source.data.connections)) { source.data.connections = {}; } let migrated = false; hidden_entries.forEach(([key, timestamp]) => { const prefixed_key = ensure_prefixed_key(key); if (typeof prefixed_key !== "string") return; const connection_state = source.data.connections[prefixed_key] || {}; if (connection_state.hidden === void 0 || connection_state.hidden === null) { connection_state.hidden = timestamp; migrated = true; } source.data.connections[prefixed_key] = connection_state; }); if (migrated) { delete source.data.hidden_connections; } return migrated || hidden_entries.length > 0; } // src/items/connections_list.js var ConnectionsList = class extends CollectionItem2 { static key = "connections_list"; static get defaults() { return { data: {} }; } get_key() { return `${this.data.collection_key}:${this.data.item_key}`; } async pre_process(params) { migrate_hidden_connections(this.item); if (typeof this.actions.connections_list_pre_process === "function") { await this.actions.connections_list_pre_process(params); } if (typeof this.env.config?.actions?.[params.score_algo_key]?.pre_process === "function") { await this.env.config.actions[params.score_algo_key].pre_process.call(this.item, params); } } /** * Produce ranked connections for the current source item. * @param {object} params * @returns {Promise} */ async get_results(params = {}) { await this.pre_process(params); let results = this.filter_and_score(params); results = await this.post_process(results, params); results = merge_pinned_results(results, params); results = results.map((r) => Object.assign(r, { connections_list: this })); this.results = results; return results; } filter_and_score(params = {}) { const collection = this.env[params.results_collection_key]; const score_errors = []; const { results: raw_results } = Object.values(collection.items).reduce((acc, target) => { const scored = target.filter_and_score(params); if (!scored?.score) { if (scored?.error) score_errors.push(scored.error); return acc; } results_acc2(acc, scored, params.limit); return acc; }, { min: 0, results: /* @__PURE__ */ new Set() }); const results = Array.from(raw_results).sort(sort_by_score_descending2); if (!results.length) return results; while (!results.some((r) => r.score > 0.5)) { results.forEach((r) => r.score *= 2); } return results; } async post_process(results, params = {}) { if (!results?.length) { console.warn("No results to post-process, received:", results); return []; } const action_key = this.settings.connections_post_process; const post_process_action = this.actions[action_key]; let processed_results = results; if (typeof post_process_action === "function") { const response = await post_process_action(results, params); if (Array.isArray(response)) { processed_results = response.filter(Boolean); if (!processed_results.length) processed_results = results; } else if (response !== void 0 && response !== null) { console.warn(`connections post_process '${action_key}' returned non-array`, response); } } else if (action_key && action_key !== "none") { console.warn(`Post-process action "${action_key}" not found, falling back to base results.`); } return processed_results; } get item() { return this.env[this.data.collection_key]?.items[this.data.item_key]; } get connections_list_component_key() { const stored_key = this.data.connections_list_component_key || this.settings?.connections_list_component_key; if (this.env.config.components[stored_key]) return stored_key; return "connections_list_v3"; } }; // migrations/migrate_connections_lists_settings.js var MIGRATED_SETTING_KEYS = [ "inline_connections", "inline_connections_score_threshold", "footer_connections" ]; function migrate_connections_lists_settings(env) { const settings = env?.settings; if (!settings) return false; const legacy = settings.connections_pro; if (!legacy) return false; const target = settings.connections_lists ||= {}; let migrated = false; for (const key of MIGRATED_SETTING_KEYS) { if (!(key in legacy)) continue; if (!(key in target)) { target[key] = legacy[key]; migrated = true; } delete legacy[key]; } if ("rank_model" in legacy) { if (!target.actions) target.actions = {}; if (!("rank_connections" in target.actions)) { target.actions.rank_connections = legacy.rank_model; migrated = true; } delete legacy.rank_model; } return migrated; } // src/collections/connections_lists.js var ConnectionsLists = class extends Collection2 { static version = 1; process_load_queue() { } // no persisting data (for now) constructor(env, opts = {}) { migrate_connections_lists_settings(env); super(env, opts); } static get default_settings() { return { results_collection_key: "smart_sources", score_algo_key: "similarity", connections_post_process: "none", results_limit: 20, exclude_frontmatter_blocks: true, connections_list_item_component_key: "connections_list_item_v3", components: { connections_list_item_v3: { render_markdown: true, show_full_path: false } } }; } get settings_config() { return settings_config10(this); } new_item(item) { const connections_list = new this.item_type(this.env, { collection_key: item.collection_key, item_key: item.key }); this.set(connections_list); Object.defineProperty(item, "connections", { get: () => connections_list, configurable: true }); return connections_list; } get_connections_list_item_options() { return Object.entries(this.env.config.components || {}).filter(([key, fn]) => key.startsWith("connections_list_item_")).map(([value, fn]) => ({ value, name: fn.display_name || value, description: fn.display_description })); } get score_algo_key() { const stored_key = this.settings?.score_algo_key; if (this.env.config?.actions?.[stored_key]) return stored_key; return "similarity"; } get results_collection_key() { const stored_key = this.settings?.results_collection_key; if (this.env[stored_key]) return stored_key; return "smart_sources"; } }; function settings_config10(scope) { const config = { "results_collection_key": { name: "Connection results type", type: "dropdown", description: "Choose whether results should be sources or blocks.", option_1: "smart_sources|Sources", option_2: "smart_blocks|Blocks", options_callback: () => { const options = [ { value: "smart_sources", name: "Sources" } ]; if (scope.env.smart_blocks) { options.push({ value: "smart_blocks", name: "Blocks" }); } return options; } }, "results_limit": { name: "Results limit", type: "number", description: "Adjust the number of connections displayed in the connections view (default 20)." } }; if (!scope.env.smart_blocks.settings.embed_blocks) { config.results_collection_key = { type: "html", value: create_settings_section_heading("Connection results type", 'Enable "Embed blocks" in Smart Blocks settings to use block connections.'), name: "Connection results type" }; } return config; } var connections_lists_default = { class: ConnectionsLists, collection_key: "connections_lists", item_type: ConnectionsList, settings_config: settings_config10 }; // src/components/connections_codeblock.css var connections_codeblock_default = ".connections-codeblock {\r\n .connections-top-bar {\r\n display: flex;\r\n gap: 0.5rem 1rem;\r\n\r\n .connections-actions {\r\n display: flex;\r\n flex-direction: row;\r\n flex-wrap: wrap;\r\n align-items: center;\r\n gap: 0.5rem;\r\n span {\r\n font-weight: 600;\r\n justify-self: end;\r\n }\r\n }\r\n }\r\n .connections-list {\r\n padding-top: 0.85rem;\r\n\r\n > .sc-collapsed ul {\r\n display: none;\r\n }\r\n > .sc-collapsed span svg {\r\n transform: rotate(-90deg);\r\n }\r\n > .sc-result-pinned {\r\n box-shadow: 0 0 0 1px var(--interactive-accent);\r\n border-radius: var(--radius-s);\r\n background-color: var(--background-modifier-hover);\r\n }\r\n }\r\n}"; // src/components/connections_codeblock.js var import_obsidian45 = require("obsidian"); // node_modules/obsidian-smart-env/src/modals/story.js var import_obsidian44 = require("obsidian"); // node_modules/obsidian-smart-env/utils/open_url_externally.js function open_url_externally(plugin, url) { const webviewer = plugin.app.internalPlugins?.plugins?.webviewer?.instance; window.open(url, webviewer ? "_external" : "_blank"); } // node_modules/obsidian-smart-env/src/modals/story.js var StoryModal = class _StoryModal extends import_obsidian44.Modal { constructor(plugin, { title, url }) { super(plugin.app); this.plugin = plugin; this.title = title; this.url = url; } static open(plugin, story_url) { const modal = new _StoryModal(plugin, story_url); modal.open(); } onOpen() { this.titleEl.setText(this.title); this.modalEl.addClass("sc-story-modal"); const container = this.contentEl.createEl("div", { cls: "sc-story-container" }); if (import_obsidian44.Platform.isMobile) { const btn = container.createEl("button", { text: "Open in browser" }); btn.addEventListener("click", () => { open_url_externally(this.plugin, this.url); this.close(); }); return; } else { const webview = container.createEl("webview", { attr: { src: this.url, allowpopups: "" } }); webview.style.width = "100%"; webview.style.height = "100%"; webview.addEventListener("did-navigate", (event) => { const new_url = event.url || webview.getAttribute("src"); if (new_url && new_url !== this.url) { open_url_externally(this.plugin, new_url); this.close(); } }); } } onClose() { this.contentEl.empty(); } }; // src/utils/format_connections_as_links.js function format_connections_as_links(results = []) { if (!Array.isArray(results) || !results.length) return ""; return results.map(({ item }) => { let lines; if (item.key.endsWith("}")) { lines = `Lines: ${item.lines.join("-")}`; } const link = get_wikilink(item); if (!link) return ""; if (lines) { return `${link.replace(/\#\{\d+\}/, "")} (${lines})`; } return link; }).filter(Boolean).join("\n"); } function get_wikilink(item) { if (!item?.key) return ""; const [source_key, ...block_parts] = item.key.split("#"); const filename = source_key.split("/").pop().replace(/\.md$/i, ""); if (block_parts.length) { const block_ref = block_parts.pop(); return `- [[${filename}#${block_ref}]]`; } return `- [[${filename}]]`; } // src/utils/connections_list_item_state.js function build_prefixed_connection_key(collection_key, item_key) { if (typeof item_key !== "string" || !item_key.length) return item_key; if (item_key.includes(":")) return item_key; if (typeof collection_key !== "string" || !collection_key.length) return item_key; return `${collection_key}:${item_key}`; } function apply_hidden_state(connections, prefixed_key, hidden_at = Date.now()) { if (!connections || typeof connections !== "object") return connections; if (typeof prefixed_key !== "string" || !prefixed_key.length) return connections; const state = connections[prefixed_key] || {}; state.hidden = hidden_at; connections[prefixed_key] = state; return connections; } function apply_pinned_state(connections, prefixed_key, pinned_at = Date.now()) { if (!connections || typeof connections !== "object") return connections; if (typeof prefixed_key !== "string" || !prefixed_key.length) return connections; const state = connections[prefixed_key] || {}; state.pinned = pinned_at; connections[prefixed_key] = state; return connections; } function remove_pinned_state(connections, prefixed_key) { if (!connections || typeof connections !== "object") return connections; if (typeof prefixed_key !== "string" || !prefixed_key.length) return connections; const state = connections[prefixed_key]; if (!state || typeof state !== "object") return connections; delete state.pinned; if (!Object.keys(state).length) delete connections[prefixed_key]; return connections; } function remove_all_hidden_states(connections) { if (!connections || typeof connections !== "object") return false; let changed = false; Object.entries(connections).forEach(([key, state]) => { if (!state || typeof state !== "object") return; if (state.hidden === void 0 || state.hidden === null) return; delete state.hidden; if (!Object.keys(state).length) delete connections[key]; changed = true; }); return changed; } function remove_all_pinned_states(connections) { if (!connections || typeof connections !== "object") return false; let changed = false; Object.entries(connections).forEach(([key, state]) => { if (!state || typeof state !== "object") return; if (state.pinned === void 0 || state.pinned === null) return; delete state.pinned; if (!Object.keys(state).length) delete connections[key]; changed = true; }); return changed; } function count_hidden_connections(connections) { if (!connections || typeof connections !== "object") return 0; return Object.values(connections).reduce((count, state) => { if (state && typeof state === "object" && state.hidden !== void 0 && state.hidden !== null) { return count + 1; } return count; }, 0); } function count_pinned_connections(connections) { if (!connections || typeof connections !== "object") return 0; return Object.values(connections).reduce((count, state) => { if (state && typeof state === "object" && state.pinned !== void 0 && state.pinned !== null) { return count + 1; } return count; }, 0); } function is_connection_pinned(connections, prefixed_key) { if (!connections || typeof connections !== "object") return false; if (typeof prefixed_key !== "string" || !prefixed_key.length) return false; const state = connections[prefixed_key]; if (!state || typeof state !== "object") return false; return state.pinned !== void 0 && state.pinned !== null; } function is_connection_hidden(connections, prefixed_key) { if (!connections || typeof connections !== "object") return false; if (typeof prefixed_key !== "string" || !prefixed_key.length) return false; const state = connections[prefixed_key]; if (!state || typeof state !== "object") return false; return state.hidden !== void 0 && state.hidden !== null; } // src/utils/filter_hidden_results.js function filter_hidden_results(results = [], connections_state = {}) { if (!Array.isArray(results) || !results.length) return []; if (!connections_state || typeof connections_state !== "object") return results; return results.filter((result) => { const item = result?.item; if (!item) return false; const prefixed_key = build_prefixed_connection_key(item.collection_key, item.key); const state = connections_state[prefixed_key]; if (!state || typeof state !== "object") return true; const hidden = state.hidden !== void 0 && state.hidden !== null; const pinned = state.pinned !== void 0 && state.pinned !== null; return !(hidden && !pinned); }); } // src/components/connections_codeblock.js async function build_html26(connections_list, opts = {}) { const top_bar_buttons = [ { title: "Refresh connections", icon: "refresh-cw", attrs: 'data-action="refresh-connections"' }, { title: "Expand all", icon: "unfold-vertical", attrs: 'data-action="expand-all"' }, { title: "Collapse all", icon: "fold-vertical", attrs: 'data-action="collapse-all"' }, { title: "Send results to Smart Context", icon: "briefcase", attrs: 'data-action="send-to-smart-context"' }, { title: "Copy as list of links", icon: "copy", attrs: 'data-action="copy-as-links"' }, { title: "Connections settings", icon: "settings", attrs: 'data-action="open-settings"' }, { title: "Help & getting started", icon: "help-circle", attrs: 'data-action="open-help"' } ].map((btn) => ` `).join(""); const html = `
    ${top_bar_buttons} Smart Connections
    `; return html; } async function render26(connections_list, opts = {}) { const html = await build_html26.call(this, connections_list, opts); const frag = this.create_doc_fragment(html); this.apply_style_sheet(connections_codeblock_default); const container = frag.firstElementChild; post_process25.call(this, connections_list, container, opts); return frag; } async function post_process25(connections_list, container, opts = {}) { const list_container = container.querySelector(".connections-list-container"); const env = connections_list.env; const app = env.plugin.app || window.app; const render_list = async () => { console.log("Rendering connections list in codeblock view"); const connections_list_component_key = opts.connections_list_component_key || connections_list.connections_list_component_key || "connections_list_v3"; const list = await env.smart_components.render_component( connections_list_component_key, connections_list, opts ); this.empty(list_container); list_container.appendChild(list); }; if (!container._has_listeners) { container._has_listeners = true; const refresh_button = container.querySelector('[data-action="refresh-connections"]'); refresh_button?.addEventListener("click", async () => { const refresh_entity = connections_list.item; if (refresh_entity) { await refresh_entity.read(); refresh_entity.queue_import(); await refresh_entity.collection.process_source_import_queue?.(); render_list(); } else { console.warn("No entity found for refresh"); } }); const expand_all_button = container.querySelector('[data-action="expand-all"]'); expand_all_button?.addEventListener("click", () => { container.querySelectorAll(".sc-result").forEach((elm) => { elm.classList.remove("sc-collapsed"); }); }); const collapse_all_button = container.querySelector('[data-action="collapse-all"]'); collapse_all_button?.addEventListener("click", () => { container.querySelectorAll(".sc-result").forEach((elm) => { elm.classList.add("sc-collapsed"); }); }); const context_button = container.querySelector('[data-action="send-to-smart-context"]'); context_button?.addEventListener("click", async () => { const raw_results = await get_results_fallback(connections_list, opts); if (!raw_results.length) return new import_obsidian45.Notice("No connection results to send to Smart Context"); const connections_state = connections_list?.item?.data?.connections || {}; const visible_results = filter_hidden_results(raw_results, connections_state); if (!visible_results.length) return new import_obsidian45.Notice("No visible connection results to send to Smart Context"); const smart_context = env.smart_contexts.new_context(); smart_context.add_items(visible_results.map((r) => ({ key: r.item.key, score: r.score }))); smart_context.emit_event("context_selector:open"); connections_list.emit_event("connections:sent_to_context"); }); const copy_links_button = container.querySelector('[data-action="copy-as-links"]'); copy_links_button?.addEventListener("click", async () => { const raw_results = await get_results_fallback(connections_list, opts); if (!raw_results.length) return new import_obsidian45.Notice("No connection results to copy"); const connections_state = connections_list?.item?.data?.connections || {}; const visible_results = filter_hidden_results(raw_results, connections_state); const links_payload = format_connections_as_links(visible_results); if (!links_payload) return new import_obsidian45.Notice("No visible connection results to copy"); await copy_to_clipboard2(links_payload); new import_obsidian45.Notice("Copied connections as list of links"); connections_list.emit_event("connections:copied_list"); }); const settings_button = container.querySelector('[data-action="open-settings"]'); settings_button?.addEventListener("click", () => { app.setting.open(); app.setting.openTabById("smart-connections"); }); const open_help = () => { StoryModal.open(env.plugin, { title: "Getting Started With Smart Connections", url: "https://smartconnections.app/story/smart-connections-getting-started/?utm_source=connections-view-help#page=understanding-connections-1" }); }; const help_button = container.querySelector('[data-action="open-help"]'); help_button?.addEventListener("click", open_help); } render_list(); return container; } async function get_results_fallback(connections_list, opts = {}) { const cached = Array.isArray(connections_list?.results) ? connections_list.results : []; if (cached.length) return cached; try { const results = await connections_list.get_results({ ...opts }); return Array.isArray(results) ? results : []; } catch (err) { console.error("Failed to fetch connections results", err); return []; } } // src/components/connections-list-item/v3.js var import_obsidian46 = require("obsidian"); // src/components/connections-list-item/v3.css var css_sheet = new CSSStyleSheet(); css_sheet.replaceSync(`a.sc-result-file-title { text-decoration: none !important; } a.sc-result-file-title > .sc-score { display: inline-block; width: 4.5ch; height: 1.7em; line-height: 1.7em; text-align: center; font-weight: 600 !important; font-size: 0.8em !important; color: var(--nav-item-color) !important; background: var(--background-modifier-hover); border-radius: 6px; } a.sc-result-file-title > .sc-path { margin-right: -3px; } a.sc-result-file-title > .sc-path, a.sc-result-file-title > .sc-title { font-weight: bold !important; text-decoration: underline; } a.sc-result-file-title > .sc-breadcrumb:not(.sc-path, .sc-title, .sc-score) { font-style: italic; } a.sc-result-file-title > .sc-breadcrumb-separator { color: color-mix(in srgb, var(--text-normal) 50%, transparent) !important; } .sc-result.sc-result-graph-focus { outline: 2px solid color-mix(in srgb, var(--interactive-accent) 70%, transparent); border-radius: var(--radius-s); box-shadow: 0 0 0 1px color-mix(in srgb, var(--background-primary) 80%, transparent), 0 0 0.5rem color-mix(in srgb, var(--interactive-accent) 45%, transparent); transition: outline 120ms ease, box-shadow 120ms ease; } `); var v3_default = css_sheet; // src/utils/get_item_display_name.js function get_item_display_name3(item, settings = {}) { if (!item?.key) return ""; const show_full_path = settings.show_full_path ?? true; if (show_full_path) { return item.key.replace(/#/g, " > ").replace(/\//g, " > "); } const pcs = []; const [source_key, ...block_parts] = item.key.split("#"); const filename = source_key.split("/").pop(); pcs.push(filename); if (block_parts.length) { pcs.push(block_parts.pop()); } return pcs.join(" > "); } // node_modules/obsidian-smart-env/utils/parse_item_key_to_wikilink.js function parse_item_key_to_wikilink(key) { if (!key) return ""; const [file_path, ...parts] = key.split("#"); const file_name = file_path.split("/").pop().replace(/\.md$/, ""); if (!parts.length) return `[[${file_name}]]`; const heading = parts.filter((part) => !part.startsWith("{")).pop(); if (!heading) return `[[${file_name}]]`; return `[[${file_name}#${heading}]]`; } // node_modules/obsidian-smart-env/src/utils/register_item_drag.js function handle_connection_drag(obsidian_app, item, params, event) { const drag_manager = obsidian_app.dragManager; const link_text = parse_item_key_to_wikilink(item.key); const drag_data = drag_manager.dragLink(event, link_text); drag_manager.onDragStart(event, drag_data); if (params.drag_event_key) { item.emit_event(params.drag_event_key); } else { item.emit_event("connections:drag_result"); } } function register_item_drag(container, item, params = {}) { const env = item.env; const app = env.obsidian_app; container.setAttribute("draggable", "true"); container.addEventListener("dragstart", handle_connection_drag.bind(null, app, item, params)); } // src/components/connections-list-item/v3.js async function build_html27(result, params = {}) { const item = result.item; const env = item.env; const score = result.score; const connections_settings = params.connections_settings ?? env.connections_lists.settings; const component_settings = connections_settings.components?.connections_list_item_v3 || {}; const header_html = get_result_header_html(score, item, component_settings); const all_expanded = connections_settings.expanded_view; return `
    ${this.get_icon_html("right-triangle")} ${header_html}
    `; } async function render27(result_scope, params = {}) { this.apply_style_sheet(v3_default); let html = await build_html27.call(this, result_scope, params); const frag = this.create_doc_fragment(html); const container = frag.querySelector(".sc-result"); post_process26.call(this, result_scope, container, params); return container; } async function post_process26(result_scope, container, params = {}) { const { item } = result_scope; const env = item.env; const plugin = env.smart_connections_plugin; const app = plugin.app; const connections_settings = params.connections_settings ?? env.connections_lists.settings; const component_settings = connections_settings.components?.connections_list_item_v3 || {}; const should_render_markdown = component_settings?.render_markdown ?? true; if (!should_render_markdown) container.classList.add("sc-result-plaintext"); const source_item = result_scope.connections_list?.item; const prefixed_key = build_prefixed_connection_key(item.collection_key, item.key); container.dataset.prefixedKey = prefixed_key; const connection_state = source_item?.data?.connections; if (is_connection_hidden(connection_state, prefixed_key)) { container.style.display = "none"; container.dataset.hidden = "true"; } if (is_connection_pinned(connection_state, prefixed_key)) { container.classList.add("sc-result-pinned"); container.dataset.pinned = "true"; } const render_result = async (_result_elm) => { if (!_result_elm.querySelector("li").innerHTML) { const collection_key = _result_elm.dataset.collection; const entity = env[collection_key].get(_result_elm.dataset.path); let markdown; if (should_render_embed(entity)) markdown = `${entity.embed_link} ${await entity.read()}`; else markdown = process_for_rendering(await entity.read()); let entity_frag; if (should_render_markdown) entity_frag = await this.render_markdown(markdown, entity); else entity_frag = this.create_doc_fragment(markdown); container.querySelector("li").appendChild(entity_frag); } }; const toggle_fold_elm = container.querySelector(".header .svg-icon.right-triangle"); toggle_fold_elm.addEventListener("click", toggle_result); const event_key_domain = params.event_key_domain || "connections"; const drag_event_key = `${event_key_domain}:drag_result`; register_item_drag(container, item, { drag_event_key }); register_item_hover_popover(container, item, { event_key_domain }); container.addEventListener("click", (event) => { open_source(item, event); item.emit_event(`${event_key_domain}:open_result`, { event_source: "connections-list-item-v3" }); }); const observer = new MutationObserver((mutations) => { const has_expansion_change = mutations.some((mutation) => { const target = mutation.target; return mutation.attributeName === "class" && mutation.oldValue?.includes("sc-collapsed") !== target.classList.contains("sc-collapsed"); }); if (has_expansion_change && !mutations[0].target.classList.contains("sc-collapsed")) { render_result(mutations[0].target); } }); observer.observe(container, { attributes: true, attributeOldValue: true, attributeFilter: ["class"] }); plugin.registerDomEvent(container, "contextmenu", (event) => { event.preventDefault(); event.stopPropagation(); if (!source_item) return; source_item.data.connections ||= {}; const prefixed_key2 = build_prefixed_connection_key( item.collection_key, item.key ); const pinned = is_connection_pinned(source_item.data.connections, prefixed_key2); const hidden_count = count_hidden_connections(source_item.data.connections); const pinned_count = count_pinned_connections(source_item.data.connections); const results = result_scope.connections_list?.results || []; const target_name = get_item_display_name3(item, component_settings) || item.key; console.log({ target_name, item }); const menu = new import_obsidian46.Menu(app); menu.addItem((menu_item) => { menu_item.setTitle(`Hide ${target_name}`).setIcon("eye-off").onClick(() => { try { apply_hidden_state(source_item.data.connections, prefixed_key2, Date.now()); if (source_item.data.hidden_connections) { delete source_item.data.hidden_connections[item.key]; if (!Object.keys(source_item.data.hidden_connections).length) delete source_item.data.hidden_connections; } source_item.queue_save(); container.style.display = "none"; container.dataset.hidden = "true"; source_item.collection.save(); } catch (err) { new import_obsidian46.Notice("Hide failed \u2013 check console"); console.error(err); } }); }); menu.addItem((menu_item) => { const title_prefix = pinned ? "Unpin" : "Pin"; menu_item.setTitle(`${title_prefix} ${target_name}`).setIcon(pinned ? "pin-off" : "pin").onClick(() => { try { if (pinned) { remove_pinned_state(source_item.data.connections, prefixed_key2); container.classList.remove("sc-result-pinned"); container.removeAttribute("data-pinned"); } else { apply_pinned_state(source_item.data.connections, prefixed_key2, Date.now()); container.classList.add("sc-result-pinned"); container.dataset.pinned = "true"; } source_item.queue_save(); source_item.collection.save(); } catch (err) { new import_obsidian46.Notice(`${title_prefix} failed \u2013 check console`); console.error(err); } }); }); menu.addSeparator(); const links_payload = format_connections_as_links(results); if (links_payload) { menu.addItem((menu_item) => { menu_item.setTitle("Copy as list of links").setIcon("copy").onClick(async () => { await copy_to_clipboard2(links_payload); new import_obsidian46.Notice("Connections links copied to clipboard"); result_scope.connections_list.emit_event("connections:copied_list"); }); }); } menu.addSeparator(); menu.addItem((menu_item) => { menu_item.setTitle(`Unhide All (${hidden_count})`).setIcon("eye").setDisabled(!hidden_count).onClick(() => { try { if (!source_item.data.connections) return; const changed = remove_all_hidden_states(source_item.data.connections); if (!changed) return; if (source_item.data.hidden_connections) delete source_item.data.hidden_connections; source_item.queue_save(); container.closest(".sc-connections-view")?.querySelector('[title="Refresh"]')?.click(); source_item.collection.save(); } catch (err) { new import_obsidian46.Notice("Unhide failed \u2013 check console"); console.error(err); } }); }); menu.addItem((menu_item) => { menu_item.setTitle(`Unpin All (${pinned_count})`).setIcon("pin-off").setDisabled(!pinned_count).onClick(() => { try { if (!source_item.data.connections) return; const changed = remove_all_pinned_states(source_item.data.connections); if (!changed) return; const list_root = container.closest(".connections-list"); list_root?.querySelectorAll(".sc-result[data-pinned]").forEach((result_el) => { result_el.classList.remove("sc-result-pinned"); result_el.removeAttribute("data-pinned"); }); source_item.queue_save(); source_item.collection.save(); } catch (err) { new import_obsidian46.Notice("Unpin failed \u2013 check console"); console.error(err); } }); }); menu.showAtMouseEvent(event); }); if (!container.classList.contains("sc-collapsed")) { render_result(container); } return container; } function get_result_header_html(score, item, component_settings = {}) { const raw_parts = get_item_display_name3(item, component_settings).split(" > ").filter(Boolean); const parts = format_item_parts(raw_parts, item?.lines); const name = parts.pop(); const formatted_score = typeof score === "number" ? score.toFixed(2) : ""; const separator = ' > '; const parts_html = parts.map((part) => `${part}`).join(separator); return [ `${formatted_score}`, `${parts_html}${separator}`, `${name}` ].join(""); } function format_item_parts(parts, lines = []) { if (!Array.isArray(parts) || !parts.length) return []; const has_line_marker = Array.isArray(lines) && lines.length; return parts.map((part) => { if (has_line_marker && part.startsWith("{")) { return `Lines: ${lines.join("-")}`; } return part; }); } function should_render_embed(entity) { if (!entity) return false; if (entity.is_media) return true; return false; } function process_for_rendering(content) { if (content.includes("```dataview")) content = content.replace(/```dataview/g, "```\\dataview"); if (content.includes("```smart-context")) content = content.replace(/```smart-context/g, "```\\smart-context"); if (content.includes("```smart-chatgpt")) content = content.replace(/```smart-chatgpt/g, "```\\smart-chatgpt"); if (content.includes("![[")) content = content.replace(/\!\[\[/g, "! [["); return content; } function toggle_result(event) { event.preventDefault(); event.stopPropagation(); const _result_elm = event.target.closest(".sc-result"); _result_elm.classList.toggle("sc-collapsed"); } var settings_config11 = { "show_full_path": { name: "Show full path", type: "toggle", description: "Turning on will include the folder path in the connections results.", default: true }, "render_markdown": { name: "Render markdown", type: "toggle", description: "Turn off to prevent rendering markdown and display connection results as plain text.", default: true } }; // src/components/connections-list/v3.js async function build_html28(connections_list, opts = {}) { return `
    `; } async function render28(connections_list, opts = {}) { const html = await build_html28.call(this, connections_list, opts); const frag = this.create_doc_fragment(html); const container = frag.querySelector(".connections-list"); post_process27.call(this, connections_list, container, opts); return container; } async function post_process27(connections_list, container, opts = {}) { container.dataset.key = connections_list.item.key; const results = await connections_list.get_results(opts); if (!results || !Array.isArray(results) || results.length === 0) { const no_results = this.create_doc_fragment(`

    No results found.
    Try using the refresh button. If that doesn't work, try running "Clear sources data" and then "Reload sources" in the Smart Environment settings.

    `); container.appendChild(no_results); return container; } const smart_components = connections_list.env.smart_components; const result_frags = await Promise.all(results.map((result) => { return smart_components.render_component("connections_list_item_v3", result, { ...opts }); })); result_frags.forEach((result_frag) => container.appendChild(result_frag)); return container; } var display_name4 = "List only"; // src/components/connections-settings/header.js var import_obsidian47 = require("obsidian"); async function build_html29(scope_plugin) { return `
    `; } async function render29(scope_plugin) { const html = await build_html29.call(this, scope_plugin); const frag = this.create_doc_fragment(html); const container = frag.firstElementChild; post_process28.call(this, scope_plugin, container); return container; } async function post_process28(scope_plugin, frag) { const user_agreement_container = frag.querySelector("[data-user-agreement]"); if (user_agreement_container) { const user_agreement = await scope_plugin.env.render_component( "user_agreement_callout", scope_plugin ); user_agreement_container.appendChild(user_agreement); } const header_link = frag.querySelector("#header-callout a"); if (header_link) { header_link.addEventListener("click", (e) => { e.preventDefault(); window.open(header_link.href, "_external"); }); } frag.querySelector(".sc-getting-started-button")?.addEventListener("click", () => { StoryModal.open(scope_plugin, { title: "Getting Started With Smart Connections", url: "https://smartconnections.app/story/smart-connections-getting-started/?utm_source=sc-op-settings" }); }); frag.querySelector(".sc-report-bug-button")?.addEventListener("click", () => { if (scope_plugin.env?.is_pro) { new ScProSupportModal(scope_plugin.app).open(); return; } window.open( "https://github.com/brianpetro/obsidian-smart-connections/issues/new?template=bug_report.yml", "_external" ); }); frag.querySelector(".sc-request-feature-button")?.addEventListener("click", () => { window.open( "https://github.com/brianpetro/obsidian-smart-connections/issues/new?template=feature_request.yml", "_external" ); }); frag.querySelector(".sc-share-workflow-button")?.addEventListener("click", () => { window.open( "https://github.com/brianpetro/obsidian-smart-connections/discussions/new?category=showcase", "_external" ); }); return frag; } var ScProSupportModal = class extends import_obsidian47.Modal { open() { super.open(); this.titleEl.setText("Need help and support?"); const content = this.contentEl.createDiv({ cls: "sc-pro-support-modal" }); content.createEl("p", { text: "Reply to your Smart Environment Pro welcome email for priority support." }); const reportBugButton = content.createEl("button", { text: "Report a bug", cls: "mod-warning" }); reportBugButton.addEventListener("click", () => { window.open( "https://github.com/brianpetro/obsidian-smart-connections/issues/new?template=bug_report.yml", "_external" ); }); const closeButton = content.createEl("button", { text: "Close" }); closeButton.addEventListener("click", () => { this.close(); }); } }; // src/components/connections-view/v3.css var v3_default2 = ".connections-view-early {\n .connections-top-bar {\n display: flex;\n gap: 0.5rem 1rem;\n\n .connections-actions {\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n flex: 0 0 auto;\n }\n\n .sc-context {\n display: flex;\n flex-direction: column;\n margin: 0;\n gap: 0.1rem;\n width: auto;\n flex: 1 1 auto;\n\n .sc-context-line {\n line-height: 1.1;\n }\n\n .sc-context-line--parent {\n color: var(--text-muted);\n font-size: 0.85em;\n }\n\n .sc-context-line--focus {\n color: var(--text-normal);\n font-size: 1.05em;\n font-weight: 600;\n }\n }\n }\n .connections-list {\n padding-top: 0.85rem;\n\n > .sc-collapsed ul {\n display: none;\n }\n > .sc-collapsed span svg {\n transform: rotate(-90deg);\n }\n > .sc-result-pinned {\n box-shadow: 0 0 0 1px var(--interactive-accent);\n border-radius: var(--radius-s);\n background-color: var(--background-modifier-hover);\n }\n }\n}"; // src/components/connections-view/v3.js var import_obsidian48 = require("obsidian"); // src/utils/context_lines.js var BLOCK_SEPARATOR = "#"; var DISPLAY_SEPARATOR = " \u203A "; var PATH_SEPARATOR = "/"; function get_context_lines(item) { const key = item.key; let top_line = ""; let bottom_line = ""; let parts = []; if (key.includes(BLOCK_SEPARATOR)) { parts = key.split(BLOCK_SEPARATOR); bottom_line = parts.pop().trim(); if (bottom_line[0] === "{") { const lines = item.lines.join("-"); bottom_line = parts.pop().trim() + ` Lines: ${lines}`; } top_line = parts.filter(Boolean).join(BLOCK_SEPARATOR); } else if (key.includes(PATH_SEPARATOR)) { parts = key.split(PATH_SEPARATOR); bottom_line = parts.pop().trim(); } top_line = parts.filter(Boolean).join(DISPLAY_SEPARATOR); return [top_line, bottom_line]; } // src/utils/connections_view_refresh_handler.js async function connections_view_refresh_handler(event) { const view_container = event.target.closest(".connections-view"); const list_el = view_container?.querySelector(".connections-list"); const entity_key = list_el?.dataset?.key; console.log(`Refreshing smart connections view entity ${entity_key}`); const refresh_entity = this.env.smart_sources.get(entity_key); if (refresh_entity) { await refresh_entity.read(); refresh_entity.queue_import(); await refresh_entity.collection.process_source_import_queue?.(); this.render_view(refresh_entity); } else { console.warn("No entity found for refresh"); } } // src/components/connections-view/v3.js async function build_html30(view, opts = {}) { const is_paused = Boolean(view.paused); const pause_title = is_paused ? "Resume auto-refresh" : "Pause auto-refresh"; const top_bar_buttons = [ { title: pause_title, icon: is_paused ? "play-circle" : "pause-circle", attrs: `data-action="toggle-pause" aria-pressed="${is_paused}"` }, { title: "More actions", icon: "menu", attrs: 'data-action="open-menu"' } ].map((btn) => ` `).join(""); const html = `
    ${top_bar_buttons}

    Loading...

    `; return html; } async function render30(view, opts = {}) { const html = await build_html30.call(this, view, opts); const frag = this.create_doc_fragment(html); this.apply_style_sheet(v3_default2); const container = frag.querySelector(".sc-connections-view"); post_process29.call(this, view, container, opts); return frag; } async function post_process29(view, container, opts = {}) { const list_container = container.querySelector(".connections-list-container"); const sc_top_bar_context = container.querySelector(".sc-top-bar .sc-context"); const env = view.env; let connections_item = opts.connections_item; if (!connections_item) { list_container.textContent = "No source item detected for current active view."; return container; } let connections_list = connections_item.connections || env.connections_lists.new_item(connections_item); if (!container._has_listeners) { container._has_listeners = true; const pause_button = container.querySelector('[data-action="toggle-pause"]'); pause_button?.addEventListener("click", () => { view.toggle_connections_paused(); }); const open_help = () => { StoryModal.open(view.plugin, { title: "Getting Started With Smart Connections", url: "https://smartconnections.app/story/smart-connections-getting-started/?utm_source=connections-view-help#page=understanding-connections-1" }); }; const menu_button = container.querySelector('[data-action="open-menu"]'); menu_button?.addEventListener("click", (event) => { const menu = new import_obsidian48.Menu(view.plugin.app); const raw_results = Array.isArray(connections_list?.results) ? connections_list.results : []; const connections_state = connections_list?.item?.data?.connections || {}; const visible_results = filter_hidden_results(raw_results, connections_state); menu.addItem((menu_item) => { menu_item.setTitle("Refresh connections").setIcon("refresh-cw").onClick(() => { connections_view_refresh_handler.call(view, { target: container }); }); }); menu.addItem((menu_item) => { menu_item.setTitle("Send results to Smart Context").setIcon("briefcase").setDisabled(!visible_results.length).onClick(async () => { if (!visible_results.length) return new import_obsidian48.Notice("No connection results to send to Smart Context"); const smart_context = env.smart_contexts.new_context(); smart_context.add_items(visible_results.map((r) => ({ key: r.item.key, score: r.score }))); smart_context.emit_event("context_selector:open"); connections_list.emit_event("connections:sent_to_context"); }); }); menu.addItem((menu_item) => { const links_payload = format_connections_as_links(visible_results); menu_item.setTitle("Copy as list of links").setIcon("copy").setDisabled(!links_payload).onClick(async () => { if (!links_payload) return new import_obsidian48.Notice("No connection results to copy"); await copy_to_clipboard2(links_payload); new import_obsidian48.Notice("Connections links copied to clipboard"); connections_list.emit_event("connections:copied_list"); }); }); menu.addItem((menu_item) => { const connections_settings = opts.connections_settings ?? connections_list?.settings; const expanded = connections_settings?.expanded_view; const title = expanded ? "Collapse all results" : "Expand all results"; const icon_name = expanded ? "fold-vertical" : "unfold-vertical"; menu_item.setTitle(title).setIcon(icon_name).onClick(() => { const curr_settings = opts.connections_settings ?? connections_list?.settings; const curr_expanded = curr_settings?.expanded_view; if (curr_settings) curr_settings.expanded_view = !curr_expanded; container.querySelectorAll(".sc-result").forEach((elm) => { curr_expanded ? elm.classList.add("sc-collapsed") : elm.classList.remove("sc-collapsed"); }); }); }); menu.addSeparator(); menu.addItem((menu_item) => { menu_item.setTitle("Connections settings").setIcon("settings").onClick(() => { view.open_settings(); }); }); menu.addItem((menu_item) => { menu_item.setTitle("Help & getting started").setIcon("help-circle").onClick(open_help); }); menu.showAtMouseEvent(event); }); } const connections_list_component_key = opts.connections_list_component_key || connections_list.connections_list_component_key || "connections_list_v3"; const list = await env.smart_components.render_component(connections_list_component_key, connections_list, opts); this.empty(list_container); list_container.appendChild(list); const entity = connections_list?.item || connections_item; const [top_line, bottom_line] = get_context_lines(entity); this.empty(sc_top_bar_context); const doc = sc_top_bar_context.ownerDocument; const context_spans = [ { text: top_line, class_name: "sc-context-line sc-context-line--parent" }, { text: bottom_line, class_name: "sc-context-line sc-context-line--focus" } ]; context_spans.forEach((line) => { const span = doc.createElement("span"); span.className = line.class_name; span.textContent = line.text || "\xA0"; sc_top_bar_context.appendChild(span); }); sc_top_bar_context.dataset.key = connections_item.key; const pause_btn = container.querySelector('[data-action="toggle-pause"]'); if (pause_btn) { const update_pause_button = (paused) => { const next_title = paused ? "Resume auto-refresh" : "Pause auto-refresh"; pause_btn.title = next_title; pause_btn.setAttribute("aria-label", `${next_title}`); pause_btn.setAttribute("aria-pressed", String(paused)); this.safe_inner_html( pause_btn, this.get_icon_html(paused ? "play-circle" : "pause-circle") ); }; update_pause_button(Boolean(view.paused)); view.register_pause_controls({ update: update_pause_button }); } return container; } // src/components/lookup_item_view.css var css_sheet2 = new CSSStyleSheet(); css_sheet2.replaceSync(`.lookup-item-view { display: flex; flex-direction: column; gap: 1rem; } .lookup-item-view .lookup-query-form { display: flex; flex-direction: column; gap: 0.5rem; } .lookup-item-view .lookup-query-label { font-size: 0.75rem; font-weight: 600; letter-spacing: 0.03em; text-transform: uppercase; color: var(--text-muted); } .lookup-item-view .lookup-query-input { resize: vertical; min-height: 5rem; padding: 0.75rem; border-radius: var(--radius-m); border: 1px solid var(--background-modifier-border); background-color: var(--background-secondary); color: var(--text-normal); font-size: 0.95rem; line-height: 1.5; } .lookup-item-view .lookup-query-input:focus { outline: none; border-color: var(--interactive-accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--interactive-accent) 30%, transparent); } .lookup-item-view .lookup-list-container { display: flex; flex-direction: column; gap: 0.5rem; } `); var lookup_item_view_default = css_sheet2; // src/components/lookup_item_view.js var DEFAULT_DEBOUNCE_MS = 300; var REQUIRED_MESSAGE = "Enter a lookup query to continue."; var PLACEHOLDER = "Describe the idea, topic, or question you want to explore\u2026"; var INFO = "Use semantic (embeddings) search to surface relevant notes. Results are sorted by similarity to your query. Note: returns different results than lexical (keyword) search."; async function build_html31(view, params = {}) { return `

    ${INFO}

    `; } async function render31(view, params = {}) { this.apply_style_sheet(lookup_item_view_default); const html = await build_html31.call(this, view, params); const frag = this.create_doc_fragment(html); const container = frag.querySelector(".lookup-item-view"); post_process30.call(this, view, container, params); return container; } async function post_process30(view, container, params = {}) { const query_input = container.querySelector(".lookup-query-input"); const query_form = container.querySelector(".lookup-query-form"); const list_container = container.querySelector(".lookup-list-container"); const state = { last_query: null }; const submit_query = async (raw_query) => { const query = sanitize_query(raw_query); update_query_validity({ input_el: query_input, query }); if (!query) { this.empty(list_container); list_container.innerHTML = `

    ${INFO}

    `; return; } ; if (query === state.last_query) return; state.last_query = query; const next_params = { ...params, query }; const lookup_list = view.env.lookup_lists.new_item(next_params); const rendered_list = await view.env.render_component("lookup_list", lookup_list, next_params); this.empty(list_container); list_container.appendChild(rendered_list); }; const debounced_submit = create_debounced_submit(submit_query); query_input.addEventListener("input", () => { debounced_submit(query_input.value); }); query_form.addEventListener("submit", (event) => { event.preventDefault(); debounced_submit.cancel?.(); submit_query(query_input.value); }); return container; } function create_debounced_submit(handler, delay = DEFAULT_DEBOUNCE_MS) { let timeout_id; const schedule = (value) => { if (timeout_id) clearTimeout(timeout_id); timeout_id = setTimeout(() => { timeout_id = void 0; handler(value); }, delay); }; schedule.cancel = () => { if (timeout_id) { clearTimeout(timeout_id); timeout_id = void 0; } }; return schedule; } function sanitize_query(value) { if (typeof value !== "string") return ""; return value.trim(); } function update_query_validity({ input_el, query }) { if (!input_el?.setCustomValidity) return; if (!query) input_el.setCustomValidity(REQUIRED_MESSAGE); else input_el.setCustomValidity(""); } // src/components/lookup_list.js async function build_html32(lookup_list, opts = {}) { return `
    `; } async function render32(lookup_list, opts = {}) { const html = await build_html32.call(this, lookup_list, opts); const frag = this.create_doc_fragment(html); const container = frag.querySelector(".lookup-list"); post_process31.call(this, lookup_list, container, opts); return container; } async function post_process31(lookup_list, container, opts = {}) { container.dataset.key = lookup_list.key; const results = await lookup_list.get_results(opts); if (!results || !Array.isArray(results) || results.length === 0) { const no_results = this.create_doc_fragment(`

    No results found

    `); container.appendChild(no_results); return container; } const smart_components = lookup_list.env.smart_components; const result_frags = await Promise.all(results.map((result) => { return smart_components.render_component(`connections_list_item_v3`, result, { event_key_domain: "lookup", ...opts }); })); result_frags.forEach((result_frag) => container.appendChild(result_frag)); return container; } // src/actions/connections-list/pre_process.js function pre_process2(params) { if (!params.limit) params.limit = this.settings?.results_limit ?? 20; if (!params.results_collection_key) { params.results_collection_key = this.collection.results_collection_key; } if (!params.filter) params.filter = {}; if (!params.score_algo_key) params.score_algo_key = this.collection.score_algo_key; params.to_item = this.item; if (!params.filter.exclude_keys) params.filter.exclude_keys = []; if (!params.filter.exclude_key_starts_with_any) { params.filter.exclude_key_starts_with_any = []; } get_connections_feedback_items(this, params); const exclude_keys_set = new Set(params.filter.exclude_keys); exclude_keys_set.add(this.item.key); params.hidden_keys.forEach((key) => exclude_keys_set.add(key)); params.pinned_keys.forEach((key) => exclude_keys_set.add(key)); params.filter.exclude_keys = Array.from(exclude_keys_set); if (params.results_collection_key === "smart_blocks") { const exclude_starts_set = new Set(params.filter.exclude_key_starts_with_any); exclude_starts_set.add(this.item.key); params.hidden_keys.forEach((key) => exclude_starts_set.add(key)); params.pinned_keys.forEach((key) => exclude_starts_set.add(key)); params.filter.exclude_key_starts_with_any = Array.from(exclude_starts_set); if (this.collection.settings.exclude_frontmatter_blocks) { if (!params.filter.exclude_key_ends_with_any || !Array.isArray(params.filter.exclude_key_ends_with_any)) { params.filter.exclude_key_ends_with_any = []; } params.filter.exclude_key_ends_with_any.push("---frontmatter---"); } } } function get_connections_feedback_items(connections_list, params) { params.hidden = []; params.hidden_keys = []; params.pinned = []; params.pinned_keys = []; const connections_state = connections_list.item.data?.connections || {}; Object.entries(connections_state).forEach(([key, state]) => { if (!state || state.hidden == null && state.pinned == null) return; const [collection_key, ...item_key_parts] = key.split(":"); if (!collection_key || !item_key_parts.length) return; const item_key = item_key_parts.join(":"); const collection = connections_list.env[collection_key]; if (!collection) return; const item = collection.get(item_key); if (!item) return; if (state.hidden && !state.pinned) { params.hidden.push(item); params.hidden_keys.push(item_key); } if (state.pinned) { params.pinned.push(item); params.pinned_keys.push(item_key); } }); } // smart_env.config.js var smart_env_config3 = { collections: { connections_lists: connections_lists_default }, item_types: { ConnectionsList }, items: { connections_list: { class: ConnectionsList } }, modules: {}, components: { connections_codeblock: { render: render26 }, connections_list_item_v3: { render: render27, settings_config: settings_config11 }, connections_list_v3: { render: render28, display_name: display_name4 }, connections_settings_header: { render: render29 }, connections_view_v3: { render: render30 }, lookup_item_view: { render: render31 }, lookup_list: { render: render32 } }, actions: { connections_list_pre_process: { action: pre_process2, pre_process: pre_process2 } } }; // node_modules/obsidian-smart-env/utils/open_note.js var import_obsidian49 = require("obsidian"); async function open_note(plugin, target_path, event = null, opts = {}) { const { new_tab = false } = opts; const env = plugin.env; if (target_path.endsWith("#")) target_path = target_path.slice(0, -1); let target_file; let block = null; if (target_path.includes("#")) { const [file_path] = target_path.split("#"); target_file = plugin.app.metadataCache.getFirstLinkpathDest(file_path, ""); block = env.smart_blocks.get(target_path); } else { target_file = plugin.app.metadataCache.getFirstLinkpathDest(target_path, ""); } if (!target_file) { console.warn(`[open_note] Unable to resolve file for ${target_path}`); return; } let leaf; if (event) { const is_mod = import_obsidian49.Keymap.isModEvent(event); const is_alt = import_obsidian49.Keymap.isModifier(event, "Alt"); if (is_mod && is_alt) { leaf = plugin.app.workspace.splitActiveLeaf("vertical"); } else if (is_mod || new_tab) { leaf = plugin.app.workspace.getLeaf(true); } else { leaf = plugin.app.workspace.getMostRecentLeaf(); } } else { leaf = plugin.app.workspace.getMostRecentLeaf(); } await leaf.openFile(target_file); if (typeof block?.line_start === "number") { const { editor } = leaf.view; const pos = { line: block.line_start, ch: 0 }; editor.setCursor(pos); editor.scrollIntoView({ to: pos, from: pos }, true); } } // node_modules/smart-notices/smart_notices.js var import_obsidian50 = require("obsidian"); // node_modules/smart-notices/notices.js var NOTICES2 = { item_excluded: { en: "Cannot show Smart Connections for excluded entity: {{entity_key}}" }, load_env: { en: "Mobile detected: to prevent performance issues, click to load Smart Environment when ready.", button: { en: `Load Smart Env`, callback: (env) => { env.load(true); } }, timeout: 1e4 }, /** @deprecated in favor of in-component insctructions (2025-06-22) */ missing_entity: { en: "No entity found for key: {{key}}" }, notice_muted: { en: "Notice muted" }, new_version_available: { en: "A new version is available! (v{{version}})", timeout: 15e3, button: { en: "Release notes", callback: (scope) => { window.open("https://github.com/brianpetro/obsidian-smart-connections/releases", "_blank"); } } }, new_early_access_version_available: { en: "A new early access version is available! (v{{version}})" }, supporter_key_required: { en: "Supporter license key required for early access update" }, revert_to_stable_release: { en: 'Click "Check for Updates" in the community plugins tab and complete the update for Smart Connections to finish reverting to the stable release.', timeout: 0 }, action_installed: { en: 'Installed action "{{name}}"' }, action_install_error: { en: 'Error installing action "{{name}}": {{error}}', timeout: 0 }, embed_model_not_loaded: { en: "Embed model not loaded. Please wait for the model to load and try again." }, embed_search_text_failed: { en: "Failed to embed search text." }, error_in_embedding_search: { en: "Error in embedding search. See console for details." }, copied_to_clipboard: { en: "Message: {{content}} copied successfully." }, copy_failed: { en: "Unable to copy message to clipboard." }, copied_chatgpt_url_to_clipboard: { en: "ChatGPT URL copied to clipboard." }, loading_collection: { en: "Loading {{collection_key}}..." }, done_loading_collection: { en: "{{collection_key}} loaded." }, saving_collection: { en: "Saving {{collection_key}}..." }, initial_scan: { en: "[{{collection_key}}] Starting initial scan...", timeout: 0 }, done_initial_scan: { en: "[{{collection_key}}] Initial scan complete.", timeout: 3e3 }, pruning_collection: { en: "Pruning {{collection_key}}..." }, done_pruning_collection: { en: "Pruned {{count}} items from {{collection_key}}." }, embedding_progress: { en: "Embedding progress: {{progress}} / {{total}}\n{{tokens_per_second}} tokens/sec using {{model_name}}", button: { en: "Pause", callback: (env) => { console.log("pausing"); env.smart_sources.entities_vector_adapter.halt_embed_queue_processing(); } }, timeout: 0 }, embedding_complete: { en: "Embedding complete. {{total_embeddings}} embeddings created. {{tokens_per_second}} tokens/sec using {{model_name}}", timeout: 0 }, embedding_paused: { en: "Embedding paused. Progress: {{progress}} / {{total}}\n{{tokens_per_second}} tokens/sec using {{model_name}}", button: { en: "Resume", callback: (env) => { env.smart_sources.entities_vector_adapter.resume_embed_queue_processing(100); } }, timeout: 0 }, embedding_error: { en: "Error embedding: {{error}}", timeout: 0 }, import_progress: { en: "Importing... {{progress}} / {{total}} sources", timeout: 0 }, done_import: { en: "Import complete. {{count}} sources imported in {{time_in_seconds}}s", timeout: 0 }, no_import_queue: { en: "No items in import queue" }, clearing_all: { en: "Clearing all data...", timeout: 0 }, done_clearing_all: { en: "All data cleared and reimported", timeout: 3e3 }, image_extracting: { en: "Extracting text from Image(s)", timeout: 0 }, pdf_extracting: { en: "Extracting text from PDF(s)", timeout: 0 }, insufficient_settings: { en: "Insufficient settings for {{key}}, missing: {{missing}}", timeout: 0 }, unable_to_init_source: { en: "Unable to initialize source: {{key}}", timeout: 0 }, reload_sources: { en: "Reloaded sources in {{time_ms}}ms" } }; // node_modules/smart-notices/smart_notices.js function define_default_create_methods2(notices) { for (const key of Object.keys(notices)) { const notice_obj = notices[key]; if (typeof notice_obj.create !== "function") { notice_obj.create = function(opts = {}) { let text = this.en ?? key; for (const [k, v] of Object.entries(opts)) { text = text.replace(new RegExp(`{{${k}}}`, "g"), String(v)); } let button; if (!opts.button && this.button) { const btn_label = typeof this.button.en === "string" ? this.button.en : "OK"; button = { text: btn_label, callback: typeof this.button.callback === "function" ? this.button.callback : () => { } // no-op }; } else { button = opts.button; } let final_timeout = opts.timeout ?? this.timeout ?? 5e3; return { text, button, timeout: final_timeout, confirm: opts.confirm, // pass any user-provided confirm immutable: opts.immutable // pass any user-provided immutable }; }; } } return notices; } var SmartNotices2 = class { /** * @param {Object} scope - The main plugin instance */ constructor(env, opts = {}) { env?.create_env_getter(this); this.active = {}; this.adapter = opts.adapter || this.env.config.modules.smart_notices.adapter; define_default_create_methods2(NOTICES2); } /** plugin settings for notices (muted, etc.) */ get settings() { if (!this.env?.settings?.smart_notices) { this.env.settings.smart_notices = {}; } if (!this.env?.settings?.smart_notices?.muted) { this.env.settings.smart_notices.muted = {}; } return this.env?.settings?.smart_notices; } /** * Displays a notice by key or custom message. * Usage: * notices.show('load_env', { scope: this }); * * @param {string} id - The notice key or custom ID * @param {object} opts - Additional user opts */ show(id, opts = {}) { let message = null; if (typeof opts === "string") { message = opts; } else { opts = opts || {}; } const normalized_id = this._normalize_notice_key(id); if (this.settings?.muted?.[normalized_id]) { if (opts.confirm?.callback) { opts.confirm.callback(); } return; } const notice_entry = NOTICES2[id]; let derived = { text: message || id, timeout: opts.timeout ?? 5e3, button: opts.button, immutable: opts.immutable, confirm: opts.confirm }; if (notice_entry?.create) { const result = notice_entry.create({ ...opts }); derived.text = message || result.text; derived.timeout = result.timeout; derived.button = result.button; derived.immutable = result.immutable; derived.confirm = result.confirm; } const content_fragment = this._build_fragment(normalized_id, derived.text, derived); if (this.active[normalized_id]?.noticeEl?.isConnected) { return this.active[normalized_id].setMessage(content_fragment, derived.timeout); } return this._render_notice(normalized_id, content_fragment, derived); } /** * Normalizes the notice key to a safe string. */ _normalize_notice_key(key) { return key.replace(/[^a-zA-Z0-9_-]/g, "_"); } /** * Creates and tracks the notice instance */ _render_notice(normalized_id, content_fragment, { timeout }) { this.active[normalized_id] = new this.adapter(content_fragment, timeout); return this.active[normalized_id]; } /** * Builds a DocumentFragment with notice text & possible buttons */ _build_fragment(id, text, { button, confirm: confirm2, immutable }) { const frag = document.createDocumentFragment(); frag.createEl("p", { cls: "sc-notice-head", text: `[Smart Env v${this.env.constructor.version}]` }); const content = frag.createEl("p", { cls: "sc-notice-content", text }); const actions = frag.createEl("div", { cls: "sc-notice-actions" }); if (confirm2?.text && typeof confirm2.callback === "function") { this._add_button(confirm2, actions); } if (button?.text && typeof button.callback === "function") { this._add_button(button, actions); } if (!immutable) { this._add_mute_button(id, actions); } return frag; } /** * Creates a