logancyang_obsidian-copilot/src/contextProcessor.ts
Logan Yang 684a3fc2aa Squashed new commits from master to preview
commit 730d0c82b1
Author: Logan Yang <logancyang@gmail.com>
Date:   Mon Apr 21 20:13:21 2025 -0700

    Switch insert and copy button positions (#1463)

commit 4efdc941f2
Author: Logan Yang <logancyang@gmail.com>
Date:   Mon Apr 21 16:15:50 2025 -0700

    Fix canvas read (#1462)

commit 93e4a04ef1
Author: Emt-lin <41323133+Emt-lin@users.noreply.github.com>
Date:   Tue Apr 22 07:13:47 2025 +0800

    fix: Add a new line when press the Enter key on mobile. (#1450)

commit 3c65d79a74
Author: Logan Yang <logancyang@gmail.com>
Date:   Mon Apr 21 15:38:36 2025 -0700

    Implement canvas adaptor (#1461)

    * Implement canvas parser
    * Refactor context processing and custom prompt handling

    - Update `processContextNotes` to exclude notes already processed by custom prompts using a set of excluded note paths.
    - Modify `extractVariablesFromPrompt` to return both a variables map and a set of included files.
    - Adjust `processPrompt` to track included files during prompt processing.
    - Update `Chat` component to handle context notes more efficiently by avoiding duplication of processed notes.
    - Ensure that the `FileParserManager` is initialized with the vault in `main.ts`.

    * Include canvas files in note context modal
    * Fix tests

commit 6a9085a71c
Author: Logan Yang <logancyang@gmail.com>
Date:   Sun Apr 20 16:14:54 2025 -0700

    Add a toggle to turn custom prompt templating off (#1460)

commit 448a64c383
Author: Logan Yang <logancyang@gmail.com>
Date:   Sun Apr 20 15:52:42 2025 -0700

    Update dependencies and support gpt 4.1 series, o4-mini and grok 3 (#1459)

commit 1d46ab90b0
Author: Logan Yang <logancyang@gmail.com>
Date:   Sun Apr 20 12:14:56 2025 -0700

    Fix image in note logic (#1457)

    * Refactor image processing in note context
    * Add passMarkdownImages setting

commit f4bf334c27
Author: Felix Haase <felix.haase@feki.de>
Date:   Fri Apr 18 08:19:35 2025 +0200

    Ollama ApiKey support (#1421)

    * Update @langchain/ollama and ollama
    * Ollama: support api keys by passing headers

commit 19d8b50a74
Author: Emt-lin <41323133+Emt-lin@users.noreply.github.com>
Date:   Fri Apr 18 14:13:25 2025 +0800

    refactor: Optimize some user experiences. (#1441)

commit fe9b9311ba
Author: Zero Liu <zerolxy@gmail.com>
Date:   Sun Apr 13 17:40:42 2025 -0700

    Improve custom command (v3) (#1446)

    * Trim responses and auto focus replace on finishing
    * Make textarea auto scroll to bottom when generating
    * Support custom prompt syntax in custom command
    * Support follow up instruction
    * Fix unit test
    * Allow followup instruction to use custom prompt syntax

commit 648412a914
Author: Zero Liu <zerolxy@gmail.com>
Date:   Wed Apr 2 21:44:37 2025 -0700

    Add update notification (#1415)

commit cb4510e920
Author: Zero Liu <zerolxy@gmail.com>
Date:   Wed Apr 2 21:43:26 2025 -0700

    Add user_id to broca requests (#1414)
2025-04-22 00:04:15 -07:00

163 lines
5.2 KiB
TypeScript

import { ChainType } from "@/chainFactory";
import { FileParserManager } from "@/tools/FileParserManager";
import { TFile, Vault } from "obsidian";
export class ContextProcessor {
private static instance: ContextProcessor;
private constructor() {}
static getInstance(): ContextProcessor {
if (!ContextProcessor.instance) {
ContextProcessor.instance = new ContextProcessor();
}
return ContextProcessor.instance;
}
async processEmbeddedPDFs(
content: string,
vault: Vault,
fileParserManager: FileParserManager
): Promise<string> {
const pdfRegex = /!\[\[(.*?\.pdf)\]\]/g;
const matches = [...content.matchAll(pdfRegex)];
for (const match of matches) {
const pdfName = match[1];
const pdfFile = vault.getAbstractFileByPath(pdfName);
if (pdfFile instanceof TFile) {
try {
const pdfContent = await fileParserManager.parseFile(pdfFile, vault);
content = content.replace(match[0], `\n\nEmbedded PDF (${pdfName}):\n${pdfContent}\n\n`);
} catch (error) {
console.error(`Error processing embedded PDF ${pdfName}:`, error);
content = content.replace(
match[0],
`\n\nEmbedded PDF (${pdfName}): [Error: Could not process PDF]\n\n`
);
}
}
}
return content;
}
/**
* Processes context notes, excluding any already handled by custom prompts.
*
* @param excludedNotePaths A set of file paths that should be skipped.
* @param fileParserManager
* @param vault
* @param contextNotes
* @param includeActiveNote
* @param activeNote
* @param currentChain
* @returns The combined content string of the processed context notes.
*/
async processContextNotes(
excludedNotePaths: Set<string>,
fileParserManager: FileParserManager,
vault: Vault,
contextNotes: TFile[],
includeActiveNote: boolean,
activeNote: TFile | null,
currentChain: ChainType
): Promise<string> {
let additionalContext = "";
const processNote = async (note: TFile) => {
try {
// Check if this note was already processed (via custom prompt)
if (excludedNotePaths.has(note.path)) {
console.log(`Skipping note ${note.path} as it was included via custom prompt.`);
return;
}
console.log(
`Processing note: ${note.path}, extension: ${note.extension}, chain: ${currentChain}`
);
// 1. Check if the file extension is supported by any parser
if (!fileParserManager.supportsExtension(note.extension)) {
console.warn(`Unsupported file type: ${note.extension}`);
return;
}
// 2. Apply chain restrictions only to supported files that are NOT md or canvas
if (
currentChain !== ChainType.COPILOT_PLUS_CHAIN &&
note.extension !== "md" &&
note.extension !== "canvas"
) {
// This file type is supported, but requires Plus mode (e.g., PDF)
console.warn(
`File type ${note.extension} requires Copilot Plus mode for context processing.`
);
return;
}
// 3. If we reach here, parse the file (md, canvas, or other supported type in Plus mode)
let content = await fileParserManager.parseFile(note, vault);
// Special handling for embedded PDFs within markdown (only in Plus mode)
if (note.extension === "md" && currentChain === ChainType.COPILOT_PLUS_CHAIN) {
content = await this.processEmbeddedPDFs(content, vault, fileParserManager);
}
additionalContext += `\n\nTitle: [[${note.basename}]]\nPath: ${note.path}\n\n${content}`;
} catch (error) {
console.error(`Error processing file ${note.path}:`, error);
additionalContext += `\n\nTitle: [[${note.basename}]]\nPath: ${note.path}\n\n[Error: Could not process file]`;
}
};
// Process active note if included
if (includeActiveNote && activeNote) {
await processNote(activeNote);
}
// Process context notes
for (const note of contextNotes) {
await processNote(note);
}
return additionalContext;
}
async hasEmbeddedPDFs(content: string): Promise<boolean> {
const pdfRegex = /!\[\[(.*?\.pdf)\]\]/g;
return pdfRegex.test(content);
}
async addNoteToContext(
note: TFile,
vault: Vault,
contextNotes: TFile[],
activeNote: TFile | null,
setContextNotes: (notes: TFile[] | ((prev: TFile[]) => TFile[])) => void,
setIncludeActiveNote: (include: boolean) => void
): Promise<void> {
// Only check if the note exists in contextNotes
if (contextNotes.some((existing) => existing.path === note.path)) {
return; // Note already exists in context
}
// Read the note content
const content = await vault.read(note);
const hasEmbeddedPDFs = await this.hasEmbeddedPDFs(content);
// Set includeActiveNote if it's the active note
if (activeNote && note.path === activeNote.path) {
setIncludeActiveNote(true);
}
// Add to contextNotes with wasAddedViaReference flag
setContextNotes((prev: TFile[]) => [
...prev,
Object.assign(note, {
wasAddedViaReference: true,
hasEmbeddedPDFs,
}),
]);
}
}