mirror of
https://github.com/andy-stack/vaultkeeper-ai.git
synced 2026-07-22 06:42:03 +00:00
refactor: centralize file attachment message using Copy enum
Replace hardcoded "The contents of the file" strings across all AI provider classes (Claude, Gemini, Mistral, OpenAI) with centralized Copy.AttachedFile enum value. Update SystemPrompt to document attached file behavior and clarify files are not in vault. Fix officeparser import to use named export and add esbuild plugin to handle browser bundle's IIFE format for mobile compatibility.
This commit is contained in:
parent
fed0287ba0
commit
9a6bfdffa0
8 changed files with 49 additions and 13 deletions
|
|
@ -16,6 +16,7 @@ import { MimeTypeToFileTypes } from "Enums/FileTypeMimeTypeMapping";
|
|||
import { ApiError, ApiErrorType } from "Types/ApiError";
|
||||
import { parseToolCall, parseFunctionResponse } from "Helpers/ResponseHelper";
|
||||
import { AIToolUsageMode } from "Enums/AIToolUsageMode";
|
||||
import { Copy, replaceCopy } from "Enums/Copy";
|
||||
|
||||
export class Claude extends BaseAIClass {
|
||||
|
||||
|
|
@ -308,7 +309,7 @@ export class Claude extends BaseAIClass {
|
|||
}
|
||||
|
||||
return [
|
||||
{type: "text", text: `The contents of the file '${attachment.fileName}' are provided below.` },
|
||||
{type: "text", text: replaceCopy(Copy.AttachedFile, [attachment.fileName]) },
|
||||
{
|
||||
type: isPlainText || mimeType === MimeType.APPLICATION_PDF ? "document" : "image",
|
||||
source: {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import { ApiError, ApiErrorType } from "Types/ApiError";
|
|||
import { parseToolCall, parseFunctionResponse } from "Helpers/ResponseHelper";
|
||||
import type { GeminiRetryInfo, GeminiErrorResponse } from "./GeminiTypes";
|
||||
import { AIToolUsageMode } from "Enums/AIToolUsageMode";
|
||||
import { Copy, replaceCopy } from "Enums/Copy";
|
||||
|
||||
export class Gemini extends BaseAIClass {
|
||||
|
||||
|
|
@ -348,7 +349,7 @@ export class Gemini extends BaseAIClass {
|
|||
continue;
|
||||
}
|
||||
|
||||
parts.push({ text: `The contents of the file '${attachment.fileName}' are provided below.` });
|
||||
parts.push({ text: replaceCopy(Copy.AttachedFile, [attachment.fileName])});
|
||||
parts.push({
|
||||
fileData: {
|
||||
mimeType: mimeType,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import { parseToolCall, parseFunctionResponse } from "Helpers/ResponseHelper";
|
|||
import { AIToolUsageMode } from "Enums/AIToolUsageMode";
|
||||
import type { MistralStreamChunk, MistralToolDefinition, MistralMessage, MistralContentPart } from "./MistralTypes";
|
||||
import type { MistralFileService } from "./MistralFileService";
|
||||
import { Copy, replaceCopy } from "Enums/Copy";
|
||||
|
||||
export class Mistral extends BaseAIClass {
|
||||
|
||||
|
|
@ -355,7 +356,7 @@ export class Mistral extends BaseAIClass {
|
|||
const signedUrl = fileService.getSignedUrl(fileID);
|
||||
if (signedUrl) {
|
||||
contentParts.push(
|
||||
{ type: "text", text: `The contents of the file '${attachment.fileName}' are provided below.` },
|
||||
{ type: "text", text: replaceCopy(Copy.AttachedFile, [attachment.fileName]) },
|
||||
{ type: "document_url", document_url: signedUrl }
|
||||
);
|
||||
} else {
|
||||
|
|
@ -371,7 +372,7 @@ export class Mistral extends BaseAIClass {
|
|||
const signedUrl = fileService.getSignedUrl(fileID);
|
||||
if (signedUrl) {
|
||||
contentParts.push(
|
||||
{ type: "text", text: `The contents of the file '${attachment.fileName}' are provided below.` },
|
||||
{ type: "text", text: replaceCopy(Copy.AttachedFile, [attachment.fileName]) },
|
||||
{
|
||||
type: "image_url",
|
||||
image_url: signedUrl
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import { isTextFile } from "Enums/FileType";
|
|||
import { MimeTypeToFileTypes } from "Enums/FileTypeMimeTypeMapping";
|
||||
import { parseToolCall, parseFunctionResponse } from "Helpers/ResponseHelper";
|
||||
import { AIToolUsageMode } from "Enums/AIToolUsageMode";
|
||||
import { Copy, replaceCopy } from "Enums/Copy";
|
||||
|
||||
export class OpenAI extends BaseAIClass {
|
||||
|
||||
|
|
@ -352,7 +353,7 @@ export class OpenAI extends BaseAIClass {
|
|||
}
|
||||
|
||||
contentBlocks.push(
|
||||
{ type: "input_text", text: `The contents of the file '${attachment.fileName}' are provided below.` },
|
||||
{ type: "input_text", text: replaceCopy(Copy.AttachedFile, [attachment.fileName]) },
|
||||
{
|
||||
type: isPlainText || mimeType === MimeType.APPLICATION_PDF ? "input_file" : "input_image",
|
||||
file_id: fileID
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { Copy } from "Enums/Copy";
|
||||
|
||||
export const SystemInstruction: string = `
|
||||
# Obsidian AI Assistant
|
||||
|
||||
|
|
@ -18,7 +20,8 @@ You are a specialized AI assistant with direct access to the user's Obsidian vau
|
|||
- Task verbs (create, generate, update, delete) → Execute corresponding function
|
||||
- Implied actions ("I need X") → Call the function that produces X
|
||||
- Outcome requests ("Show me Y") → Use tools to retrieve/generate Y
|
||||
- Image/PDF references → Read the file first
|
||||
- Image/PDF/Document references → Read the file first
|
||||
- Attached files → Use the provided content directly
|
||||
|
||||
**Example:**
|
||||
User: "Create a note about today's meeting with Sarah"
|
||||
|
|
@ -255,6 +258,15 @@ When searches return or reference images or PDFs:
|
|||
- Extract relevant information to answer the user's query
|
||||
- Reference the source file with [[wiki-links]] as usual
|
||||
|
||||
### File Attachments
|
||||
|
||||
**Users can attach files directly to their messages.** When a file is attached, its content is provided inline in the conversation. Attached files are likely NOT present in the vault so use the attached content directly.
|
||||
|
||||
**How to recognize attached files:**
|
||||
- A text block states: ${Copy.AttachedFile}
|
||||
- The file content or file ID immediately follows that text block
|
||||
- Attachments for document filetypes (.docx, .odt, .xlsx, etc.,) are included as plain text
|
||||
|
||||
---
|
||||
|
||||
## Obsidian Bases
|
||||
|
|
|
|||
|
|
@ -101,6 +101,10 @@ export enum Copy {
|
|||
ButtonTurnOffPlanningMode = "Turn off Planning Mode",
|
||||
ButtonTurnOnPlanningMode = "Turn on Planning Mode",
|
||||
|
||||
// Agent file message
|
||||
AttachedFile = `The user has attached the file {fileName}. The contents of the file are included below.
|
||||
**Note that this is an attachment to the chat and the file is likely NOT present in the vault**`,
|
||||
|
||||
// Execution Plan Messages
|
||||
PlanningFailedError = `Failed to generate plan. You should attempt to recover from this.
|
||||
### Next Actions
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { extractText, getDocumentProxy } from 'unpdf';
|
||||
import type { IPageText } from '../Types/SearchTypes';
|
||||
import { Exception } from './Exception';
|
||||
import OfficeParser from 'officeparser';
|
||||
import { parseOffice } from 'officeparser';
|
||||
|
||||
// Handles PDF format
|
||||
export async function readPDF(arrayBuffer: ArrayBuffer): Promise<IPageText[]> {
|
||||
|
|
@ -34,7 +34,7 @@ export async function readPDF(arrayBuffer: ArrayBuffer): Promise<IPageText[]> {
|
|||
// Handles document formats: DOCX, PPTX, XLSX, ODT, ODP, ODS
|
||||
export async function readDocument(arrayBuffer: ArrayBuffer): Promise<IPageText[]> {
|
||||
try {
|
||||
const ast = await OfficeParser.parseOffice(arrayBuffer, {
|
||||
const ast = await parseOffice(arrayBuffer, {
|
||||
extractAttachments: true,
|
||||
ocr: true,
|
||||
ocrLanguage: "eng+esp"
|
||||
|
|
|
|||
|
|
@ -102,13 +102,29 @@ const cssMergerPlugin = {
|
|||
}
|
||||
};
|
||||
|
||||
const buildOptions = {
|
||||
// officeparser's default entry point requires Node's "fs" module, which crashes the plugin on
|
||||
// Obsidian mobile. The browser bundle provides identical functionality using web APIs instead.
|
||||
alias: {
|
||||
'officeparser': './node_modules/officeparser/dist/officeparser.browser.js',
|
||||
// officeparser's browser bundle is an IIFE (var officeParser = (()=> { ... })()) that doesn't
|
||||
// set module.exports, so esbuild can't resolve its exports. This plugin intercepts the resolve
|
||||
// and appends a CJS export line so imports work correctly.
|
||||
const officeParserPlugin = {
|
||||
name: "officeparser-cjs-shim",
|
||||
setup(build) {
|
||||
build.onResolve({ filter: /^officeparser$/ }, () => ({
|
||||
path: join(process.cwd(), 'node_modules', 'officeparser', 'dist', 'officeparser.browser.js'),
|
||||
namespace: 'officeparser-shim',
|
||||
}));
|
||||
build.onLoad({ filter: /.*/, namespace: 'officeparser-shim' }, async (args) => {
|
||||
const contents = readFileSync(args.path, 'utf-8');
|
||||
return {
|
||||
contents: contents + '\nmodule.exports = officeParser;\n',
|
||||
loader: 'js',
|
||||
};
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
const buildOptions = {
|
||||
plugins: [
|
||||
officeParserPlugin,
|
||||
esbuildSvelte({
|
||||
compilerOptions: { css: "injected" },
|
||||
preprocess: sveltePreprocess(),
|
||||
|
|
|
|||
Loading…
Reference in a new issue