Remove main.js from tracking and update gitignore

This commit is contained in:
djayatillake 2025-01-23 23:04:57 +00:00
parent 484cfa44f8
commit 4ee9389d2c
3 changed files with 45 additions and 495 deletions

2
.gitignore vendored
View file

@ -8,6 +8,8 @@ yarn-error.log*
dist/
build/
*.js.map
main.js
data.json
# IDE and editor files
.idea/

489
main.js
View file

@ -1,489 +0,0 @@
/*
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
if you want to view the source, please visit the github repository of this plugin
*/
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
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 __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// main.ts
var main_exports = {};
__export(main_exports, {
default: () => LLMTaggerPlugin
});
module.exports = __toCommonJS(main_exports);
var import_obsidian = require("obsidian");
var ICON_NAME = "llm-tagger-robot";
var VIEW_TYPE = "llm-tagger-view";
var DEFAULT_SETTINGS = {
selectedModel: null,
defaultTags: [],
autoAddTags: false,
taggedFiles: {}
};
var LLMTaggerPlugin = class extends import_obsidian.Plugin {
constructor() {
super(...arguments);
this.autoTaggingEnabled = false;
}
async onload() {
console.log("Loading LLM Tagger plugin");
await this.loadSettings();
(0, import_obsidian.addIcon)(ICON_NAME, `<svg width="100" height="100" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 2C11.1 2 10.5 2.6 10.5 3.5V4H9C7.9 4 7 4.9 7 6V7H6.5C5.7 7 5 7.7 5 8.5V11.5C5 12.3 5.7 13 6.5 13H7V14C7 15.1 7.9 16 9 16H10V16.5C10 17.3 10.7 18 11.5 18H12.5C13.3 18 14 17.3 14 16.5V16H15C16.1 16 17 15.1 17 14V13H17.5C18.3 13 19 12.3 19 11.5V8.5C19 7.7 18.3 7 17.5 7H17V6C17 4.9 16.1 4 15 4H13.5V3.5C13.5 2.6 12.9 2 12 2Z" fill="currentColor"/>
<circle cx="9.5" cy="9.5" r="1.5" fill="currentColor"/>
<circle cx="14.5" cy="9.5" r="1.5" fill="currentColor"/>
<path d="M12 12C10.6 12 9.5 13.1 9.5 14.5H14.5C14.5 13.1 13.4 12 12 12Z" fill="currentColor"/>
<path d="M6 19H18V21H6V19Z" fill="currentColor"/>
</svg>`);
this.registerView(
VIEW_TYPE,
(leaf) => this.view = new LLMTaggerView(leaf, this)
);
this.addRibbonIcon(ICON_NAME, "LLM Tagger", () => {
this.activateView();
});
this.app.workspace.onLayoutReady(() => {
if (this.settings.autoAddTags) {
this.enableAutoTagging();
}
});
this.addCommand({
id: "add-tags",
name: "Add Tags to Documents",
callback: () => this.addTagsToDocuments()
});
this.addSettingTab(new LLMTaggerSettingTab(this.app, this));
console.log("LLM Tagger plugin loaded");
}
enableAutoTagging() {
if (this.autoTaggingEnabled)
return;
this.autoTaggingEnabled = true;
const debouncedAutoTag = (0, import_obsidian.debounce)(async (file) => {
await this.autoTagFile(file);
}, 2e3, true);
this.registerEvent(
this.app.vault.on("create", async (file) => {
if (this.autoTaggingEnabled && file instanceof import_obsidian.TFile && file.extension === "md") {
await debouncedAutoTag(file);
}
})
);
this.registerEvent(
this.app.vault.on("modify", async (file) => {
if (this.autoTaggingEnabled && file instanceof import_obsidian.TFile && file.extension === "md") {
await debouncedAutoTag(file);
}
})
);
}
disableAutoTagging() {
this.autoTaggingEnabled = false;
}
async autoTagFile(file) {
if (!this.settings.autoAddTags || !this.settings.selectedModel || !this.settings.defaultTags.length) {
return;
}
try {
const content = await this.app.vault.read(file);
if (!content.trim()) {
return;
}
const taggedContent = await this.processContentWithOllama(
content,
this.settings.defaultTags
);
if (taggedContent !== content) {
await this.app.vault.modify(file, taggedContent);
new import_obsidian.Notice(`Auto-tagged: ${file.basename}`);
this.settings.taggedFiles[file.path] = Date.now();
await this.saveSettings();
}
} catch (error) {
console.error("Error auto-tagging file:", error);
new import_obsidian.Notice(`Failed to auto-tag ${file.basename}: ${error.message}`);
}
}
async activateView() {
const { workspace } = this.app;
let leaf = workspace.getLeavesOfType(VIEW_TYPE)[0];
if (!leaf) {
const rightLeaf = workspace.getRightLeaf(false);
if (rightLeaf) {
await rightLeaf.setViewState({ type: VIEW_TYPE });
leaf = rightLeaf;
}
}
if (leaf) {
workspace.revealLeaf(leaf);
}
}
async onunload() {
console.log("Unloading LLM Tagger plugin");
this.disableAutoTagging();
this.app.workspace.detachLeavesOfType(VIEW_TYPE);
}
async loadSettings() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
}
async saveSettings() {
await this.saveData(this.settings);
if (this.settings.autoAddTags) {
this.enableAutoTagging();
} else {
this.disableAutoTagging();
}
}
async getOllamaModels() {
var _a;
try {
const response = await fetch("http://localhost:11434/api/tags");
const data = await response.json();
return ((_a = data.models) == null ? void 0 : _a.map((model) => model.name)) || [];
} catch (error) {
console.error("Failed to fetch Ollama models:", error);
return [];
}
}
async getUserDefinedTags() {
return new Promise(async (resolve) => {
const modal = new import_obsidian.Modal(this.app);
modal.titleEl.setText("Configure Tags");
const modelContainer = modal.contentEl.createDiv();
modelContainer.style.marginBottom = "1em";
const modelLabel = modelContainer.createEl("label");
modelLabel.setText("Select Ollama Model:");
const modelSelect = modelContainer.createEl("select");
modelSelect.style.width = "100%";
modelSelect.style.marginTop = "0.5em";
const placeholderOption = modelSelect.createEl("option");
placeholderOption.value = "";
placeholderOption.text = "Select a model...";
placeholderOption.disabled = true;
placeholderOption.selected = !this.settings.selectedModel;
try {
const models = await this.getOllamaModels();
models.forEach((model) => {
const option = modelSelect.createEl("option");
option.value = model;
option.text = model;
if (model === this.settings.selectedModel) {
option.selected = true;
}
});
} catch (error) {
console.error("Failed to load models:", error);
const option = modelSelect.createEl("option");
option.text = "Failed to load models";
option.disabled = true;
}
const tagsContainer = modal.contentEl.createDiv();
tagsContainer.style.marginTop = "1em";
const tagsLabel = tagsContainer.createEl("label");
tagsLabel.setText("Enter tags (comma-separated):");
const input = tagsContainer.createEl("textarea");
input.style.width = "100%";
input.style.height = "100px";
input.style.marginTop = "0.5em";
input.placeholder = "Enter tags separated by commas...";
if (this.settings.defaultTags.length > 0) {
input.value = this.settings.defaultTags.join(", ");
}
const buttonContainer = modal.contentEl.createDiv();
buttonContainer.style.marginTop = "1em";
buttonContainer.style.display = "flex";
buttonContainer.style.justifyContent = "flex-end";
buttonContainer.style.gap = "10px";
const cancelButton = buttonContainer.createEl("button", { text: "Cancel" });
const okButton = buttonContainer.createEl("button", { text: "OK", cls: "mod-cta" });
cancelButton.addEventListener("click", () => {
resolve(null);
modal.close();
});
okButton.addEventListener("click", () => {
if (!modelSelect.value) {
new import_obsidian.Notice("Please select a model first");
return;
}
const tagInput = input.value.trim();
if (!tagInput) {
new import_obsidian.Notice("Please enter at least one tag");
return;
}
this.settings.selectedModel = modelSelect.value;
this.saveSettings();
const tags = tagInput.split(",").map((tag) => tag.trim()).filter((tag) => tag);
resolve(tags);
modal.close();
});
modal.open();
});
}
async processContentWithOllama(content, availableTags) {
if (!this.settings.selectedModel) {
throw new Error("No Ollama model selected");
}
if (this.isAlreadyTagged(content)) {
return content;
}
const response = await fetch("http://localhost:11434/api/generate", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
model: this.settings.selectedModel,
prompt: `You are an expert at analyzing and tagging markdown documents. Your task is to create a brief tagged summary of the document content.
Available tags: ${availableTags.join(", ")}
Instructions:
1. Create a brief 1-2 sentence summary of the content
2. Add relevant tags from the provided list
3. Only use tags from the provided list
4. Each tag MUST start with a # symbol
5. Keep the summary concise and focused
Content to analyze:
${content}
Provide a tagged summary:`,
stream: false
})
});
const data = await response.json();
const taggedSummary = data.response.trim();
if (taggedSummary) {
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
return `---
LLM-tagged: ${timestamp}
---
${taggedSummary}
---
${content}`;
}
return content;
}
shouldProcessFile(file) {
const lastTagged = this.settings.taggedFiles[file.path];
if (!lastTagged)
return true;
return file.stat.mtime > lastTagged;
}
isAlreadyTagged(content) {
return content.includes("---\nLLM-tagged:");
}
async addTagsToDocuments(view) {
if (!this.settings.selectedModel) {
new import_obsidian.Notice("Please select an Ollama model first");
return;
}
const tags = await this.getUserDefinedTags();
if (!tags)
return;
const files = this.app.vault.getMarkdownFiles();
let processed = 0;
let modified = 0;
try {
for (const file of files) {
processed++;
if (view) {
view.updateProgress(processed, files.length, file.basename);
}
if (!this.shouldProcessFile(file)) {
console.log(`Skipping ${file.basename} - already tagged and not modified`);
continue;
}
try {
const content = await this.app.vault.read(file);
if (this.isAlreadyTagged(content)) {
console.log(`Skipping ${file.basename} - already has tag metadata`);
continue;
}
const taggedContent = await this.processContentWithOllama(content, tags);
if (taggedContent !== content) {
await this.app.vault.modify(file, taggedContent);
this.settings.taggedFiles[file.path] = Date.now();
await this.saveSettings();
modified++;
new import_obsidian.Notice(`Tagged: ${file.basename}`);
}
} catch (error) {
console.error(`Error processing ${file.basename}:`, error);
new import_obsidian.Notice(`Failed to process ${file.basename}: ${error.message}`);
}
}
new import_obsidian.Notice(`Completed! Tagged ${modified} of ${files.length} files`);
} finally {
if (view) {
view.resetProgress();
}
}
}
};
var LLMTaggerView = class extends import_obsidian.ItemView {
constructor(leaf, plugin) {
super(leaf);
this.plugin = plugin;
}
getViewType() {
return VIEW_TYPE;
}
getDisplayText() {
return "LLM Tagger";
}
getIcon() {
return ICON_NAME;
}
async onOpen() {
const container = this.containerEl.children[1];
container.empty();
container.createEl("h2", { text: "LLM Tagger" });
const modelContainer = container.createDiv();
modelContainer.createEl("h3", { text: "Select Model" });
const modelSelect = modelContainer.createEl("select");
modelSelect.style.width = "100%";
modelSelect.style.marginBottom = "1em";
const placeholderOption = modelSelect.createEl("option");
placeholderOption.value = "";
placeholderOption.text = "Select a model...";
placeholderOption.disabled = true;
placeholderOption.selected = !this.plugin.settings.selectedModel;
try {
const models = await this.plugin.getOllamaModels();
models.forEach((model) => {
const option = modelSelect.createEl("option");
option.value = model;
option.text = model;
if (model === this.plugin.settings.selectedModel) {
option.selected = true;
}
});
} catch (error) {
console.error("Failed to load models:", error);
const option = modelSelect.createEl("option");
option.text = "Failed to load models";
option.disabled = true;
}
modelSelect.addEventListener("change", async () => {
this.plugin.settings.selectedModel = modelSelect.value || null;
await this.plugin.saveSettings();
});
const tagsContainer = container.createDiv();
tagsContainer.createEl("h3", { text: "Enter Tags" });
const tagsInput = tagsContainer.createEl("textarea");
tagsInput.placeholder = "Enter tags separated by commas...";
tagsInput.style.width = "100%";
tagsInput.style.height = "100px";
tagsInput.style.marginBottom = "1em";
if (this.plugin.settings.defaultTags.length > 0) {
tagsInput.value = this.plugin.settings.defaultTags.join(", ");
}
const progressContainer = container.createDiv();
progressContainer.createEl("h3", { text: "Progress" });
this.progressBar = progressContainer.createEl("progress", {
attr: { value: "0", max: "100" }
});
this.progressBar.style.width = "100%";
this.progressBar.style.marginBottom = "0.5em";
this.progressText = progressContainer.createDiv("progress-text");
this.progressText.style.fontSize = "0.9em";
this.progressText.style.color = "var(--text-muted)";
this.progressText.style.marginBottom = "1em";
this.progressText.textContent = "Ready to tag documents";
const startButton = container.createEl("button", {
text: "Start Tagging",
cls: "mod-cta"
});
startButton.style.width = "100%";
startButton.addEventListener("click", async () => {
if (!modelSelect.value) {
new import_obsidian.Notice("Please select a model first");
return;
}
const tagInput = tagsInput.value.trim();
if (!tagInput) {
new import_obsidian.Notice("Please enter at least one tag");
return;
}
const tags = tagInput.split(",").map((tag) => tag.trim()).filter((tag) => tag);
if (tagInput !== this.plugin.settings.defaultTags.join(", ")) {
this.plugin.settings.defaultTags = tags;
await this.plugin.saveSettings();
}
startButton.disabled = true;
try {
await this.plugin.addTagsToDocuments(this);
} finally {
startButton.disabled = false;
}
});
}
updateProgress(current, total, filename) {
const percentage = Math.round(current / total * 100);
this.progressBar.value = percentage;
this.progressText.textContent = `Processing ${filename} (${current}/${total})`;
}
resetProgress() {
this.progressBar.value = 0;
this.progressText.textContent = "Ready to tag documents";
}
};
var LLMTaggerSettingTab = class extends import_obsidian.PluginSettingTab {
constructor(app, plugin) {
super(app, plugin);
this.plugin = plugin;
}
async display() {
const { containerEl } = this;
containerEl.empty();
containerEl.createEl("h2", { text: "LLM Tagger Settings" });
new import_obsidian.Setting(containerEl).setName("Default Model").setDesc("Select the default Ollama model to use").addDropdown(async (dropdown) => {
dropdown.addOption("", "Select a model...");
try {
const models = await this.plugin.getOllamaModels();
models.forEach((model) => {
dropdown.addOption(model, model);
});
if (this.plugin.settings.selectedModel) {
dropdown.setValue(this.plugin.settings.selectedModel);
}
} catch (error) {
console.error("Failed to load models:", error);
dropdown.addOption("error", "Failed to load models");
dropdown.setDisabled(true);
}
dropdown.onChange(async (value) => {
this.plugin.settings.selectedModel = value || null;
await this.plugin.saveSettings();
});
});
new import_obsidian.Setting(containerEl).setName("Default Tags").setDesc("Enter default tags (comma-separated) that will be pre-filled when adding tags").addTextArea((text) => text.setPlaceholder("tag1, tag2, tag3").setValue(this.plugin.settings.defaultTags.join(", ")).onChange(async (value) => {
this.plugin.settings.defaultTags = value.split(",").map((tag) => tag.trim()).filter((tag) => tag);
await this.plugin.saveSettings();
}));
new import_obsidian.Setting(containerEl).setName("Auto-add Tags").setDesc("Automatically add tags to new documents").addToggle((toggle) => toggle.setValue(this.plugin.settings.autoAddTags).onChange(async (value) => {
this.plugin.settings.autoAddTags = value;
await this.plugin.saveSettings();
}));
}
};

