mirror of
https://github.com/andy-stack/vaultkeeper-ai.git
synced 2026-07-22 16:30:27 +00:00
Add support for reading images (PNG, JPG, JPEG, WebP) and PDFs in ReadVaultFiles. Implement provider-specific formatting for binary content (Claude, OpenAI, Gemini). Update conversation structure to handle binary files separately from text responses. Add unpdf library for PDF text extraction in search.
121 lines
No EOL
4.1 KiB
TypeScript
121 lines
No EOL
4.1 KiB
TypeScript
import { StringTools } from "Helpers/StringTools";
|
|
import { ConversationContent } from "./ConversationContent";
|
|
import type { AIFunctionResponse } from "AIClasses/FunctionDefinitions/AIFunctionResponse";
|
|
import { Role } from "Enums/Role";
|
|
import { AIFunction } from "Enums/AIFunction";
|
|
import { isTextFile } from "Enums/FileType";
|
|
|
|
export class Conversation {
|
|
|
|
title: string;
|
|
created: Date;
|
|
updated: Date;
|
|
path: string;
|
|
|
|
contents: ConversationContent[] = [];
|
|
|
|
constructor() {
|
|
this.created = new Date();
|
|
this.updated = new Date();
|
|
this.title = `${StringTools.dateToString(this.created)}`;
|
|
}
|
|
|
|
public addFunctionResponse(
|
|
functionResponse: AIFunctionResponse,
|
|
formatBinaryFiles?: (files: Array<{type: string, path: string, contents: string}>) => string
|
|
): void {
|
|
if (functionResponse.name !== AIFunction.ReadVaultFiles) {
|
|
const functionResponseString = functionResponse.toConversationString();
|
|
this.contents.push(new ConversationContent(
|
|
Role.User,
|
|
functionResponseString,
|
|
functionResponseString,
|
|
"",
|
|
new Date(),
|
|
false,
|
|
true,
|
|
false,
|
|
functionResponse.toolId
|
|
));
|
|
return;
|
|
}
|
|
|
|
const results = (functionResponse.response as
|
|
{results: Array<{type: string, path: string, contents: string}>}).results;
|
|
|
|
const textResults = results.filter(result => isTextFile(result.type));
|
|
const binaryResults = results.filter(result => !isTextFile(result.type));
|
|
|
|
// 1. Function response with text files (or success message if none)
|
|
let functionResponseData;
|
|
if (textResults.length > 0) {
|
|
// Include text file contents in function response
|
|
functionResponseData = {
|
|
id: functionResponse.toolId,
|
|
functionResponse: {
|
|
name: functionResponse.name,
|
|
response: {
|
|
results: textResults,
|
|
...(binaryResults.length > 0 && {message: "Binary files follow in next message"})
|
|
}
|
|
}
|
|
};
|
|
} else {
|
|
// No text files - just acknowledge success
|
|
functionResponseData = {
|
|
id: functionResponse.toolId,
|
|
functionResponse: {
|
|
name: functionResponse.name,
|
|
response: {
|
|
message: "Files retrieved successfully. Binary content follows in next message.",
|
|
count: binaryResults.length
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
this.contents.push(new ConversationContent(
|
|
Role.User,
|
|
JSON.stringify(functionResponseData),
|
|
JSON.stringify(functionResponseData),
|
|
"",
|
|
new Date(),
|
|
false,
|
|
true,
|
|
false,
|
|
functionResponse.toolId
|
|
));
|
|
|
|
// 2. If there are non-text files, add follow-up user message
|
|
if (binaryResults.length > 0 && formatBinaryFiles) {
|
|
const providerContent = formatBinaryFiles(binaryResults);
|
|
|
|
this.contents.push(new ConversationContent(
|
|
Role.User,
|
|
providerContent,
|
|
providerContent,
|
|
"",
|
|
new Date(),
|
|
false,
|
|
false,
|
|
true
|
|
));
|
|
}
|
|
}
|
|
|
|
public static isConversationData(data: unknown): data is { title: string; created: string; updated: string; contents: ConversationContent[] } {
|
|
return (
|
|
typeof data === "object" &&
|
|
data !== null &&
|
|
"title" in data &&
|
|
"created" in data &&
|
|
"updated" in data &&
|
|
"contents" in data &&
|
|
typeof data.title === "string" &&
|
|
typeof data.created === "string" &&
|
|
typeof data.updated === "string" &&
|
|
Array.isArray(data.contents) &&
|
|
data.contents.every(ConversationContent.isConversationContentData)
|
|
);
|
|
}
|
|
} |