Add demo GIF and refine README; improve tag and note generation utilities

This commit is contained in:
Travis V 2025-02-07 16:11:28 -05:00
parent a998e8d44e
commit d00026a760
4 changed files with 53 additions and 32 deletions

View file

@ -72,3 +72,5 @@
- **Opening Notes:** Click on a screenshot in the Vision Recall View to open its generated Obsidian note, containing summaries, OCR text, vision analysis, and tags.
- **Editing Metadata:** Edit tags and other metadata directly from the Vision Recall View.
- **Managing Processing Queue:** Use the Processing Queue modal (accessible from the status bar button or commands) to monitor and control screenshot processing.
![Vision Recall Obsidian Plugin Demo](./demo.gif)

BIN
demo.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 MiB

View file

@ -10,7 +10,6 @@ export const TagsSchema = z.object({
// export const tagsJsonSchema = TagsSchema.toJSON();
export const tagsJsonSchema = zodToJsonSchema(TagsSchema);
export function formatTags(tags: string[]): string[] {
return tags.map((tag) => {
let formattedTag = tag.replace(/ /g, '_');
@ -39,3 +38,20 @@ export function tagsToCommaString(tags: string[]): string {
export function tagsFromCommaString(tags: string): string[] {
return tags.split(',').map(tag => tag.trim());
}
export function sanitizeObsidianTag(tag: string): string | null {
// Replace spaces with underscores to maintain readability
tag = tag.replace(/\s+/g, '_');
// Remove all disallowed characters (keep letters, numbers, _, -, /)
tag = tag.replace(/[^a-zA-Z0-9_\/-]/g, '');
// Ensure there's at least one non-numeric character
if (!/[a-zA-Z_\/-]/.test(tag)) {
return null; // Return null if there's no valid non-numeric character
}
// Convert to lowercase to ensure case insensitivity
// return tag.toLowerCase();
return tag;
}

View file

@ -4,7 +4,7 @@ import { VisionRecallPluginSettings } from '@/types/settings-types';
import { DEFAULT_TAGS_AND_TITLE, TagsAndTitle, VISION_LLM_PROMPT, callLLMAPI, llmSuggestTagsAndTitle } from '@/services/llm-service';
import VisionRecallPlugin from '@/main';
import { checkOCRText } from '@/lib/ocr-validation';
import { formatTags, tagsToCommaString } from '@/lib/tag-utils';
import { formatTags, sanitizeObsidianTag, tagsToCommaString } from '@/lib/tag-utils';
import { useQueueStore } from '@/stores/queueStore';
import { PluginLogger } from '@/lib/Logger';
import { base64EncodeImage } from '@/lib/encode';
@ -252,6 +252,9 @@ export class ScreenshotProcessor {
this.progressManager.updateProgress('Saving results...', 20);
if (this.progressManager.isStoppedByUser()) return false;
const tagPath = sanitizeObsidianTag(paths.uniqueName);
const linkingTag = `#${this.settings.tagPrefix}/${tagPath}`;
const noteInfo = await this.createObsidianNote(
imageFile,
paths.newScreenshotPath,
@ -261,7 +264,8 @@ export class ScreenshotProcessor {
tagsAndTitle,
formattedTags,
entryId,
paths.uniqueName
paths.uniqueName,
linkingTag
);
if (!noteInfo || this.progressManager.isStoppedByUser()) return false;
@ -276,7 +280,8 @@ export class ScreenshotProcessor {
tagsAndTitle,
formattedTags,
noteInfo,
entryId
entryId,
linkingTag
);
if (this.progressManager.isStoppedByUser()) return false;
@ -414,14 +419,14 @@ export class ScreenshotProcessor {
try {
const outputNotesFolder = normalizePath(await this.plugin.getFolderFromSettingsKey('outputNotesFolderPath'));
const noteTitle = tagsAndTitle.title || imageFile.basename;
let noteFileName = `${noteTitle} Notes.md`;
let notePath = normalizePath(`${outputNotesFolder}/${noteFileName}`);
const noteTitle = sanitizeFilename(tagsAndTitle.title || imageFile.basename);
let noteFileName = `${noteTitle} Notes`;
let notePath = normalizePath(`${outputNotesFolder}/${noteFileName}.md`);
let counter = 1;
while (this.app.vault.getAbstractFileByPath(notePath) && counter < 100) {
noteFileName = `${noteTitle} Notes (${counter}).md`;
notePath = normalizePath(`${outputNotesFolder}/${noteFileName}`);
noteFileName = `${noteTitle} Notes (${counter})`;
notePath = normalizePath(`${outputNotesFolder}/${noteFileName}.md`);
counter++;
}
@ -447,7 +452,8 @@ export class ScreenshotProcessor {
tagsAndTitle: TagsAndTitle,
formattedTags: string,
id: string,
uniqueName: string
uniqueName: string,
linkingTag: string
): Promise<{ notePath: string; noteTitle: string } | null> {
try {
const outputNotesFolder = normalizePath(await this.plugin.getFolderFromSettingsKey('outputNotesFolderPath'));
@ -488,8 +494,6 @@ export class ScreenshotProcessor {
// Main note content
const noteContent = `# Notes from Screenshot: ${noteTitleBase}\n\n${generatedNotes}`;
const linkingTag = `#${this.settings.tagPrefix}/${uniqueName}`;
const metadataContent = `\n\n---\n*Screenshot Filename:* [[${newScreenshotPath}]]\n*${ocrTitle}*:\n\`\`\`\n${truncatedOcrText}...\n\`\`\`\n*${visionTitle}*:\n\`\`\`\n${truncatedVisionLLMResponse}...\n\`\`\`\n\n*Tags:* ${formattedTags}\n\n${linkingTag}\n`;
const finalNoteContent = this.settings.includeMetadataInNote
@ -522,7 +526,8 @@ export class ScreenshotProcessor {
tagsAndTitle: TagsAndTitle,
formattedTags: string,
noteInfo: { notePath: string; noteTitle: string },
entryId: string
entryId: string,
linkingTag: string
): Promise<void> {
try {
@ -534,8 +539,6 @@ export class ScreenshotProcessor {
const metadataFilename = `${uniqueName}.json`;
const metadataPath = normalizePath(`${await this.plugin.getFolderFromSettingsKey('screenshotStorageFolderPath')}/${metadataFilename}`);
const linkingTag = `#${this.settings.tagPrefix}/${uniqueName}`;
const metadata = {
id: entryId,
originalFilename: imageFile.name,