49
main.ts
View file

@ -276,6 +276,40 @@ export default class LLMTaggerPlugin extends Plugin {
});
}
private addDeterministicTags(content: string, availableTags: string[]): string {
// Convert content to lowercase for case-insensitive matching
const lowerContent = content.toLowerCase();
// Create a set to track which tags we've added to avoid duplicates
const addedTags = new Set<string>();
// Find matching tags
for (const tag of availableTags) {
// Remove # if present and convert to lowercase
const cleanTag = tag.replace(/^#/, '').toLowerCase();
// Check if the tag exists as a whole word in the content
const regex = new RegExp(`\\b${cleanTag}\\b`, 'i');
if (regex.test(lowerContent)) {
addedTags.add(tag.startsWith('#') ? tag : `#${tag}`);
}
}
if (addedTags.size === 0) {
return content;
}
// Add tags at the start of the content, after any existing frontmatter
const frontMatterMatch = content.match(/^---\n[\s\S]*?\n---\n/);
if (frontMatterMatch) {
const frontMatter = frontMatterMatch[0];
const restContent = content.slice(frontMatter.length);
return `${frontMatter}${Array.from(addedTags).join(' ')} ${restContent}`;
} else {
return `${Array.from(addedTags).join(' ')} ${content}`;
}
}
async processContentWithOllama(content: string, availableTags: string[]): Promise<string> {
if (!this.settings.selectedModel) {
throw new Error('No Ollama model selected');
@ -286,7 +320,10 @@ export default class LLMTaggerPlugin extends Plugin {
return content;
}
// Get tag suggestions and placement from Ollama
// First, add deterministic tags based on word matches
let processedContent = this.addDeterministicTags(content, availableTags);
// Get tag suggestions and placement from Ollama for additional semantic tagging
const response = await fetch('http://localhost:11434/api/generate', {
method: 'POST',
headers: {
@ -300,13 +337,13 @@ Available tags: ${availableTags.join(', ')}
Instructions:
1. Create a brief 1-2 sentence summary of the content
2. Add relevant tags from the provided list
2. Add relevant tags from the provided list that WEREN'T already matched by word (don't repeat tags)
3. Only use tags from the provided list
4. Each tag MUST start with a # symbol
5. Keep the summary concise and focused
Content to analyze:
${content}
Content to analyze (with existing tags):
${processedContent}
Provide a tagged summary:`,
stream: false
@ -328,10 +365,10 @@ ${taggedSummary}
---
${content}`;
${processedContent}`;
}
return content;
return processedContent;
}
private shouldProcessFile(file: TFile): boolean {