/*! smart-connections-obsidian v4.1.7 | (c) 2026 🌴 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_obsidian41 = 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.7"; 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.config.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(); } get_display_name(params = {}) { return this.block_adapter?.get_display_name(params); } // 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"); } /** * @method get_display_name * @abstract * @param {Object} params Parameters for display name generation. * @returns {string} The display name of the block. * @throws {Error} If not implemented by subclass. */ get_display_name(params) { 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(); } get_display_name(params = {}) { if (!this.item?.key) return ""; const show_full_path = params.show_full_path ?? true; if (show_full_path) { return this.item.key.replace(/#/g, " > ").replace(/\//g, " > "); } const pcs = []; const [source_key, ...block_parts] = this.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 (this.item.lines) pcs.push(`Lines: ${this.item.lines.join("-")}`); } else { pcs.push(block_parts.pop()); } } return pcs.filter(Boolean).join(" > "); } }; // 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: {} }; 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; if (typeof this.model.re_render_settings === "function") { 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_obsidian36 = 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_preset: "xml_structured", 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_preset: "xml_structured", 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 MILESTONES_HELP_URL = "https://smartconnections.app/smart-environment/milestones/?utm_source=milestones_modal_help"; var MilestonesModal = class extends import_obsidian7.Modal { constructor(app, env) { super(app); this.env = env; } async onOpen() { render_milestones_modal_title(this.titleEl, this.env); this.contentEl.empty(); const milestones = await this.env.smart_components.render_component("milestones", this.env, {}); this.contentEl.appendChild(milestones); } onClose() { this.contentEl.empty(); } }; function render_milestones_modal_title(title_el, env) { if (!title_el) return; title_el.empty(); title_el.classList.add("sc-milestones-modal__title"); const row_el = document.createElement("div"); row_el.className = "sc-milestones-modal__title-row"; const text_el = document.createElement("div"); text_el.className = "sc-milestones-modal__title-text"; text_el.textContent = "Smart Milestones"; const help_btn_el = document.createElement("button"); help_btn_el.type = "button"; help_btn_el.className = "sc-milestones-modal__help-btn"; help_btn_el.setAttribute("aria-label", "Open Smart Milestones help"); help_btn_el.setAttribute("title", "Help"); render_help_icon(help_btn_el); help_btn_el.addEventListener("click", (evt) => { evt.preventDefault(); evt.stopPropagation(); try { env?.events?.emit?.("milestones:help", {}); } catch (err) { } window.open(MILESTONES_HELP_URL, "_external"); }); row_el.appendChild(text_el); row_el.appendChild(help_btn_el); title_el.appendChild(row_el); } function render_help_icon(icon_el) { const ok = set_icon_with_fallback(icon_el, ["circle-help", "help-circle", "help", "info"]); if (!ok) icon_el.textContent = "?"; } function set_icon_with_fallback(icon_el, icon_ids) { if (!icon_el) return false; const ids = Array.isArray(icon_ids) ? icon_ids : []; for (const icon_id of ids) { if (typeof icon_id !== "string" || icon_id.length === 0) continue; icon_el.textContent = ""; try { (0, import_obsidian7.setIcon)(icon_el, icon_id); } catch (err) { continue; } if (icon_el.querySelector("svg")) return true; } return false; } // 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" }, "Xenova/multilingual-e5-small": { "id": "Xenova/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-normal);\r font-variant-numeric: tabular-nums;\r \r padding: 0.15em 0.6em;\r border-radius: 999px;\r background-color: var(--background-secondary);\r border: 1px solid var(--background-modifier-border);\r white-space: nowrap;\r }\r \r .sc-events-checklist__hint {\r font-size: var(--font-ui-small);\r color: var(--text-muted);\r text-align: right;\r }\r \r .sc-events-checklist__progress {\r height: 6px;\r border-radius: 999px;\r overflow: hidden;\r background-color: var(--background-secondary);\r border: 1px solid var(--background-modifier-border);\r }\r \r .sc-events-checklist__progress-fill {\r height: 100%;\r width: var(--sc-events-checklist-progress, 0%);\r background-color: var(--color-green, var(--interactive-accent));\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 display: flex;\r align-items: baseline;\r justify-content: space-between;\r gap: var(--size-4-2);\r }\r \r .sc-events-checklist__group-name {\r display: inline-flex;\r align-items: center;\r min-width: 0;\r }\r \r .sc-events-checklist__group-count {\r font-size: var(--font-ui-small);\r color: var(--text-muted);\r white-space: nowrap;\r font-variant-numeric: tabular-nums;\r flex: 0 0 auto;\r }\r \r /*\r Group completion badge:\r - shows only when every item in the group is checked\r - uses :has() (no JS needed)\r - avoids false positives on empty groups by requiring at least one item\r */\r .sc-events-checklist__group:has(.sc-events-checklist__item):not(:has(.sc-events-checklist__item[data-checked='false'])) {\r .sc-events-checklist__group-name::after {\r content: "DONE";\r \r /* layout */\r display: inline-flex;\r align-items: center;\r justify-content: center;\r margin-left: 0.5em;\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.65em;\r font-weight: 700;\r letter-spacing: 0.12em;\r text-transform: uppercase;\r \r /* theme vars (with safe fallback) */\r color: var(--color-green, var(--text-accent));\r background-color: var(--background-secondary);\r border: 1px solid var(--background-modifier-border);\r \r /* subtle depth, theme-aware */\r box-shadow:\r 0 0 0 1px var(--background-primary),\r 0 1px 3px rgba(0, 0, 0, 0.25);\r transform: translateY(-0.03em);\r }\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 cursor: pointer;\r border: 1px solid transparent;\r \r transition:\r background-color 120ms ease,\r border-color 120ms ease,\r transform 120ms ease;\r \r &:hover {\r background: var(--background-modifier-hover);\r border-color: var(--background-modifier-border);\r }\r \r &:active {\r transform: translateY(1px);\r }\r \r &:focus-visible {\r outline: 2px solid var(--interactive-accent);\r outline-offset: 2px;\r background: var(--background-modifier-hover);\r border-color: var(--interactive-accent);\r }\r }\r \r .sc-events-checklist__label {\r display: flex;\r align-items: flex-start;\r gap: var(--size-2-2);\r cursor: pointer;\r }\r \r .sc-events-checklist__icon {\r display: inline-flex;\r align-items: center;\r justify-content: center;\r margin-top: 2px;\r width: 18px;\r height: 18px;\r flex: 0 0 auto;\r color: var(--text-muted);\r \r svg {\r width: 18px;\r height: 18px;\r }\r }\r \r .sc-events-checklist__milestone {\r line-height: 1.3;\r user-select: text;\r cursor: text;\r }\r \r .sc-events-checklist__item[data-checked='true'] {\r .sc-events-checklist__icon {\r color: var(--color-green, var(--text-accent));\r }\r \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 .sc-events-checklist__label.pro-milestone > .sc-events-checklist__milestone {\r position: relative; /* safe default, keeps ::after anchored */\r }\r \r /* 2) The PRO badge itself */\r .sc-events-checklist__label.pro-milestone > .sc-events-checklist__milestone::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 .sc-events-checklist__label.pro-milestone > .sc-events-checklist__milestone:hover::after {\r background-color: var(--interactive-accent-hover);\r filter: brightness(1.05);\r }\r \r /* Milestones modal: title row help icon */\r .sc-milestones-modal__title {\r width: 100%;\r }\r \r .sc-milestones-modal__title-row {\r display: flex;\r align-items: center;\r gap: var(--size-4-2);\r width: 100%;\r }\r \r .sc-milestones-modal__title-text {\r min-width: 0;\r }\r \r .sc-milestones-modal__help-btn {\r display: inline-flex;\r align-items: center;\r justify-content: center;\r \r width: 28px;\r height: 28px;\r padding: 0;\r border-radius: var(--radius-s);\r \r background: transparent;\r border: 1px solid transparent;\r color: var(--text-muted);\r \r cursor: pointer;\r }\r \r .sc-milestones-modal__help-btn svg {\r width: 18px;\r height: 18px;\r }\r \r .sc-milestones-modal__help-btn:hover {\r background: var(--background-modifier-hover);\r border-color: var(--background-modifier-border);\r color: var(--text-normal);\r }\r \r .sc-milestones-modal__help-btn:active {\r transform: translateY(1px);\r }\r \r .sc-milestones-modal__help-btn:focus-visible {\r outline: 2px solid var(--interactive-accent);\r outline-offset: 2px;\r background: var(--background-modifier-hover);\r border-color: var(--interactive-accent);\r }\r `; // node_modules/obsidian-smart-env/src/components/milestones.js var import_obsidian14 = require("obsidian"); // node_modules/obsidian-smart-env/src/utils/onboarding_events.js var import_obsidian13 = require("obsidian"); var PLUGIN_INSTALL_EVENT_CONFIG = { "connections:installed": { ids: ["smart-connections"] }, "connections_pro:installed": { ids: ["smart-connections"], require_pro_name: true }, "context:installed": { ids: ["smart-context"] }, "context_pro:installed": { ids: ["smart-context"], require_pro_name: true }, "chat:installed": { ids: ["smart-chatgpt", "smart-chat"] }, "chat_pro:installed": { ids: ["smart-chat"], require_pro_name: true } }; 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); } }); } function check_if_event_emitted(env, event_key) { const plugin_event_state = resolve_plugin_install_event(env, event_key); if (plugin_event_state === true) return true; if (env?.event_logs?.items?.[event_key]) return true; if (plugin_event_state === false) return false; return false; } var EVENTS_CHECKLIST_ITEMS_BY_EVENT_KEY = { // Environment "sources:import_completed": { group: "Environment", milestone: "Initial vault import completed (all sources discovered).", link: "https://smartconnections.app/smart-environment/settings/?utm_source=milestones#sources" }, "embedding:completed": { group: "Environment", milestone: "Initial embedding completed, you are ready to make connections!", link: "https://smartconnections.app/smart-environment/settings/?utm_source=milestones#embedding-models" }, // Connections "connections:installed": { group: "Connections", milestone: "Installed Smart Connections (core plugin).", link: "https://smartconnections.app/smart-connections/list-feature/?utm_source=milestones" }, "connections:opened": { group: "Connections", milestone: "Opened the connections view.", link: "https://smartconnections.app/smart-connections/list-feature/?utm_source=milestones#quick-start" }, "connections:drag_result": { group: "Connections", milestone: "Dragged a Smart Connections result into a note to create a link.", link: "https://smartconnections.app/smart-connections/list-feature/?utm_source=milestones#drag-link" }, "connections:open_result": { group: "Connections", milestone: "Opened a Smart Connections result from the UI (list item or inline popover).", link: "https://smartconnections.app/smart-connections/list-feature/?utm_source=milestones#core-interactions" }, "connections:sent_to_context": { group: "Connections", milestone: "Sent Connections results to Smart Context (turn discovery into a context pack).", link: "https://smartconnections.app/smart-connections/list-feature/?utm_source=milestones#send-to-context" }, "connections:copied_list": { group: "Connections", milestone: "Copied Connections results as a list of links.", link: "https://smartconnections.app/smart-connections/list-feature/?utm_source=milestones#copy-list" }, "connections:hover_preview": { group: "Connections", milestone: "Previewed a connection by holding cmd/ctrl while hovering the result.", link: "https://smartconnections.app/smart-connections/list-feature/?utm_source=milestones#core-interactions" }, "connections:open_random": { group: "Connections", milestone: "Opened a random connection from Smart Connections.", link: "https://smartconnections.app/smart-connections/getting-started/?utm_source=milestones#open-a-random-connection" }, // Connections Pro "connections_pro:installed": { group: "Connections Pro", milestone: "Installed Smart Connections Pro.", link: "https://smartconnections.app/pro-plugins/?utm_source=milestones#connections-pro", is_pro: true }, // Lookup "lookup:hover_preview": { group: "Lookup", milestone: "Previewed a Smart Lookup result by holding cmd/ctrl while hovering.", link: "https://smartconnections.app/smart-connections/lookup/?utm_source=milestones#understanding-results" }, "lookup:get_results": { group: "Lookup", milestone: "Submitted a lookup query (started a semantic search).", link: "https://smartconnections.app/smart-connections/lookup/?utm_source=milestones" }, "lookup:drag_result": { group: "Lookup", milestone: "Dragged a Smart Lookup result into a note to create a link.", link: "https://smartconnections.app/smart-connections/lookup/?utm_source=milestones#understanding-results" }, "lookup:open_result": { group: "Lookup", milestone: "Opened a Lookup result.", link: "https://smartconnections.app/smart-connections/lookup/?utm_source=milestones#understanding-results" }, // Context "context:created": { group: "Context", milestone: "First context created!", link: "https://smartconnections.app/smart-context/builder/?utm_source=milestones#quick-start" }, "context:copied": { group: "Context", milestone: "Copied context to clipboard.", link: "https://smartconnections.app/smart-context/clipboard/?utm_source=milestones#copy-current" }, "context_selector:open": { group: "Context", milestone: "Opened the Context Builder selector modal.", link: "https://smartconnections.app/smart-context/builder/?utm_source=milestones#open-builder" }, "context:named": { group: "Context", milestone: "Named a Smart Context (created a reusable saved context).", link: "https://smartconnections.app/smart-context/builder/?utm_source=milestones#save-reuse" }, "context:renamed": { group: "Context", milestone: "Renamed a Smart Context (increased clarity).", link: "https://smartconnections.app/smart-context/builder/?utm_source=milestones#save-reuse" }, "context:copied_with_media": { group: "Context Pro", milestone: "Copied context with media (images/PDF pages) for multimodal workflows.", link: "https://smartconnections.app/smart-context/clipboard/?utm_source=milestones#copy-modes", is_pro: true }, // Context Pro "context_pro:installed": { group: "Context Pro", milestone: "Installed Smart Context Pro.", link: "https://smartconnections.app/pro-plugins/?utm_source=milestones#context-pro", is_pro: true }, // Chat "chat:installed": { group: "Chat", milestone: "Installed Smart ChatGPT.", link: "https://smartconnections.app/smart-chat/?utm_source=milestones" }, "chat_codeblock:saved_thread": { group: "Chat", milestone: "Started a chat in a Smart Chat codeblock (opened the loop).", link: "https://smartconnections.app/smart-chat/codeblock/?utm_source=milestones#quick-start" }, "completion:completed": { group: "Chat Pro", milestone: "Received the first Smart Chat response (a completion finished).", link: "https://smartconnections.app/smart-chat/api-integration/?utm_source=milestones#quick-start", is_pro: true }, "chat_codeblock:marked_done": { group: "Chat", milestone: "Marked the chat thread as done (closed the loop).", link: "https://smartconnections.app/smart-chat/codeblock/?utm_source=milestones#chat-inbox" }, // Chat Pro "chat_pro:installed": { group: "Chat Pro", milestone: "Installed Smart Chat Pro.", link: "https://smartconnections.app/pro-plugins/?utm_source=milestones#chat-pro", is_pro: true }, // Pro "smart_plugins_oauth_completed": { group: "Pro", milestone: "Connected account (enabled Pro plugins).", link: "https://smartconnections.app/pro-plugins/?utm_source=milestones" }, // Connections Pro (Inline Connections) "inline_connections:show": { group: "Connections Pro", milestone: "Opened inline connections in-note (used the inline workflow).", link: "https://smartconnections.app/smart-connections/inline/?utm_source=milestones", is_pro: true }, "inline_connections:open_result": { group: "Connections Pro", milestone: "Opened an inline connections result (navigated from discovery to source).", link: "https://smartconnections.app/smart-connections/inline/?utm_source=milestones", is_pro: true }, "inline_connections:drag_result": { group: "Connections Pro", milestone: "Inserted an inline link from an inline connection (converted discovery into a durable link).", link: "https://smartconnections.app/smart-connections/inline/?utm_source=milestones", is_pro: true } }; var EVENTS_CHECKLIST_GROUP_ORDER = [ "Environment", "Connections", "Lookup", "Context", "Chat", "Pro", "Connections Pro", "Context Pro", "Chat Pro" ]; 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 }; }); } function resolve_plugin_install_event(env, event_key) { const config = PLUGIN_INSTALL_EVENT_CONFIG[event_key]; if (!config) return null; const manifests = env?.plugin?.app?.plugins?.manifests || {}; const plugin_ids = Array.isArray(config.ids) ? config.ids : []; for (const plugin_id of plugin_ids) { const manifest = manifests[plugin_id]; if (!manifest) continue; if (config.require_pro_name && !is_pro_manifest(manifest)) continue; return true; } return false; } function is_pro_manifest(manifest) { const name = manifest?.name; if (typeof name !== "string") return false; return name.toLowerCase().includes("pro"); } // node_modules/obsidian-smart-env/src/components/milestones.js 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 progress_pct = total_count > 0 ? Math.round(checked_count / total_count * 100) : 0; const groups_html = groups.map((group) => { const group_checked_count = group.items.reduce((acc, item) => { return acc + (check_if_event_emitted(env, item.event_key) ? 1 : 0); }, 0); const group_total_count = group.items.length; 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)} ${group_checked_count.toString()} / ${group_total_count.toString()}

    ${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 = {}) { attach_item_link_listeners(container); render_item_state_icons(container); return container; } function build_item_html(item, state) { const checked = state.checked === true; const checked_flag = checked ? "true" : "false"; const link = typeof item.link === "string" ? item.link : ""; const status_label = checked ? "Completed" : "Incomplete"; const aria_label = `Open docs: ${item.milestone || item.event_key || "milestone"} (${status_label})`; return `
  • ${escape_html(item.milestone)}
  • `; } function attach_item_link_listeners(container) { if (!container) return; if (container.getAttribute("data-links-enabled") === "true") return; container.setAttribute("data-links-enabled", "true"); container.addEventListener("click", (evt) => { const item_el = get_item_el_from_event(container, evt); if (!item_el) return; const selection = window.getSelection(); if (selection && selection.toString().length > 0) return; open_item_link(item_el); }); container.addEventListener("keydown", (evt) => { const key = evt && /** @type {KeyboardEvent} */ evt.key; if (key !== "Enter" && key !== " ") return; const item_el = get_item_el_from_event(container, evt); if (!item_el) return; evt.preventDefault(); open_item_link(item_el); }); } function open_item_link(item_el) { const link = get_item_link(item_el); if (typeof link === "string" && link.length > 0) { window.open(link, "_external"); } } function render_item_state_icons(container) { if (!container) return; const item_els = Array.from(container.querySelectorAll(".sc-events-checklist__item")); item_els.forEach((item_el) => { const checked = item_el.getAttribute("data-checked") === "true"; const icon_el = item_el.querySelector(".sc-events-checklist__icon"); if (!icon_el) return; set_item_icon( /** @type {HTMLElement} */ icon_el, checked ); }); } function set_item_icon(icon_el, checked) { const icon_ids = checked ? ["circle-check", "check-circle", "check"] : ["circle", "circle-dashed", "dot"]; set_icon_with_fallback2(icon_el, icon_ids); } function set_icon_with_fallback2(icon_el, icon_ids) { if (!icon_el) return; const ids = Array.isArray(icon_ids) ? icon_ids : []; for (const icon_id of ids) { if (typeof icon_id !== "string" || icon_id.length === 0) continue; icon_el.textContent = ""; try { (0, import_obsidian14.setIcon)(icon_el, icon_id); } catch (err) { continue; } if (icon_el.querySelector("svg")) return; } } function get_item_el_from_event(container, evt) { const target = evt && /** @type {any} */ evt.target; if (!target || typeof target.closest !== "function") return null; const item_el = target.closest(".sc-events-checklist__item"); if (!item_el) return null; if (!container.contains(item_el)) return null; return item_el; } function get_item_link(item_el) { const data_link = item_el.getAttribute("data-link"); if (typeof data_link === "string" && data_link.length > 0) return data_link; const event_key = item_el.getAttribute("data-event-key"); if (typeof event_key !== "string" || event_key.length === 0) return ""; const item = EVENTS_CHECKLIST_ITEMS_BY_EVENT_KEY[event_key]; if (!item || typeof item.link !== "string") return ""; return item.link; } // 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_obsidian16 = require("obsidian"); // node_modules/obsidian-smart-env/src/utils/smart_plugins.js var import_obsidian15 = 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_obsidian15.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_obsidian15.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_obsidian15.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_obsidian15.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_obsidian16.Notice("Copied login link to clipboard."); } else { new import_obsidian16.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_obsidian16.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_obsidian16.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_obsidian16.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_obsidian16.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_obsidian16.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_obsidian17 = 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_obsidian17.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_obsidian17.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_obsidian17.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_obsidian17.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_obsidian17.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_obsidian17.Modal(app); modal.setTitle(display_name5 || item.name || item.repo); await import_obsidian17.MarkdownRenderer.render(app, readme, modal.contentEl, "", new import_obsidian17.Component()); modal.open(); } catch (err) { console.error("[pro-plugins:list_item] Failed to load README:", err); new import_obsidian17.Notice("Failed to load README"); } }; // node_modules/obsidian-smart-env/src/modals/smart_model_modal.js var import_obsidian18 = require("obsidian"); var SmartModelModal = class extends import_obsidian18.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_obsidian19 = 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_obsidian19.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_obsidian20 = 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_obsidian20.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_obsidian21 = 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_obsidian21.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; } const settng_is_pro = setting_config.scope_class === "pro-setting"; const env_is_pro = !!scope.env?.is_pro; 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) => { if (settng_is_pro && !env_is_pro) { new import_obsidian21.Notice("Nice try! This is a PRO feature. Please upgrade to access this setting."); return; } set_by_path(scope.settings, setting_path, value); }); if (settng_is_pro && !env_is_pro) { text.setDisabled(true); } }); 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_obsidian21.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_obsidian22 = 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_obsidian22.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_obsidian23 = require("obsidian"); var ExcludedSourcesModal = class extends import_obsidian23.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"; }, { once: true }); } }; // 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 {\n margin: 1rem 0;\n}\n\n.smart-env-settings-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 0.5rem;\n}\n\n.toggle-env-settings-btn {\n cursor: pointer;\n}\n\n\n.setting-group .setting-items .setting-item.env-setting-highlight {\n border: 1px solid var(--interactive-accent);\n background-color: var(--interactive-hover);\n padding: 0.5rem;\n margin: 0.5rem 0;\n}\n\n.settings-group {\n .setting-item {\n border-top: none;\n }\n}\n\n.sc-inline-confirm-row {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n margin-top: 0.5rem;\n}\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_obsidian24 = 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_obsidian24.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_obsidian25 = require("obsidian"); async function copy_to_clipboard2(text) { try { if (navigator?.clipboard?.writeText) { await navigator.clipboard.writeText(text); } else if (!import_obsidian25.Platform.isMobile) { const { clipboard } = require("electron"); clipboard.writeText(text); } else { new import_obsidian25.Notice("Unable to copy text: no valid method found."); } } catch (err) { console.error("Failed to copy text:", err); new import_obsidian25.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_obsidian29 = require("obsidian"); // node_modules/obsidian-smart-env/src/utils/register_status_bar_context_menu.js var import_obsidian28 = require("obsidian"); // node_modules/obsidian-smart-env/views/source_inspector.js var import_obsidian26 = require("obsidian"); var SmartNoteInspectModal = class extends import_obsidian26.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_obsidian27 = require("obsidian"); var EnvStatsModal = class extends import_obsidian27.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_obsidian28.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_obsidian28.Notice("No active note found"); return; } const src = env.smart_sources?.get(active_file.path); if (!src) { new import_obsidian28.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_obsidian28.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_obsidian29.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_obsidian30 = 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_obsidian30.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_obsidian31 = 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_obsidian31.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_obsidian32 = 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_obsidian32.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_obsidian32.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/utils/smart-context/template_presets.js var DEFAULT_TEMPLATE_PRESET = "xml_structured"; var template_presets = { xml_structured: { label: "XML-style (default)", context_template_before: "\n{{FILE_TREE}}", context_template_after: "", item_template_before: '', item_template_after: "" }, markdown_headings: { label: "Markdown headings", context_template_before: "{{FILE_TREE}}", context_template_after: "", item_template_before: [ "## {{KEY}}", "Updated: {{TIME_AGO}} | Depth: {{LINK_DEPTH}}", "````{{EXT}}" ].join("\n"), item_template_after: "````\n" }, json_structured: { label: "JSON structured", context_template_before: '{\n "context": {', context_template_after: " }\n}", item_template_before: ' "{{KEY}}": { "name": "{{ITEM_NAME}}", "updated": "{{TIME_AGO}}", "depth": {{LINK_DEPTH}}, "content": ', item_template_after: " },", json_stringify: true }, // PRO custom: { label: "Custom (PRO)" } }; var get_preset_key = (settings = {}) => { const preset_key = settings.template_preset || DEFAULT_TEMPLATE_PRESET; if (template_presets[preset_key]) return preset_key; return "custom"; }; var get_template_value = (settings, defaults, preset_field_key, settings_field_key) => { const preset_key = get_preset_key(settings); const preset = template_presets[preset_key]; if (preset_key !== "custom" && preset && typeof preset[preset_field_key] === "string") { return preset[preset_field_key]; } return settings?.[settings_field_key] || defaults?.[settings_field_key]; }; function get_template_preset_options() { return Object.entries(template_presets).map(([value, config]) => ({ value, label: config.label || value })); } function get_context_templates(settings = {}, defaults = {}) { return { template_before: get_template_value(settings, defaults, "context_template_before", "template_before"), template_after: get_template_value(settings, defaults, "context_template_after", "template_after") }; } function get_item_templates(settings = {}, defaults = {}) { const preset_key = get_preset_key(settings); const preset = template_presets[preset_key]; return { ...preset_key === "custom" ? { json_stringify: settings.json_stringify } : {}, // only include settings.json_stringify if custom ...preset && typeof preset === "object" ? preset : {}, // include all preset fields template_before: get_template_value(settings, defaults, "item_template_before", "template_before"), template_after: get_template_value(settings, defaults, "item_template_after", "template_after") }; } // node_modules/obsidian-smart-env/src/actions/context-item/merge_template.js var derive_item_name_from_key = (key = "") => { if (typeof key !== "string" || key.trim().length === 0) return ""; const [filename_with_fragment] = key.split(/[\\/]/).slice(-1); const [source_name, ...block_parts] = (filename_with_fragment || "").split("#"); const src_no_ext = source_name.includes(".") ? source_name.slice(0, source_name.lastIndexOf(".")) : source_name; if (block_parts.length > 0) { return `${src_no_ext}#${block_parts.join("#")}`; } return src_no_ext; }; var get_item_name = (context_item) => { return derive_item_name_from_key(context_item.key); }; async function merge_template(item_text, params = {}) { const MERGE_VARS = { "KEY": this.key, "ITEM_NAME": get_item_name(this), "TIME_AGO": convert_to_time_ago(this.mtime) || "Missing", "LINK_DEPTH": this.data.d || "0", "EXT": this.item_ref?.file_type || "" }; 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 templates = get_item_templates(this.settings, default_settings2); if (params.json_stringify || templates.json_stringify) { item_text = JSON.stringify(item_text); } const before = await replace_vars(templates.template_before); const after = await replace_vars(templates.template_after); return ["", before, item_text, after, ""].join("\n"); } var settings_config7 = { template_preset: { group: "Item templates", type: "dropdown", name: "Select template", description: "Wraps each context item with a pre-configured template.", options_callback: () => get_template_preset_options() }, template_before: { group: "Item templates", type: "textarea", name: "Template Before", description: "Template to wrap before the context item content.", scope_class: "pro-setting" }, template_after: { group: "Item templates", type: "textarea", name: "Template After", description: "Template to wrap after the context item content.", scope_class: "pro-setting" }, item_explanation: { type: "html", group: "Item templates", value: ` Available variables:
    • {{KEY}} - Full path of the item
    • {{ITEM_NAME}} - Source file or block name without folder path or file extension
    • {{TIME_AGO}} - Time since the item was last modified
    • {{LINK_DEPTH}} - Depth level of the item
    • {{EXT}} - File extension of the item
    ` }, json_stringify: { group: "Item templates", type: "toggle", name: "JSON Stringify", description: "Convert the item content to a JSON string (forces full content into single line in quotes).", scope_class: "pro-setting" } }; var default_settings2 = { template_preset: "xml_structured", 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, params = {}) { const context_items = params.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 templates = get_context_templates(this.settings, default_settings3); const before = await replace_vars(templates.template_before); const after = await replace_vars(templates.template_after); return [before, context_items_text, after].join("\n"); } var settings_config8 = { template_preset: { type: "dropdown", group: "Context templates", name: "Select template", description: "Wraps the full context with a pre-configured template.", options_callback: () => get_template_preset_options() }, template_before: { type: "textarea", group: "Context templates", name: "Template Before", description: "Template to wrap before the context.", scope_class: "pro-setting" }, template_after: { type: "textarea", group: "Context templates", name: "Template After", description: "Template to wrap after the context.", scope_class: "pro-setting" }, context_explanation: { type: "html", group: "Context templates", value: `Available variables:
    • {{FILE_TREE}} - Shows hierarchical view of all files
    ` } }; var default_settings3 = { template_preset: "xml_structured", 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_obsidian33 = 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_obsidian33.Keymap.isModEvent(event); const is_alt = import_obsidian33.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_obsidian36.requestUrl }) }, http_adapter: { class: SmartHttpRequest, adapter: SmartHttpObsidianRequestAdapter, obsidian_request_url: import_obsidian36.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_obsidian37 = require("obsidian"); function add_smart_chat_icon() { (0, import_obsidian37.addIcon)("smart-chat", ` `); } function add_smart_connections_icon() { (0, import_obsidian37.addIcon)("smart-connections", ` `); } function add_smart_lookup_icon() { (0, import_obsidian37.addIcon)("smart-lookup", ` `); } // node_modules/obsidian-smart-env/node_modules/smart-notices/smart_notices.js var import_obsidian38 = 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_obsidian41.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_obsidian41.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_obsidian41.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_obsidian41.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"] }); remove_smart_plugins_plugin({ app: this.plugin.app, plugin_ids: ["smart-claude"] }); remove_smart_plugins_plugin({ app: this.plugin.app, plugin_ids: ["smart-gemini"] }); remove_smart_plugins_plugin({ app: this.plugin.app, plugin_ids: ["smart-deepseek"] }); remove_smart_plugins_plugin({ app: this.plugin.app, plugin_ids: ["smart-perplexity"] }); remove_smart_plugins_plugin({ app: this.plugin.app, plugin_ids: ["smart-grok"] }); remove_smart_plugins_plugin({ app: this.plugin.app, plugin_ids: ["smart-aistudio"] }); } }; 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_obsidian43 = require("obsidian"); // node_modules/obsidian-smart-env/utils/wait_for_env_to_load.js var import_obsidian42 = 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_obsidian42.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_obsidian43.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_obsidian43.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_obsidian44 = require("obsidian"); var SmartPlugin = class extends import_obsidian44.Plugin { SmartEnv = SmartEnv2; /** * 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; } /** * @deprecated use SmartEnv.notices instead */ get notices() { if (this.env?.notices) return this.env.notices; if (!this._notices) this._notices = new SmartNotices(this.env, import_obsidian44.Notice); return this._notices; } }; // 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_obsidian46 = require("obsidian"); // node_modules/obsidian-smart-env/src/modals/story.js var import_obsidian45 = 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_obsidian45.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_obsidian45.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/connections_context_items.js function build_connections_context_items(params = {}) { const { source_item, results = [] } = params; const items = []; const seen_keys = /* @__PURE__ */ new Set(); const append_item = (item, score) => { const key = item?.key; if (!key || seen_keys.has(key)) return; seen_keys.add(key); items.push({ key, score }); }; if (source_item) append_item(source_item, source_item.score ?? 1); results.forEach((result) => append_item(result?.item, result?.score)); return items; } // 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_obsidian46.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); const context_items = build_connections_context_items({ source_item: connections_list?.item, results: visible_results }); if (!context_items.length) return new import_obsidian46.Notice("No visible connection results to send to Smart Context"); const smart_context = env.smart_contexts.new_context(); smart_context.add_items(context_items); 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_obsidian46.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_obsidian46.Notice("No visible connection results to copy"); await copy_to_clipboard2(links_payload); new import_obsidian46.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_obsidian47 = 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_obsidian47.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_obsidian47.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_obsidian47.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_obsidian47.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_obsidian47.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_obsidian47.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_obsidian48 = 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_obsidian48.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_obsidian49 = 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_obsidian49.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) => { const context_items = build_connections_context_items({ source_item: connections_item, results: visible_results }); menu_item.setTitle("Send results to Smart Context").setIcon("briefcase").setDisabled(!context_items.length).onClick(async () => { if (!context_items.length) return new import_obsidian49.Notice("No connection results to send to Smart Context"); const smart_context = env.smart_contexts.new_context(); smart_context.add_items(context_items); 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_obsidian49.Notice("No connection results to copy"); await copy_to_clipboard2(links_payload); new import_obsidian49.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_obsidian50 = 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_obsidian50.Keymap.isModEvent(event); const is_alt = import_obsidian50.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); } } // src/views/settings_tab.js var ScEarlySettingsTab = class extends SmartPluginSettingsTab { constructor(app, plugin) { super(app, plugin); this.plugin = plugin; } hide() { super.hide?.(); this.plugin_container?.empty?.(); this.turn_off_listener?.(); } async render_header(container) { const header = await this.env.smart_components.render_component("connections_settings_header", this.plugin); container.appendChild(header); } async render_plugin_settings(container) { if (!container) return; container.empty?.(); container.innerHTML = '
    Loading main settings...
    '; container.empty?.(); const cl_container = container.createDiv({ cls: "sc-settings-tab__section", attr: { "data-section-key": "connections_lists" } }); cl_container.createEl("h1", { text: "Connections" }); const connections_lists_settings_config = this.env.config.collections.connections_lists.settings_config; render_settings_config( connections_lists_settings_config, this.env.connections_lists, cl_container, { default_group_name: "Connections lists", group_params: { "Connections lists": { heading_btn: [ { label: "Learn about Connections Lists", btn_text: "Learn more", callback: () => window.open("https://smartconnections.app/smart-connections/list-feature/?utm_source=connections-settings-tab", "_external") }, { label: "Settings documentation for Connections Lists", btn_icon: "help-circle", callback: () => window.open("https://smartconnections.app/smart-connections/settings/?utm_source=connections-settings-tab#connections-lists", "_external") } ] }, "Display": { heading_btn: { label: "Settings documentation for Display", btn_icon: "help-circle", callback: () => window.open("https://smartconnections.app/smart-connections/settings/?utm_source=connections-settings-tab#display", "_external") } }, "Score algorithm": { heading_btn: { label: "Settings documentation for Score Algorithms", btn_icon: "help-circle", callback: () => window.open("https://smartconnections.app/smart-connections/settings/?utm_source=connections-settings-tab#score-algorithm", "_external") } }, "Ranking algorithm": { heading_btn: { label: "Settings documentation for Ranking Algorithms", btn_icon: "help-circle", callback: () => window.open("https://smartconnections.app/smart-connections/settings/?utm_source=connections-settings-tab#ranking-algorithm", "_external") } }, "Filters": { heading_btn: { label: "Settings documentation for Filters", btn_icon: "help-circle", callback: () => window.open("https://smartconnections.app/smart-connections/settings/?utm_source=connections-settings-tab#filters", "_external") } }, "Inline connections": { heading_btn: [ { label: "Learn about the inline connections feature", btn_text: "Learn more", callback: () => window.open("https://smartconnections.app/smart-connections/inline/?utm_source=connections-settings-tab", "_external") }, { label: "Settings documentation for inline connections", btn_icon: "help-circle", callback: () => window.open("https://smartconnections.app/smart-connections/settings/?utm_source=connections-settings-tab#inline-connections", "_external") } ] }, "Footer connections": { heading_btn: { label: "Settings documentation for Footer Connections", btn_icon: "help-circle", callback: () => window.open("https://smartconnections.app/smart-connections/settings/?utm_source=connections-settings-tab#footer-connections", "_external") } } } } ); const ll_container = container.createDiv({ cls: "sc-settings-tab__section", attr: { "data-section-key": "lookup_lists" } }); const lookup_lists_settings_config = this.env.config.collections.lookup_lists.settings_config; render_settings_config( lookup_lists_settings_config, this.env.lookup_lists, ll_container, { default_group_name: "Lookup lists", group_params: { "Lookup lists": { heading_btn: [ { label: "Learn about Lookup Lists", btn_text: "Learn more", callback: () => window.open("https://smartconnections.app/smart-connections/lookup/?utm_source=connections-settings-tab", "_external") }, { label: "Settings documentation for Lookup Lists", btn_icon: "help-circle", callback: () => window.open("https://smartconnections.app/smart-connections/settings/?utm_source=connections-settings-tab#lookup-lists", "_external") } ] } } } ); this.register_env_events(); } register_env_events() { if (this.turn_off_listener || !this.env?.events) return; this.turn_off_listener = this.env.events.on("settings:changed", (event) => { if (event.path?.includes("connections_post_process") || event.path?.includes("score_algo_key") || event.path?.includes("connections_list_item")) { this.render_plugin_settings(this.plugin_container); } }); } }; // src/views/release_notes_view.js var import_obsidian52 = require("obsidian"); // node_modules/obsidian-smart-env/views/smart_item_view.js var import_obsidian51 = require("obsidian"); var SmartItemView = class extends import_obsidian51.ItemView { /** * Creates an instance of SmartItemView. * @param {any} leaf * @param {any} plugin */ constructor(leaf, plugin) { super(leaf); this.app = plugin.app; this.plugin = plugin; } /** * The unique view type. Must be implemented in subclasses. * @returns {string} */ static get view_type() { throw new Error("view_type must be implemented in subclass"); } /** * The display text for this view. Must be implemented in subclasses. * @returns {string} */ static get display_text() { throw new Error("display_text must be implemented in subclass"); } /** * The icon name for this view. * @returns {string} */ static get icon_name() { return "smart-connections"; } /** * Registers this ItemView subclass against a plugin instance and * installs ergonomic accessors, an open helper, and an `${view_type}:open` listener. * * Usage from a plugin class: * SubClass.register_item_view(this); * * This will: * - call plugin.registerView(view_type, ...) * - add a command "Open: view" * - define a getter on plugin: plugin[method_name] -> the view instance * - define a method on plugin: plugin["open_" + method_name]() -> opens the view * - listen for env events named `${view_type}:open` and open the view when emitted * * @param {import('obsidian').Plugin} plugin * @returns {{method_name:string, open_method_name:string, event_name:string}} */ static register_item_view(plugin) { const View = ( /** @type {typeof SmartItemView} */ this ); plugin.registerView(View.view_type, (leaf) => new View(leaf, plugin)); plugin.addCommand({ id: View.view_type, name: "Open: " + View.display_text + " view", callback: () => { View.open(plugin.app.workspace); } }); const method_name = View.view_type.replace(/^smart-/, "").replace(/-/g, "_"); const open_method_name = "open_" + method_name; if (!Object.getOwnPropertyDescriptor(plugin, method_name)) { Object.defineProperty(plugin, method_name, { configurable: true, enumerable: false, get: () => View.get_view(plugin.app.workspace) }); } plugin[open_method_name] = () => View.open(plugin.app.workspace); const event_name = `${method_name}:open`; const handler = (payload) => { const active = typeof payload?.active === "boolean" ? payload.active : true; View.open(plugin.app.workspace, active); }; const unsubscribe = plugin?.env?.events.on(event_name, handler); if (typeof plugin.register === "function" && typeof unsubscribe === "function") { plugin.register(() => unsubscribe()); } return { method_name, open_method_name, event_name }; } /** * Retrieves the Leaf instance for this view type if it exists. * @param {import("obsidian").Workspace} workspace * @returns {import("obsidian").WorkspaceLeaf | undefined} */ static get_leaf(workspace) { return workspace.getLeavesOfType(this.view_type)[0]; } /** * Retrieves the view instance if it exists. * @param {import("obsidian").Workspace} workspace * @returns {SmartItemView | undefined} */ static get_view(workspace) { const leaf = this.get_leaf(workspace); return leaf ? leaf.view : void 0; } /** * Opens the view. If `this.default_open_location` is "root", * it opens (or reveals) in a root leaf; otherwise in the right leaf. * * @param {import("obsidian").Workspace} workspace * @param {boolean} [active=true] */ static open(workspace, params = {}) { const { active = true } = params; const existing_leaf = this.get_leaf(workspace); if (this.default_open_location === "root") { if (existing_leaf) { existing_leaf.setViewState({ type: this.view_type, active }); } else { workspace.getLeaf(false).setViewState({ type: this.view_type, active }); } } else { if (existing_leaf) { existing_leaf.setViewState({ type: this.view_type, active }); } else { workspace.getRightLeaf(false).setViewState({ type: this.view_type, active }); } if (workspace.rightSplit?.collapsed) { workspace.rightSplit.toggle(); } } setTimeout(() => { this.get_view(workspace)?.render_view(params); }, 100); } static is_open(workspace) { return this.get_leaf(workspace)?.view instanceof this; } // instance getViewType() { return this.constructor.view_type; } getDisplayText() { return this.constructor.display_text; } getIcon() { return this.constructor.icon_name; } async onOpen() { this.app.workspace.onLayoutReady(this.initialize.bind(this)); } async initialize() { await wait_for_env_to_load(this); this.container.empty(); this.register_plugin_events(); this.app.workspace.registerHoverLinkSource(this.constructor.view_type, { display: this.getDisplayText(), defaultMod: true }); this.render_view(); if (import_obsidian51.Platform.isMobile) { const status_bar_container = this.containerEl.querySelector(".status-bar-mobile") ?? this.containerEl.createDiv({ cls: "status-bar-mobile" }); ; status_bar_container.empty(); const status_bar_item = status_bar_container.createDiv({ cls: "status-bar-item" }); this.env.smart_components.render_component("status_bar", this.env).then((el) => status_bar_item.appendChild(el)); } } register_plugin_events() { } render_view(params = {}) { throw new Error("render_view must be implemented in subclass"); } get container() { return this.containerEl.children[1]; } get env() { return this.plugin.env; } async open_settings() { await this.app.setting.open(); await this.app.setting.openTabById(this.plugin.manifest.id); } }; // releases/latest_release.md var latest_release_default = '> [!NOTE] Patch v4.1.7\n> - Added: Links to docs from [milestones](https://smartconnections.app/smart-environment/milestones/?utm_source=release-notes)\n> - Added: Include active source item in context created from the connections view\n> - Fixed: replaced model source for multilingual E5 Small embedding model to ensure quantized variations available\n\n> [!NOTE]- Previous patches\n> > [!NOTE]- v4.1.6\n> > - Added: milestones feature with modal and checklist components\n> > - Added: connections view menu option to copy results as links \n> > - Added: multilingual-e5-small embedding model support\n> > - Improved: settings tab with added "learn more" and "help" buttons to settings groups\n> > - Fixed: markdown parser should handle frontmatter correctly (prevent false-positive frontmatter detection)\n> \n> > [!NOTE]- v4.1.4\n> > - Added fallback for opening Pro login in case the button doesn\'t automatically open the browser as expected\n> \n> > [!NOTE]- v4.1.3\n> > - Added link to documentation in settings for easier access\n> > - Fixed highlight "Reset data" after embedding model change\n> \n> > [!NOTE]- v4.1.2\n> > - Improved model configuration UX\n> > - Added "Delete" functionality to better manage models\n> > - Enhanced UI for settings and improved styling\n> > - Pro: Updated score algorithm settings to clarify descriptions\n> \n> > [!NOTE]- v4.1.1\n> > - Connections codeblock view should render as expected without errors\n> \n> \n# Smart Connections `v4`\r\n\r\n## What\'s new in v4\r\n\r\nSmart Connections v4 focuses the core plugin on a simple promise: install, enable, and AI-powered connections just work. Advanced configuration and power-user workflows now live in Pro plugins. Read [Introducing Pro Plugins](https://smartconnections.app/introducing-pro-plugins/?utm_source=connections-release-notes) to learn more.\r\n\r\n### Pause connections\r\n\r\nUse the new Connections "pause" button to freeze the connections results. This allows you to move through your vault while keeping the connections to a specific note visible while you work.\r\n\r\n### Copy connections as list of links\r\n\r\nRight-click the connections results to *copy all links* to clipboard.\r\n\r\n### Copy all connections content (Context Engineering)\r\n\r\nClick the connections view menu button and "Send to Smart Context" (briefcase icon) option. This allows you to quickly copy *all content from the connections* to clipboard for use as context with any AI chat! The Smart Context view also lets you add or remove items before copying all to the clipboard in one-click!\r\n\r\n### Pinned connections\r\n\r\nIn addition to "hiding" connections, you can now "Pin" connections. This ensures the pinned connections are always visible in the connections view. **Connections Pro:** *Hidden and pinned connections are used by new connections algorithms (available in Pro) to improve results!*\r\n\r\n### Events and notifications\r\n\r\nImportant events are now surfaced in a dedicated notifications modal:\r\n\r\n- On desktop, click the Smart Env item in the status bar to open the notifications modal. \r\n- On mobile, a Smart Environment notice appears at the bottom of the Connections view; tap it to review events.\r\n\r\nExamples of events you might see:\r\n\r\n- Initial indexing complete for your vault \r\n- Sources reimported after model changes \r\n- Warnings when exclusions block indexing on specific folders or files \r\n\r\nObjectives of the new Events system:\r\n\r\n- make the environment inspectable and understandable\r\n- reduce the number of Obsidian native notifications\r\n\r\n### Connections Pro\r\n\r\nConnections Pro builds on the core plugin and Smart Environment to give power users more control.\r\n\r\n![](https://smartconnections.app/assets/connections-view-pro-notes.gif)\r\n\r\nExamples of Pro features:\r\n\r\n- **Inline connections** \r\n Small badges in the editor that show how many strong matches a block has, with a pop-over of related blocks and notes. \r\n- **Footer connections** \r\n A persistent panel that updates as you type so high value connections stay visible while you write. \r\n- **Configurable scoring and ranking** \r\n Choose different algorithms for how results are scored and optionally add a rerank stage. \r\n- **Connections in Bases** \r\n Use `score_connection` and `list_connections` in Obsidian Bases to show similarity columns and related note lists in tables. \r\n- **Advanced filters and models** \r\n Extra Smart Environment controls for embeddings, collections, and include or exclude rules. \r\n- **Early release experiments** \r\n New ideas launch in Early channels first so supporters can shape how they evolve.\r\n\r\nConnections Pro is part of the [Pro plugins](https://smartconnections.app/pro-plugins/?utm_source=connections-release-notes) family and is available to active project supporters. It is still built on the same open Smart Environment. Supporting Pro helps fund development of all Smart Plugins and the free core.\n'; // src/views/release_notes_view.js var ReleaseNotesView = class _ReleaseNotesView extends SmartItemView { static get view_type() { return "smart-release-notes-view"; } static get display_text() { return "Release Notes"; } static get icon_name() { return "file-text"; } static open(workspace, version, active = true) { const leaf = workspace.getLeaf("tab"); leaf.setViewState({ type: this.view_type, active, state: { version } }); } /* ───────────────────────────── item-view API ───────────────────────────── */ getViewType() { return _ReleaseNotesView.view_type; } getDisplayText() { return _ReleaseNotesView.display_text; } getIcon() { return _ReleaseNotesView.icon_name; } /** * Build the preview container & render markdown using core renderer so the * output styles match native note preview exactly. */ onOpen() { this.titleEl.setText(`What\u2019s new in v${this.version}`); this.render(); } get container() { const content = this.containerEl?.querySelector(".view-content"); let preview = content?.querySelector(".markdown-preview-view"); if (!preview) { const main = content?.createDiv("cm-scroller is-readable-line-width"); preview = main?.createDiv("markdown-preview-view markdown-rendered"); } return preview; } async render() { while (!this.container) { await new Promise((resolve) => setTimeout(resolve, 100)); console.warn("Waiting for containerEl to be ready...", this.container); } await import_obsidian52.MarkdownRenderer.render( this.app, latest_release_default, this.container, "", this ); this.container.querySelectorAll("a").forEach((a) => { a.setAttribute("target", "_external"); }); requestAnimationFrame(() => this.#scroll_to_version(this.container, this.version)); } get version() { const version = this.leaf.viewState?.state?.version ?? this.app.plugins.getPlugin("smart-connections")?.manifest.version ?? ""; return version; } #scroll_to_version(container, version) { if (!version) return; const safe = version.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); const matcher = new RegExp(`\\bv?${safe}\\b`, "i"); container.querySelectorAll("h1,h2,h3,h4,h5,h6").forEach((h) => { if (matcher.test(h.textContent ?? "")) { h.scrollIntoView({ behavior: "smooth", block: "start" }); } }); } }; // src/utils/get_random_connection.js var DEFAULT_RESULTS_LIMIT = 20; async function get_random_connection(env, file_path, { rng = Math.random } = {}) { if (!env?.smart_sources || !file_path) return null; const source = env.smart_sources.get(file_path); if (!source?.should_embed) return null; const connections = await source.connections.get_results({ limit: DEFAULT_RESULTS_LIMIT }); if (!Array.isArray(connections) || connections.length === 0) return null; return pick_weighted_connection(connections, { rng }); } function pick_weighted_connection(connections, { rng }) { const scored_connections = connections.map((connection) => ({ connection, score: Math.max(0, typeof connection?.score === "number" ? connection.score : 0) })); const total_score = scored_connections.reduce((sum, { score }) => sum + score, 0); if (total_score === 0) return connections[0]; const threshold = rng() * total_score; let cumulative = 0; for (const { connection, score } of scored_connections) { cumulative += score; if (threshold < cumulative) return connection; } return connections.at(-1); } // src/utils/add_icons.js var import_obsidian53 = require("obsidian"); function add_smart_dice_icon() { (0, import_obsidian53.addIcon)("smart-dice", ` `); } // src/utils/pause_controls.js function apply_pause_state(target, paused) { const value = Boolean(paused); target.paused = value; target.pause_controls?.update?.(value); return value; } function toggle_pause_state(target) { const next = !target.paused; target.paused = next; target.pause_controls?.update?.(next); return next; } // src/views/connections_item_view.js var ConnectionsItemView = class extends SmartItemView { static get view_type() { return "smart-connections-view"; } static get display_text() { return "Connections"; } static get icon_name() { return "smart-connections"; } constructor(leaf, plugin) { super(leaf, plugin); this.paused = false; this.pause_controls = null; this.current = null; } async render_view(params = {}, container = this.container) { if (!params.connections_item) { const active_path = this.plugin.app.workspace.getActiveFile()?.path; params.connections_item = this.env.smart_sources.get(active_path); } this.current = params.connections_item; this.pause_controls = null; const frag = await this.env.smart_components.render_component("connections_view_v3", this, { connections_item: params.connections_item }); container.empty(); container.appendChild(frag); this.register_env_listeners(); this.env.events.emit("connections:opened"); } async open_settings() { await this.app.setting.open(); await this.app.setting.openTabById(this.plugin.manifest.id); } register_env_listeners() { let handle_current_source_debounce; register_env_event_listener(this, "sources:opened", (event = {}) => { if (this.paused) return; if (!is_visible(this.container)) return; const connections_item = this.env[event.collection_key || "smart_sources"]?.get(event.item_key || event.key); if (connections_item.key === this.current?.key) return; if (handle_current_source_debounce) clearTimeout(handle_current_source_debounce); handle_current_source_debounce = setTimeout(() => { this.render_view({ connections_item }); }, 250); }); register_env_event_listener(this, "settings:changed", (event) => { if (event.path?.includes("expanded_view")) return; if (event.path?.includes("connections_lists") && is_visible(this.container)) { this.render_view({ connections_item: this.current }); } }); register_env_event_listener(this, "connections:show", (event) => { console.log("connections:show event received", { event }); if (event.collection_key && event.item_key) { const collection = this.env[event.collection_key]; const item = collection.get(event.item_key); console.log({ collection, item }); if (item) { this.set_connections_paused(true); this.render_view({ connections_item: item }); } } }); register_env_event_listener(this, "item:embedded", (event) => { if (event.item_key === this.current?.key && is_visible(this.container)) { this.render_view({ connections_item: this.current }); } }); } /** * Register UI controls for reflecting pause state. * @param {{ update: (paused: boolean) => void }} controls */ register_pause_controls(controls) { this.pause_controls = controls; this.pause_controls?.update(this.paused); } /** * Set the paused state and sync UI controls. * @param {boolean} paused * @returns {boolean} */ set_connections_paused(paused) { return apply_pause_state(this, paused); } /** * Toggle the paused state and sync UI controls. * @returns {boolean} */ toggle_connections_paused() { return toggle_pause_state(this); } }; function is_visible(container) { if (!container) { return false; } if (!container.isConnected) { console.warn("Connections container is not connected to DOM"); return false; } if (typeof container.checkVisibility === "function" && container.checkVisibility() === false) { console.log("Connections container is not visible"); return false; } return true; } var view_event_registry = /* @__PURE__ */ new WeakMap(); var get_registry = (view) => { if (!view_event_registry.has(view)) { view_event_registry.set(view, /* @__PURE__ */ new Map()); } return view_event_registry.get(view); }; var register_env_event_listener = (view, event_key, callback) => { if (!view || typeof view.env?.events?.on !== "function") { console.warn("View or event system not available for registering event listener"); return () => { }; } const registry = get_registry(view); const previous_dispose = registry.get(event_key); if (typeof previous_dispose === "function") { previous_dispose(); } const off = view.env.events.on(event_key, (event) => { callback(event); }); let active = true; const dispose = () => { if (!active) return; active = false; off?.(); if (registry.get(event_key) === dispose) { registry.delete(event_key); } }; registry.set(event_key, dispose); if (typeof view.register === "function") { view.register(() => dispose()); } return dispose; }; // src/views/lookup_item_view.js var LookupItemView = class extends SmartItemView { static get view_type() { return "smart-lookup-view"; } static get display_text() { return "Lookup"; } static get icon_name() { return "smart-lookup"; } async render_view(lookup_params, container = this.container) { const frag = await this.env.render_component("lookup_item_view", this, lookup_params); container.empty(); container.appendChild(frag); } }; // src/views/connections_codeblock.js async function register_smart_connections_codeblock(plugin) { plugin.registerMarkdownCodeBlockProcessor( "smart-connections", async (cb_content, container, mpp_ctx) => { container.empty(); container.createEl("span", { text: "Loading\u2026" }); const cb_config = JSON.parse(cb_content.trim() || "{}"); const env = plugin.env; const entity = env.smart_sources.get(mpp_ctx.sourcePath) ?? env.smart_sources.init_file_path(mpp_ctx.sourcePath); const smart_view = env.smart_view; if (!entity) { container.empty(); container.createEl("p", { text: "Entity not found: " + mpp_ctx.sourcePath }); return; } const render_codeblock = async () => { const connections_list = entity.connections; if (!connections_list?.env) { container.empty(); container.createEl("p", { text: "Smart Environment / Connections loading\u2026" }); const retry_button = container.createEl("button", { text: "Retry" }); retry_button.addEventListener("click", () => { render_codeblock(); }); return; } const connections_container = await plugin.env.smart_components.render_component( "connections_codeblock", connections_list, { ...cb_config // FUTURE: handling codeblock config options } ); container.empty(); container.appendChild(connections_container); }; if (!container._has_listeners) { container._has_listeners = true; const disposers = []; disposers.push(env.events.on("settings:changed", (event) => { console.log("connections codeblock view detected settings change", event); if (event.path?.includes("connections_lists")) { render_codeblock(); } })); smart_view.attach_disposer(container, disposers); } render_codeblock(); } ); } // src/utils/build_connections_codeblock.js function build_connections_codeblock(settings = null) { const json = settings ? JSON.stringify(settings, null, 2) : ""; return `\`\`\`smart-connections ${json} \`\`\` `; } // src/main.js var { Notice: Notice13, Plugin: Plugin2, requestUrl: requestUrl7, Platform: Platform10 } = import_obsidian54.default; var SmartConnectionsPlugin = class extends SmartPlugin { SmartEnv = SmartEnv2; get smart_env_config() { if (!this._smart_env_config) { this._smart_env_config = smart_env_config3; } return this._smart_env_config; } ConnectionsSettingsTab = ScEarlySettingsTab; get item_views() { return { ConnectionsItemView, LookupItemView, ReleaseNotesView }; } // GETTERS get obsidian() { return import_obsidian54.default; } get api() { return this._api; } onload() { this.app.workspace.onLayoutReady(this.initialize.bind(this)); this.SmartEnv.create(this, this.smart_env_config); this.addSettingTab(new this.ConnectionsSettingsTab(this.app, this)); add_smart_dice_icon(); this.register_commands(); this.register_item_views(); this.register_ribbon_icons(); } // async onload() { this.app.workspace.onLayoutReady(this.initialize.bind(this)); } // initialize when layout is ready onunload() { console.log("Unloading Smart Connections plugin"); this.notices?.unload(); this.env?.unload_main?.(this); } async initialize() { this.smart_connections_view = null; this.is_new_user().then(async (is_new) => { if (!is_new) return; setTimeout(() => { StoryModal.open(this, { title: "Getting Started With Smart Connections", url: "https://smartconnections.app/story/smart-connections-getting-started/?utm_source=sc-op-new-user" }); }, 1e3); await this.SmartEnv.wait_for({ loaded: true }); setTimeout(() => { this.open_connections_view(); if (this.app.workspace.rightSplit.collapsed) this.app.workspace.rightSplit.toggle(); }, 1e3); this.add_to_gitignore("\n\n# Ignore Smart Environment folder\n.smart-env"); }); await this.SmartEnv.wait_for({ loaded: true }); register_smart_connections_codeblock(this); await this.check_for_updates(); } /** * Initialize ribbon icons with default visibility. */ get ribbon_icons() { return { connections: { icon_name: "smart-connections", description: "Smart Connections: Open connections view", callback: () => { this.open_connections_view(); } }, lookup: { icon_name: "smart-lookup", description: "Smart Lookup: Open lookup view", callback: () => { this.open_lookup_view(); } }, random_note: { icon_name: "smart-dice", description: "Smart Connections: Open random connection", callback: () => { this.open_random_connection(); } } }; } get settings() { return this.env?.settings || {}; } async check_for_updates() { if (await this.is_new_plugin_version(this.manifest.version)) { console.log("opening release notes modal"); try { ReleaseNotesView.open(this.app.workspace, this.manifest.version); } catch (e) { console.error("Failed to open ReleaseNotesView", e); } await this.set_last_known_version(this.manifest.version); } setTimeout(this.check_for_update.bind(this), 3e3); setInterval(this.check_for_update.bind(this), 108e5); } async check_for_update() { try { const { json: response } = await requestUrl7({ url: "https://api.github.com/repos/brianpetro/obsidian-smart-connections/releases/latest", method: "GET", headers: { "Content-Type": "application/json" }, contentType: "application/json" }); const latest_release = response.tag_name; if (latest_release !== this.manifest.version) { this.env?.events?.emit("plugin:new_version_available", { version: latest_release }); this.notices?.show("new_version_available", { version: latest_release }); this.update_available = true; } } catch (error) { console.error(error); } } async restart_plugin() { this.env?.unload_main?.(this); await new Promise((r) => setTimeout(r, 3e3)); window.restart_plugin = async (id) => { await window.app.plugins.disablePlugin(id); await window.app.plugins.enablePlugin(id); }; await window.restart_plugin(this.manifest.id); } get commands() { return { ...super.commands, random_connection: { id: "smart-connections-random", name: "Open: Random note from connections", callback: async () => { await this.open_random_connection(); } }, getting_started: { id: "smart-connections-getting-started", name: "Show: Getting started slideshow", callback: () => { StoryModal.open(this, { title: "Getting Started With Smart Connections", url: "https://smartconnections.app/story/smart-connections-getting-started/?utm_source=sc-op-command" }); } }, insert_connections_codeblock: { id: "insert-connections-codeblock", name: "Insert: Connections codeblock", editorCallback: (editor) => { editor.replaceSelection(build_connections_codeblock()); } } }; } show_release_notes() { return ReleaseNotesView.open(this.app.workspace, this.manifest.version); } async open_random_connection() { const curr_file = this.app.workspace.getActiveFile(); if (!curr_file) { new Notice13("No active file to find connections for"); return; } const rand_entity = await get_random_connection(this.env, curr_file.path); if (!rand_entity) { new Notice13("Cannot open random connection for non-embedded source: " + curr_file.path); return; } this.open_note(rand_entity.item.path); this.env?.events?.emit?.("connections:open_random"); } async open_note(target_path, event = null) { await open_note(this, target_path, event); } /** * @deprecated extract into utility */ async add_to_gitignore(ignore, message = null) { if (!await this.app.vault.adapter.exists(".gitignore")) return; let gitignore_file = await this.app.vault.adapter.read(".gitignore"); if (gitignore_file.indexOf(ignore) < 0) { await this.app.vault.adapter.append(".gitignore", ` ${message ? "# " + message + "\n" : ""}${ignore}`); console.log("Added to .gitignore: " + ignore); } } }; /* nosourcemap */